From 935e7dc123c29657b20d848e54e4dd3043067b0e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 11 Mar 2023 20:06:06 +0100 Subject: [PATCH 001/143] Update dependency org.jetbrains.dokka:dokka-gradle-plugin to v1.8.10 (#1119) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 32f132ed94..9adf0fc988 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -40,7 +40,7 @@ mockito = { module = "org.mockito:mockito-core", version = "5.1.1"} # plugins needed for build.gradle.kts in buildSrc kotlin-gradle = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } -dokka-gradle = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version = "1.7.20" } # the dokka plugin is slightly behind the main Kotlin release cycle +dokka-gradle = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version = "1.8.10" } # the dokka plugin is slightly behind the main Kotlin release cycle sonarqube-gradle = { module = "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin", version.ref = "sonarqube" } spotless-gradle = { module = "com.diffplug.spotless:spotless-plugin-gradle", version.ref = "spotless" } From b416895a78cb93911d8865f4916838f4dfa5e640 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 11 Mar 2023 19:19:55 +0000 Subject: [PATCH 002/143] Update dependency org.jline:jline to v3.23.0 (#1120) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9adf0fc988..ead5f5c3be 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -15,7 +15,7 @@ kotlin-test-junit5 = { module = "org.jetbrains.kotlin:kotlin-test-junit5", versi log4j-impl = { module = "org.apache.logging.log4j:log4j-slf4j2-impl", version.ref = "log4j" } log4j-core = { module= "org.apache.logging.log4j:log4j-core", version.ref = "log4j" } -jline = { module = "org.jline:jline", version = "3.22.0" } +jline = { module = "org.jline:jline", version = "3.23.0" } apache-commons-lang3 = { module = "org.apache.commons:commons-lang3", version = "3.12.0"} neo4j-ogm-core = { module = "org.neo4j:neo4j-ogm-core", version.ref = "neo4j"} neo4j-ogm = { module = "org.neo4j:neo4j-ogm", version.ref = "neo4j"} From cad4b8b423cb7ba3291d7753063dbb5188fe75c6 Mon Sep 17 00:00:00 2001 From: KuechA <31155350+KuechA@users.noreply.github.com> Date: Sat, 11 Mar 2023 21:48:25 +0100 Subject: [PATCH 003/143] Convert types and utilities to Kotlin (#1112) Co-authored-by: Christian Banse --- .../aisec/cpg/analysis/MultiValueEvaluator.kt | 3 +- .../aisec/cpg/passes/UnreachableEOGPass.kt | 10 +- .../fraunhofer/aisec/cpg/query/QueryTree.kt | 4 +- .../fraunhofer/aisec/cpg/query/QueryTest.kt | 14 +- .../aisec/cpg/analysis/NullPointerCheck.kt | 4 +- .../aisec/cpg/analysis/OutOfBoundsCheck.kt | 2 +- .../aisec/cpg/graph/TypeManager.java | 20 +- .../aisec/cpg/graph/package-info.java | 2 - .../cpg/graph/types/FunctionPointerType.java | 150 ------------ .../aisec/cpg/graph/types/ObjectType.java | 190 ---------------- .../aisec/cpg/graph/types/PointerType.java | 178 --------------- .../aisec/cpg/graph/types/ReferenceType.java | 122 ---------- .../aisec/cpg/graph/types/Type.java | 213 ------------------ .../aisec/cpg/graph/types/TypeParser.java | 2 +- .../aisec/cpg/graph/types/UnknownType.java | 119 ---------- .../aisec/cpg/graph/types/WrapState.java | 74 ------ .../aisec/cpg/helpers/CommonPath.java | 79 ------- .../aisec/cpg/helpers/LocationConverter.java | 77 ------- .../aisec/cpg/helpers/package-info.java | 2 - .../cpg/processing/strategy/Strategy.java | 105 --------- .../aisec/cpg/sarif/PhysicalLocation.java | 122 ---------- .../de/fraunhofer/aisec/cpg/sarif/Region.java | 124 ---------- .../aisec/cpg/sarif/package-info.java | 8 - .../de/fraunhofer/aisec/cpg/ScopeManager.kt | 18 +- .../aisec/cpg/frontends/Language.kt | 10 +- .../aisec/cpg/frontends/cpp/CXXHandler.kt | 6 +- .../cpg/frontends/cpp/CXXLanguageFrontend.kt | 4 +- .../cpg/frontends/cpp/DeclarationHandler.kt | 4 +- .../cpg/frontends/cpp/DeclaratorHandler.kt | 37 +-- .../cpg/frontends/cpp/ExpressionHandler.kt | 17 +- .../de/fraunhofer/aisec/cpg/graph/AST.kt | 2 +- .../aisec/cpg/graph/ArgumentHolder.kt | 0 .../aisec/cpg/graph/DeclarationBuilder.kt | 7 +- .../aisec/cpg/graph/EdgeProperty.kt} | 9 +- .../aisec/cpg/graph/ExpressionBuilder.kt | 23 +- .../de/fraunhofer/aisec/cpg/graph/Holder.kt | 0 .../de/fraunhofer/aisec/cpg/graph/Node.kt | 4 +- .../fraunhofer/aisec/cpg/graph/NodeBuilder.kt | 13 ++ .../aisec/cpg/graph/Persistable.kt} | 4 +- .../aisec/cpg/graph/builder/Fluent.kt | 24 +- .../declarations/ClassTemplateDeclaration.kt | 10 +- .../graph/declarations/FunctionDeclaration.kt | 4 +- .../graph/declarations/RecordDeclaration.kt | 32 +-- .../graph/declarations/TypedefDeclaration.kt | 4 +- .../graph/declarations/ValueDeclaration.kt | 54 ++--- .../aisec/cpg/graph/scopes/Scope.kt | 2 +- .../statements/expressions/BinaryOperator.kt | 4 +- .../statements/expressions/CallExpression.kt | 3 +- .../statements/expressions/CastExpression.kt | 8 +- .../expressions/ConditionalExpression.kt | 4 +- .../statements/expressions/Expression.kt | 60 +++-- .../statements/expressions/ExpressionList.kt | 12 +- .../expressions/InitializerListExpression.kt | 5 +- .../expressions/LambdaExpression.kt | 2 +- .../aisec/cpg/graph/types/BooleanType.kt | 0 .../cpg/graph/types/FloatingPointType.kt | 0 .../cpg/graph/types/FunctionPointerType.kt | 116 ++++++++++ .../aisec/cpg/graph/types/FunctionType.kt | 30 +-- .../aisec/cpg/graph/{ => types}/HasType.kt | 0 .../aisec/cpg/graph/types/IncompleteType.kt} | 61 ++--- .../aisec/cpg/graph/types/IntegerType.kt | 0 .../aisec/cpg/graph/types/NumericType.kt | 5 +- .../aisec/cpg/graph/types/ObjectType.kt | 151 +++++++++++++ .../cpg/graph/types/ParameterizedType.kt} | 45 ++-- .../aisec/cpg/graph/types/PointerType.kt | 140 ++++++++++++ .../aisec/cpg/graph/types/ReferenceType.kt | 99 ++++++++ .../aisec/cpg/graph/types/SecondOrderType.kt} | 9 +- .../aisec/cpg/graph/types/StringType.kt | 0 .../fraunhofer/aisec/cpg/graph/types/Type.kt | 178 +++++++++++++++ .../aisec/cpg/graph/types/UnknownType.kt | 97 ++++++++ .../aisec/cpg/graph/types/WrapState.kt | 44 ++++ .../aisec/cpg/helpers/CommentMatcher.kt | 0 .../aisec/cpg/helpers/CommonPath.kt | 69 ++++++ .../aisec/cpg/helpers/IdentitySet.kt | 14 +- .../aisec/cpg/helpers/MeasurementHolder.kt | 0 .../aisec/cpg/helpers/SubgraphWalker.kt | 41 ++-- .../aisec/cpg/helpers/TriConsumer.kt} | 6 +- .../de/fraunhofer/aisec/cpg/helpers/Util.kt | 50 +--- .../annotations/FunctionReplacement.kt | 0 .../cpg/helpers/neo4j/LocationConverter.kt | 75 ++++++ .../aisec/cpg/helpers/neo4j}/NameConverter.kt | 2 +- .../aisec/cpg/passes/CXXCallResolverHelper.kt | 2 +- .../aisec/cpg/passes/CallResolver.kt | 8 +- .../cpg/passes/EvaluationOrderGraphPass.kt | 33 +-- .../aisec/cpg/passes/FilenameMapper.kt | 8 +- .../cpg/passes/FunctionPointerCallResolver.kt | 2 +- .../aisec/cpg/passes/ImportResolver.kt | 16 +- .../de/fraunhofer/aisec/cpg/passes/Pass.kt | 13 +- .../aisec/cpg/passes/SymbolResolverPass.kt | 6 +- .../cpg/passes/TemplateCallResolverHelper.kt | 14 +- .../aisec/cpg/passes/TypeHierarchyResolver.kt | 14 +- .../aisec/cpg/passes/VariableUsageResolver.kt | 12 +- .../aisec/cpg/processing/IStrategy.kt} | 15 +- .../aisec/cpg/processing/IVisitable.kt} | 35 ++- .../aisec/cpg/processing/IVisitor.kt} | 36 ++- .../aisec/cpg/processing/strategy/Strategy.kt | 93 ++++++++ .../aisec/cpg/sarif/PhysicalLocation.kt | 79 +++++++ .../de/fraunhofer/aisec/cpg/sarif/Region.kt | 66 ++++++ .../templates/FunctionTemplateTest.kt | 10 +- .../frontends/cpp/CXXLanguageFrontendTest.kt | 13 +- .../aisec/cpg/graph/types/TypeTests.kt | 18 +- .../aisec/cpg/helpers/IdentitySetTest.kt | 22 +- .../cpg/helpers/LocationConverterTest.kt | 35 +-- .../cpg/frontends/java/DeclarationHandler.kt | 4 +- .../cpg/frontends/java/ExpressionHandler.kt | 32 ++- .../frontends/java/JavaLanguageFrontend.kt | 11 +- .../cpg/frontends/java/StatementHandler.kt | 2 +- .../JavaExternalTypeHierarchyResolver.kt | 7 +- .../aisec/cpg/graph/types/TypeTests.kt | 2 +- .../aisec/cpg/helpers/CommentMatcherTest.kt | 7 +- .../cpg/frontends/llvm/ExpressionHandler.kt | 17 +- .../frontends/llvm/LLVMIRLanguageFrontend.kt | 3 +- .../cpg/frontends/llvm/StatementHandler.kt | 10 +- .../src/main/python/CPGPython/_expressions.py | 13 +- .../src/main/python/CPGPython/_statements.py | 17 +- .../typescript/DeclarationHandler.kt | 12 +- .../frontends/typescript/ExpressionHandler.kt | 9 +- .../cpg/frontends/typescript/TypeHandler.kt | 9 +- .../typescript/TypeScriptLanguageFrontend.kt | 8 +- 119 files changed, 1717 insertions(+), 2238 deletions(-) delete mode 100644 cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/package-info.java delete mode 100644 cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/types/FunctionPointerType.java delete mode 100644 cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/types/ObjectType.java delete mode 100644 cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/types/PointerType.java delete mode 100644 cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/types/ReferenceType.java delete mode 100644 cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/types/Type.java delete mode 100644 cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/types/UnknownType.java delete mode 100644 cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/types/WrapState.java delete mode 100644 cpg-core/src/main/java/de/fraunhofer/aisec/cpg/helpers/CommonPath.java delete mode 100644 cpg-core/src/main/java/de/fraunhofer/aisec/cpg/helpers/LocationConverter.java delete mode 100644 cpg-core/src/main/java/de/fraunhofer/aisec/cpg/helpers/package-info.java delete mode 100644 cpg-core/src/main/java/de/fraunhofer/aisec/cpg/processing/strategy/Strategy.java delete mode 100644 cpg-core/src/main/java/de/fraunhofer/aisec/cpg/sarif/PhysicalLocation.java delete mode 100644 cpg-core/src/main/java/de/fraunhofer/aisec/cpg/sarif/Region.java delete mode 100644 cpg-core/src/main/java/de/fraunhofer/aisec/cpg/sarif/package-info.java rename cpg-core/src/main/{java => kotlin}/de/fraunhofer/aisec/cpg/graph/ArgumentHolder.kt (100%) rename cpg-core/src/main/{java/de/fraunhofer/aisec/cpg/helpers/TriConsumer.java => kotlin/de/fraunhofer/aisec/cpg/graph/EdgeProperty.kt} (86%) rename cpg-core/src/main/{java => kotlin}/de/fraunhofer/aisec/cpg/graph/Holder.kt (100%) rename cpg-core/src/main/{java/de/fraunhofer/aisec/cpg/graph/Persistable.java => kotlin/de/fraunhofer/aisec/cpg/graph/Persistable.kt} (93%) rename cpg-core/src/main/{java => kotlin}/de/fraunhofer/aisec/cpg/graph/types/BooleanType.kt (100%) rename cpg-core/src/main/{java => kotlin}/de/fraunhofer/aisec/cpg/graph/types/FloatingPointType.kt (100%) create mode 100644 cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/FunctionPointerType.kt rename cpg-core/src/main/{java => kotlin}/de/fraunhofer/aisec/cpg/graph/types/FunctionType.kt (81%) rename cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/{ => types}/HasType.kt (100%) rename cpg-core/src/main/{java/de/fraunhofer/aisec/cpg/graph/types/IncompleteType.java => kotlin/de/fraunhofer/aisec/cpg/graph/types/IncompleteType.kt} (59%) rename cpg-core/src/main/{java => kotlin}/de/fraunhofer/aisec/cpg/graph/types/IntegerType.kt (100%) rename cpg-core/src/main/{java => kotlin}/de/fraunhofer/aisec/cpg/graph/types/NumericType.kt (93%) create mode 100644 cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/ObjectType.kt rename cpg-core/src/main/{java/de/fraunhofer/aisec/cpg/graph/types/ParameterizedType.java => kotlin/de/fraunhofer/aisec/cpg/graph/types/ParameterizedType.kt} (62%) create mode 100644 cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/PointerType.kt create mode 100644 cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/ReferenceType.kt rename cpg-core/src/main/{java/de/fraunhofer/aisec/cpg/graph/types/SecondOrderType.java => kotlin/de/fraunhofer/aisec/cpg/graph/types/SecondOrderType.kt} (87%) rename cpg-core/src/main/{java => kotlin}/de/fraunhofer/aisec/cpg/graph/types/StringType.kt (100%) create mode 100644 cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/Type.kt create mode 100644 cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/UnknownType.kt create mode 100644 cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/WrapState.kt rename cpg-core/src/main/{java => kotlin}/de/fraunhofer/aisec/cpg/helpers/CommentMatcher.kt (100%) create mode 100644 cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/CommonPath.kt rename cpg-core/src/main/{java => kotlin}/de/fraunhofer/aisec/cpg/helpers/IdentitySet.kt (93%) rename cpg-core/src/main/{java => kotlin}/de/fraunhofer/aisec/cpg/helpers/MeasurementHolder.kt (100%) rename cpg-core/src/main/{java => kotlin}/de/fraunhofer/aisec/cpg/helpers/SubgraphWalker.kt (94%) rename cpg-core/src/main/{java/de/fraunhofer/aisec/cpg/helpers/ShutDownException.java => kotlin/de/fraunhofer/aisec/cpg/helpers/TriConsumer.kt} (89%) rename cpg-core/src/main/{java => kotlin}/de/fraunhofer/aisec/cpg/helpers/Util.kt (90%) rename cpg-core/src/main/{java => kotlin}/de/fraunhofer/aisec/cpg/helpers/annotations/FunctionReplacement.kt (100%) create mode 100644 cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/neo4j/LocationConverter.kt rename cpg-core/src/main/{java/de/fraunhofer/aisec/cpg/helpers => kotlin/de/fraunhofer/aisec/cpg/helpers/neo4j}/NameConverter.kt (98%) rename cpg-core/src/main/{java/de/fraunhofer/aisec/cpg/processing/IStrategy.java => kotlin/de/fraunhofer/aisec/cpg/processing/IStrategy.kt} (78%) rename cpg-core/src/main/{java/de/fraunhofer/aisec/cpg/processing/IVisitable.java => kotlin/de/fraunhofer/aisec/cpg/processing/IVisitable.kt} (65%) rename cpg-core/src/main/{java/de/fraunhofer/aisec/cpg/processing/IVisitor.java => kotlin/de/fraunhofer/aisec/cpg/processing/IVisitor.kt} (59%) create mode 100644 cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/processing/strategy/Strategy.kt create mode 100644 cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/sarif/PhysicalLocation.kt create mode 100644 cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/sarif/Region.kt diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluator.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluator.kt index fc935e6ead..c572840787 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluator.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluator.kt @@ -31,6 +31,7 @@ import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration import de.fraunhofer.aisec.cpg.graph.statements.DeclarationStatement import de.fraunhofer.aisec.cpg.graph.statements.ForStatement import de.fraunhofer.aisec.cpg.graph.statements.expressions.* +import de.fraunhofer.aisec.cpg.passes.EdgeCachePass import de.fraunhofer.aisec.cpg.passes.astParent import org.slf4j.Logger import org.slf4j.LoggerFactory @@ -249,7 +250,7 @@ class MultiValueEvaluator : ValueEvaluator() { as? ForStatement if (loop == null || loop.condition !is BinaryOperator) return setOf() - var loopVar: Number? = + var loopVar: Any? = evaluateInternal(loop.initializerStatement?.declarations?.first(), depth) as? Number ?: return setOf() diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGPass.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGPass.kt index a48e403760..99db6a551d 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGPass.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGPass.kt @@ -46,13 +46,13 @@ class UnreachableEOGPass : Pass() { tu.accept( Strategy::AST_FORWARD, object : IVisitor() { - override fun visit(n: Node) { - when (n) { - is IfStatement -> handleIfStatement(n) - is WhileStatement -> handleWhileStatement(n) + override fun visit(t: Node) { + when (t) { + is IfStatement -> handleIfStatement(t) + is WhileStatement -> handleWhileStatement(t) } - super.visit(n) + super.visit(t) } } ) diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/query/QueryTree.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/query/QueryTree.kt index cdc5b1a0cb..02d0dde2ec 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/query/QueryTree.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/query/QueryTree.kt @@ -123,7 +123,7 @@ open class QueryTree( return QueryTree(result, mutableListOf(this, other), "${this.value} is ${other.value}") } - /** Checks if the value is a member of the type of [oter]. */ + /** Checks if the value is a member of the type of [other]. */ infix fun IS(other: Class<*>): QueryTree { val result = other.isInstance(this.value) return QueryTree(result, mutableListOf(this, QueryTree(other)), "${this.value} is $other") @@ -278,7 +278,7 @@ fun not(arg: QueryTree): QueryTree { /** Negates the value of [arg] and returns the resulting [QueryTree]. */ fun not(arg: Boolean): QueryTree { val result = !arg - return QueryTree(result, mutableListOf(QueryTree(arg)), "! ${arg}") + return QueryTree(result, mutableListOf(QueryTree(arg)), "! $arg") } /** diff --git a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/query/QueryTest.kt b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/query/QueryTest.kt index 7b96cd1855..a035f655ed 100644 --- a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/query/QueryTest.kt +++ b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/query/QueryTest.kt @@ -466,15 +466,19 @@ class QueryTest { val result = analyzer.analyze().get() val queryTreeResult = - result.all(mustSatisfy = { (it.value.invoke() as QueryTree) < 5 }) + result.all( + mustSatisfy = { + it.assignments.all { (it.value.invoke() as QueryTree) < 5 } + } + ) assertTrue(queryTreeResult.first) - val queryTreeResult2 = - result.allExtended( - mustSatisfy = { it.value.invoke() as QueryTree lt 5 } + /*val queryTreeResult2 = + result.allExtended( + mustSatisfy = { it.assignments.all { it.value.invoke() as QueryTree lt 5 } } ) - assertTrue(queryTreeResult2.value) + assertTrue(queryTreeResult2.value)*/ } @Test diff --git a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/NullPointerCheck.kt b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/NullPointerCheck.kt index 836486b2ab..5753bbafe1 100644 --- a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/NullPointerCheck.kt +++ b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/NullPointerCheck.kt @@ -37,7 +37,7 @@ import de.fraunhofer.aisec.cpg.graph.statements.IfStatement import de.fraunhofer.aisec.cpg.graph.statements.expressions.* import de.fraunhofer.aisec.cpg.processing.IVisitor import de.fraunhofer.aisec.cpg.processing.strategy.Strategy -import de.fraunhofer.aisec.cpg.sarif.PhysicalLocation.locationLink +import de.fraunhofer.aisec.cpg.sarif.PhysicalLocation.Companion.locationLink import org.jline.utils.AttributedString import org.jline.utils.AttributedStringBuilder import org.jline.utils.AttributedStyle.* @@ -52,7 +52,7 @@ class NullPointerCheck { for (tu in result.translationUnits) { tu.accept( Strategy::AST_FORWARD, - object : IVisitor() { + object : IVisitor() { fun visit(v: MemberCallExpression) { handleHasBase(v) } diff --git a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/OutOfBoundsCheck.kt b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/OutOfBoundsCheck.kt index debfd6c994..9f73e97f16 100644 --- a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/OutOfBoundsCheck.kt +++ b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/OutOfBoundsCheck.kt @@ -52,7 +52,7 @@ class OutOfBoundsCheck { for (tu in result.translationUnits) { tu.accept( Strategy::AST_FORWARD, - object : IVisitor() { + object : IVisitor() { fun visit(v: ArraySubscriptionExpression) { val evaluator = ValueEvaluator() val resolvedIndex = evaluator.evaluate(v.subscriptExpression) diff --git a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/TypeManager.java b/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/TypeManager.java index 8b5e2fac85..fbc2e0e135 100644 --- a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/TypeManager.java +++ b/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/TypeManager.java @@ -319,8 +319,6 @@ public boolean containsParameterizedType(List generics) { public boolean stopPropagation(Type type, Type newType) { if (type instanceof ObjectType typeObjectType && newType instanceof ObjectType newObjectType - && typeObjectType.getGenerics() != null - && newObjectType.getGenerics() != null && type.getName().equals(newType.getName())) { return containsParameterizedType(newObjectType.getGenerics()) && !(containsParameterizedType(typeObjectType.getGenerics())); @@ -397,10 +395,10 @@ private Set unwrapTypes(Collection types, WrapState wrapState) { } } - wrapState.setDepth(depth); + wrapState.depth = depth; wrapState.setPointerOrigin(pointerOrigins); wrapState.setReference(reference); - wrapState.setReferenceType(referenceType); + wrapState.referenceType = referenceType; if (unwrappedTypes.isEmpty() && !original.isEmpty()) { return original; @@ -439,10 +437,10 @@ public Optional getCommonType(@NotNull Collection types, ScopeProvid } else if (types.size() == 1) { return rewrapType( types.iterator().next(), - wrapState.getDepth(), - wrapState.getPointerOrigins(), + wrapState.depth, + wrapState.pointerOrigins, wrapState.isReference(), - wrapState.getReferenceType()); + wrapState.referenceType); } var scope = provider.getScope(); @@ -528,10 +526,10 @@ public Optional getCommonType(@NotNull Collection types, ScopeProvid return rewrapType( finalType, - wrapState.getDepth(), - wrapState.getPointerOrigins(), + wrapState.depth, + wrapState.pointerOrigins, wrapState.isReference(), - wrapState.getReferenceType()); + wrapState.referenceType); } private Set getAncestors(RecordDeclaration recordDeclaration, int depth) { @@ -554,6 +552,8 @@ private Set getAncestors(RecordDeclaration recordDeclaration, int dept public boolean isSupertypeOf(Type superType, Type subType, MetadataProvider provider) { Language language = null; + if (superType instanceof UnknownType && subType instanceof UnknownType) return true; + if (superType.getReferenceDepth() != subType.getReferenceDepth()) { return false; } diff --git a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/package-info.java b/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/package-info.java deleted file mode 100644 index 17f7f37b72..0000000000 --- a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Structure of the Code Property Graph (CPG). */ -package de.fraunhofer.aisec.cpg.graph; diff --git a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/types/FunctionPointerType.java b/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/types/FunctionPointerType.java deleted file mode 100644 index 9dc6360c1c..0000000000 --- a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/types/FunctionPointerType.java +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright (c) 2021, 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.graph.types; - -import static de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.unwrap; - -import de.fraunhofer.aisec.cpg.frontends.Language; -import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend; -import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import org.jetbrains.annotations.NotNull; -import org.neo4j.ogm.annotation.Relationship; - -/** - * FunctionPointerType represents FunctionPointers in CPP containing a list of parameters and a - * return type. - */ -public class FunctionPointerType extends Type { - @Relationship(value = "PARAMETERS", direction = Relationship.Direction.OUTGOING) - private List> parameters; - - private Type returnType; - - public void setParameters(List parameters) { - this.parameters = PropertyEdge.transformIntoOutgoingPropertyEdgeList(parameters, this); - } - - public void setReturnType(Type returnType) { - this.returnType = returnType; - } - - private FunctionPointerType() {} - - public FunctionPointerType( - List parameters, Type returnType, Language language) { - super("", language); - this.parameters = PropertyEdge.transformIntoOutgoingPropertyEdgeList(parameters, this); - this.returnType = returnType; - } - - public FunctionPointerType( - Type type, - List parameters, - Type returnType, - Language language) { - super(type); - this.parameters = PropertyEdge.transformIntoOutgoingPropertyEdgeList(parameters, this); - this.returnType = returnType; - this.setLanguage(language); - } - - public List> getParametersPropertyEdge() { - return this.parameters; - } - - public List getParameters() { - return unwrap(this.parameters); - } - - public Type getReturnType() { - return returnType; - } - - @Override - public PointerType reference(PointerType.PointerOrigin pointerOrigin) { - return new PointerType(this, pointerOrigin); - } - - @Override - public Type dereference() { - return this; - } - - @Override - public Type duplicate() { - List copiedParameters = new ArrayList<>(unwrap(this.parameters)); - return new FunctionPointerType(this, copiedParameters, this.returnType, this.getLanguage()); - } - - @Override - public boolean isSimilar(Type t) { - if (t instanceof FunctionPointerType) { - if (returnType == null || ((FunctionPointerType) t).returnType == null) { - return this.parameters.equals(((FunctionPointerType) t).parameters) - && (this.returnType == ((FunctionPointerType) t).returnType - || returnType == ((FunctionPointerType) t).getReturnType()); - } - return this.parameters.equals(((FunctionPointerType) t).parameters) - && (this.returnType == ((FunctionPointerType) t).returnType - || this.returnType.equals(((FunctionPointerType) t).returnType)); - } - return false; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof FunctionPointerType that)) return false; - if (!super.equals(o)) return false; - return Objects.equals(this.getParameters(), that.getParameters()) - && PropertyEdge.propertyEqualsList(parameters, that.parameters) - && Objects.equals(returnType, that.returnType); - } - - @Override - public int hashCode() { - return Objects.hash(super.hashCode(), parameters, returnType); - } - - @NotNull - @Override - public String toString() { - return "FunctionPointerType{" - + "parameters=" - + getParameters() - + ", returnType=" - + returnType - + ", typeName='" - + getName() - + '\'' - + ", origin=" - + this.getTypeOrigin() - + '}'; - } -} diff --git a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/types/ObjectType.java b/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/types/ObjectType.java deleted file mode 100644 index 9ba4e1b0a2..0000000000 --- a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/types/ObjectType.java +++ /dev/null @@ -1,190 +0,0 @@ -/* - * Copyright (c) 2019, 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.graph.types; - -import de.fraunhofer.aisec.cpg.frontends.Language; -import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend; -import de.fraunhofer.aisec.cpg.graph.HasType; -import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration; -import de.fraunhofer.aisec.cpg.graph.edge.Properties; -import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge; -import java.util.*; -import org.jetbrains.annotations.NotNull; -import org.neo4j.ogm.annotation.Relationship; - -/** - * This is the main type in the Type system. ObjectTypes describe objects, as instances of a class. - * This also includes primitive data types. - */ -public class ObjectType extends Type implements HasType.SecondaryTypeEdge { - - @Override - public void updateType(@NotNull Collection typeState) { - if (this.generics == null) { - return; - } - for (Type t : this.getGenerics()) { - for (Type t2 : typeState) { - if (t2.equals(t)) { - this.replaceGenerics(t, t2); - } - } - } - } - - public void replaceGenerics(Type oldType, Type newType) { - if (this.generics == null) { - return; - } - for (int i = 0; i < this.generics.size(); i++) { - PropertyEdge propertyEdge = this.generics.get(i); - if (propertyEdge.getEnd().equals(oldType)) { - propertyEdge.setEnd(newType); - } - } - } - - // Reference from the ObjectType to its class (RecordDeclaration) only if the class is available - private RecordDeclaration recordDeclaration = null; - - @Relationship(value = "GENERICS", direction = Relationship.Direction.OUTGOING) - private List> generics; - - public ObjectType( - CharSequence typeName, - List generics, - boolean primitive, - Language language) { - super(typeName, language); - this.generics = PropertyEdge.transformIntoOutgoingPropertyEdgeList(generics, this); - this.primitive = primitive; - this.setLanguage(language); - } - - public ObjectType( - Type type, - List generics, - boolean primitive, - Language language) { - super(type); - this.setLanguage(language); - this.generics = PropertyEdge.transformIntoOutgoingPropertyEdgeList(generics, this); - this.primitive = primitive; - } - - /** Empty default constructor for use in Neo4J persistence. */ - public ObjectType() { - super(); - this.generics = new ArrayList<>(); - this.primitive = false; - } - - public List getGenerics() { - List genericValues = new ArrayList<>(); - for (PropertyEdge edge : this.generics) { - genericValues.add(edge.getEnd()); - } - return Collections.unmodifiableList(genericValues); - } - - public List> getGenericPropertyEdges() { - return this.generics; - } - - public RecordDeclaration getRecordDeclaration() { - return recordDeclaration; - } - - public void setRecordDeclaration(RecordDeclaration recordDeclaration) { - this.recordDeclaration = recordDeclaration; - } - - /** - * @return PointerType to a ObjectType, e.g. int* - */ - @Override - public PointerType reference(PointerType.PointerOrigin pointerOrigin) { - return new PointerType(this, pointerOrigin); - } - - public PointerType reference() { - return new PointerType(this, PointerType.PointerOrigin.POINTER); - } - - /** - * @return UnknownType, as we cannot infer any type information when dereferencing an ObjectType, - * as it is just some memory and its interpretation is unknown - */ - @Override - public Type dereference() { - return UnknownType.getUnknownType(getLanguage()); - } - - @Override - public Type duplicate() { - ObjectType newObject = - new ObjectType(this, this.getGenerics(), this.primitive, this.getLanguage()); - return newObject; - } - - public void setGenerics(List generics) { - this.generics = PropertyEdge.transformIntoOutgoingPropertyEdgeList(generics, this); - } - - public void addGeneric(Type generic) { - var propertyEdge = new PropertyEdge<>(this, generic); - propertyEdge.addProperty(Properties.INDEX, this.generics.size()); - this.generics.add(propertyEdge); - } - - public void addGenerics(List generics) { - for (Type generic : generics) { - addGeneric(generic); - } - } - - @Override - public boolean isSimilar(Type t) { - return t instanceof ObjectType that - && this.getGenerics().equals(that.getGenerics()) - && super.isSimilar(t); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof ObjectType that)) return false; - if (!super.equals(o)) return false; - return Objects.equals(this.getGenerics(), that.getGenerics()) - && PropertyEdge.propertyEqualsList(generics, that.generics) - && this.primitive == that.primitive; - } - - @Override - public int hashCode() { - return Objects.hash(super.hashCode(), generics, primitive); - } -} diff --git a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/types/PointerType.java b/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/types/PointerType.java deleted file mode 100644 index 457823f3b6..0000000000 --- a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/types/PointerType.java +++ /dev/null @@ -1,178 +0,0 @@ -/* - * Copyright (c) 2019, 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.graph.types; - -import de.fraunhofer.aisec.cpg.graph.Name; -import java.util.Objects; -import org.neo4j.ogm.annotation.Relationship; - -/** - * PointerTypes represent all references to other Types. For C/CPP this includes pointers, as well - * as arrays, since technically arrays are pointers. For JAVA the only use case are arrays as there - * is no such pointer concept. - */ -public class PointerType extends Type implements SecondOrderType { - - @Relationship(value = "ELEMENT_TYPE") - private Type elementType; - - public enum PointerOrigin { - POINTER, - ARRAY, - } - - private PointerOrigin pointerOrigin; - - private PointerType() {} - - public PointerType(Type elementType, PointerOrigin pointerOrigin) { - super(); - this.setLanguage(elementType.getLanguage()); - - if (pointerOrigin == PointerOrigin.ARRAY) { - this.setName(elementType.getName().append("[]")); - } else { - this.setName(elementType.getName().append("*")); - } - - this.pointerOrigin = pointerOrigin; - this.elementType = elementType; - } - - public PointerType(Type type, Type elementType, PointerOrigin pointerOrigin) { - super(type); - this.setLanguage(elementType.getLanguage()); - - if (pointerOrigin == PointerOrigin.ARRAY) { - this.setName(elementType.getName().append("[]")); - } else { - this.setName(elementType.getName().append("*")); - } - - this.pointerOrigin = pointerOrigin; - this.elementType = elementType; - } - - /** - * @return referencing a PointerType results in another PointerType wrapping the first - * PointerType, e.g. int** - */ - @Override - public PointerType reference(PointerOrigin origin) { - if (origin == null) { - origin = PointerOrigin.ARRAY; - } - - return new PointerType(this, origin); - } - - /** - * @return dereferencing a PointerType yields the type the pointer was pointing towards - */ - @Override - public Type dereference() { - return elementType; - } - - @Override - public void refreshNames() { - if (this.getElementType() instanceof PointerType) { - this.getElementType().refreshNames(); - } - - String localName = elementType.getName().getLocalName(); - if (pointerOrigin == PointerOrigin.ARRAY) { - localName += "[]"; - } else { - localName += "*"; - } - - var fullTypeName = - new Name( - localName, elementType.getName().getParent(), elementType.getName().getDelimiter()); - - this.setName(fullTypeName); - } - - @Override - public Type duplicate() { - return new PointerType(this, this.elementType.duplicate(), this.pointerOrigin); - } - - public boolean isArray() { - return this.pointerOrigin == PointerOrigin.ARRAY; - } - - @Override - public boolean isSimilar(Type t) { - if (!(t instanceof PointerType)) { - return false; - } - - PointerType pointerType = (PointerType) t; - - return this.getReferenceDepth() == pointerType.getReferenceDepth() - && this.getElementType().isSimilar(pointerType.getRoot()) - && super.isSimilar(t); - } - - public PointerOrigin getPointerOrigin() { - return pointerOrigin; - } - - public Type getElementType() { - return elementType; - } - - @Override - public int getReferenceDepth() { - int depth = 1; - Type containedType = this.elementType; - while (containedType instanceof PointerType) { - depth++; - containedType = ((PointerType) containedType).getElementType(); - } - return depth; - } - - public void setElementType(Type elementType) { - this.elementType = elementType; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof PointerType that)) return false; - if (!super.equals(o)) return false; - return Objects.equals(elementType, that.elementType) - && Objects.equals(pointerOrigin, that.pointerOrigin); - } - - @Override - public int hashCode() { - return Objects.hash(super.hashCode(), elementType, pointerOrigin); - } -} diff --git a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/types/ReferenceType.java b/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/types/ReferenceType.java deleted file mode 100644 index 81dc6e631c..0000000000 --- a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/types/ReferenceType.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright (c) 2019, 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.graph.types; - -import java.util.Objects; -import org.jetbrains.annotations.NotNull; - -/** - * ReferenceTypes describe CPP References (int&), which represent an alternative name for a - * variable. It is necessary to make this distinction, and not just rely on the original type as it - * is required for matching parameters in function arguments to discover which implementation is - * called. - */ -public class ReferenceType extends Type implements SecondOrderType { - - private Type reference; - - private ReferenceType() {} - - public ReferenceType(Type reference) { - super(); - this.setLanguage(reference.getLanguage()); - this.setName(reference.getName().append("&")); - this.reference = reference; - } - - public ReferenceType(Type type, Type reference) { - super(type); - this.setLanguage(reference.getLanguage()); - this.setName(reference.getName().append("&")); - this.reference = reference; - } - - /** - * @return Referencing a ReferenceType results in a PointerType to the original ReferenceType - */ - @Override - public Type reference(PointerType.PointerOrigin pointerOrigin) { - return new PointerType(this, pointerOrigin); - } - - /** - * @return Dereferencing a ReferenceType equals to dereferencing the original (non-reference) type - */ - @Override - public Type dereference() { - return reference.dereference(); - } - - @Override - public Type duplicate() { - return new ReferenceType(this, this.reference); - } - - public Type getElementType() { - return reference; - } - - public void setElementType(Type reference) { - this.reference = reference; - } - - @Override - public boolean isSimilar(Type t) { - return t instanceof ReferenceType referenceType - && referenceType.getElementType().equals(this) - && super.isSimilar(t); - } - - public void refreshName() { - this.setName(reference.getName().append("&")); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof ReferenceType that)) return false; - if (!super.equals(o)) return false; - return Objects.equals(reference, that.reference); - } - - @Override - public int hashCode() { - return Objects.hash(super.hashCode(), reference); - } - - @Override - public @NotNull String toString() { - return "ReferenceType{" - + "reference=" - + reference - + ", typeName='" - + this.getName() - + '\'' - + ", origin=" - + this.getTypeOrigin() - + '}'; - } -} diff --git a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/types/Type.java b/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/types/Type.java deleted file mode 100644 index 84ff0ee65c..0000000000 --- a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/types/Type.java +++ /dev/null @@ -1,213 +0,0 @@ -/* - * Copyright (c) 2019, 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.graph.types; - -import de.fraunhofer.aisec.cpg.frontends.Language; -import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend; -import de.fraunhofer.aisec.cpg.graph.Name; -import de.fraunhofer.aisec.cpg.graph.NameKt; -import de.fraunhofer.aisec.cpg.graph.Node; -import java.util.*; -import org.apache.commons.lang3.builder.ToStringBuilder; -import org.jetbrains.annotations.NotNull; -import org.neo4j.ogm.annotation.Relationship; - -/** - * Abstract Type, describing all possible SubTypes, i.e. all different Subtypes are compliant with - * this class. Contains information which is included in any Type such as name, storage, qualifier - * and origin - */ -public abstract class Type extends Node { - public static final String UNKNOWN_TYPE_STRING = "UNKNOWN"; - - @Relationship(value = "SUPER_TYPE", direction = Relationship.Direction.OUTGOING) - @NotNull - protected Set superTypes = new HashSet<>(); - - protected boolean primitive = false; - - protected Origin origin; - - public Type() { - this.setName(new Name(EMPTY_NAME, null, this.getLanguage())); - } - - public Type(String typeName) { - this.setName(NameKt.parseName(this.getLanguage(), typeName)); - this.origin = Origin.UNRESOLVED; - } - - public Type(Type type) { - this.setName(type.getName().clone()); - this.origin = type.origin; - } - - public Type(CharSequence typeName, Language language) { - if (this instanceof FunctionType) { - this.setName(new Name(typeName.toString(), null, language)); - } else { - this.setName(NameKt.parseName(language, typeName)); - } - this.setLanguage(language); - this.origin = Origin.UNRESOLVED; - } - - public Type(Name fullTypeName, Language language) { - this.setName(fullTypeName.clone()); - this.origin = Origin.UNRESOLVED; - this.setLanguage(language); - } - - /** All direct supertypes of this type. */ - @NotNull - public Set getSuperTypes() { - return superTypes; - } - - public Origin getTypeOrigin() { - return origin; - } - - public void setTypeOrigin(Origin origin) { - this.origin = origin; - } - - public boolean isPrimitive() { - return primitive; - } - - /** Type Origin describes where the Type information came from */ - public enum Origin { - RESOLVED, - DATAFLOW, - GUESSED, - UNRESOLVED - } - - /** - * @param pointer Reason for the reference (array of pointer) - * @return Returns a reference to the current Type. E.g. when creating a pointer to an existing - * ObjectType - */ - public abstract Type reference(PointerType.PointerOrigin pointer); - - /** - * @return Dereferences the current Type by resolving the reference. E.g. when dereferencing a - * pointer type we obtain the type the pointer is pointing towards - */ - public abstract Type dereference(); - - public void refreshNames() {} - - /** - * Obtain the root Type Element for a Type Chain (follows Pointer and ReferenceTypes until a - * Object-, Incomplete-, or FunctionPtrType is reached). - * - * @return root Type - */ - public Type getRoot() { - if (this instanceof SecondOrderType) { - return ((SecondOrderType) this).getElementType().getRoot(); - } else { - return this; - } - } - - public void setRoot(Type newRoot) { - if (this instanceof SecondOrderType) { - if (((SecondOrderType) this).getElementType() instanceof SecondOrderType) { - ((SecondOrderType) ((SecondOrderType) this).getElementType()).setElementType(newRoot); - } else { - ((SecondOrderType) this).setElementType(newRoot); - } - } - } - - /** - * @return Creates an exact copy of the current type (chain) - */ - public abstract Type duplicate(); - - public String getTypeName() { - return getName().toString(); - } - - /** - * @return number of steps that are required in order to traverse the type chain until the root is - * reached - */ - public int getReferenceDepth() { - return 0; - } - - /** - * @return True if the Type parameter t is a FirstOrderType (Root of a chain) and not a Pointer or - * ReferenceType - */ - public boolean isFirstOrderType() { - return this instanceof ObjectType - || this instanceof UnknownType - || this instanceof FunctionType - || this instanceof TupleType - // TODO(oxisto): convert FunctionPointerType to second order type - || this instanceof FunctionPointerType - || this instanceof IncompleteType - || this instanceof ParameterizedType; - } - - /** - * Required for possibleSubTypes to check if the new Type should be considered a subtype or not - * - * @param t other type the similarity is checked with - * @return True if the parameter t is equal to the current type (this) - */ - public boolean isSimilar(Type t) { - if (this.equals(t)) { - return true; - } - - return this.getRoot().getName().equals(t.getRoot().getName()); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof Type type)) return false; - return Objects.equals(getName(), type.getName()) - && Objects.equals(getLanguage(), type.getLanguage()); - } - - @Override - public int hashCode() { - return Objects.hash(getName(), getLanguage()); - } - - @NotNull - @Override - public String toString() { - return new ToStringBuilder(this, TO_STRING_STYLE).append("name", getName()).toString(); - } -} diff --git a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/types/TypeParser.java b/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/types/TypeParser.java index a4ee10e4d3..1485886d46 100644 --- a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/types/TypeParser.java +++ b/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/types/TypeParser.java @@ -609,7 +609,7 @@ private static Type createFromUnsafe( Type returnType = createFrom(typeName, language); List parameterList = getParameterList(funcptr.group("args"), language); - return typeManager.registerType(new FunctionPointerType(parameterList, returnType, language)); + return typeManager.registerType(new FunctionPointerType(parameterList, language, returnType)); } else if (isIncompleteType(typeName)) { // IncompleteType e.g. void finalType = new IncompleteType(); diff --git a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/types/UnknownType.java b/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/types/UnknownType.java deleted file mode 100644 index 39e24ca075..0000000000 --- a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/types/UnknownType.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright (c) 2019, 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.graph.types; - -import de.fraunhofer.aisec.cpg.frontends.Language; -import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend; -import de.fraunhofer.aisec.cpg.graph.Name; -import java.util.Objects; -import org.jetbrains.annotations.NotNull; - -/** - * UnknownType describe the case in which it is not possible for the CPG to determine which Type is - * used. E.g.: This occurs when the type is inferred by the compiler automatically when using - * keywords such as auto in cpp - */ -public class UnknownType extends Type { - - // Only one instance of UnknownType for better representation in the graph - private static final UnknownType unknownType = new UnknownType(); - - private UnknownType() { - super(); - this.setName(new Name("UNKNOWN", null, this.getLanguage())); - } - - /** - * Use this function to obtain an UnknownType or call the TypeParser with the typeString UNKNOWN - * - * @return UnknownType instance - */ - @NotNull - public static UnknownType getUnknownType(Language language) { - unknownType.setLanguage(language); - return unknownType; - } - - @NotNull - public static UnknownType getUnknownType() { - // TODO: This is just a temporary solution. - return unknownType; - } - - /** - * This is only intended to be used by {@link TypeParser} for edge cases like distinct unknown - * types, such as "UNKNOWN1", thus the package-private visibility. Other users should see {@link - * #getUnknownType()} instead - * - * @param typeName The name of this unknown type, usually a variation of UNKNOWN - */ - UnknownType(String typeName) { - super(typeName); - } - - /** - * @return Same UnknownType, as it is makes no sense to obtain a pointer/reference to an - * UnknownType - */ - @Override - public Type reference(PointerType.PointerOrigin pointerOrigin) { - return this; - } - - /** - * @return Same UnknownType, - */ - @Override - public Type dereference() { - return this; - } - - @Override - public Type duplicate() { - return unknownType; - } - - @Override - public int hashCode() { - - return Objects.hash(super.hashCode()); - } - - @Override - public boolean equals(Object o) { - return o instanceof UnknownType; - } - - @Override - public String toString() { - return "UNKNOWN"; - } - - @Override - public void setTypeOrigin(Origin origin) { - // Only one instance of UnknownType, use default values - } -} diff --git a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/types/WrapState.java b/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/types/WrapState.java deleted file mode 100644 index 90bfa47803..0000000000 --- a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/types/WrapState.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (c) 2021, 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.graph.types; - -/** Stores State for rewrap when typeinformation has been unwrapped */ -public class WrapState { - - int depth; - boolean reference; - PointerType.PointerOrigin[] pointerOrigins; - ReferenceType referenceType; - - public WrapState() { - this.depth = 0; - this.reference = false; - this.pointerOrigins = new PointerType.PointerOrigin[] {PointerType.PointerOrigin.ARRAY}; - this.referenceType = null; - } - - public int getDepth() { - return depth; - } - - public void setDepth(int depth) { - this.depth = depth; - } - - public boolean isReference() { - return reference; - } - - public void setReference(boolean reference) { - this.reference = reference; - } - - public PointerType.PointerOrigin[] getPointerOrigins() { - return pointerOrigins; - } - - public void setPointerOrigin(PointerType.PointerOrigin[] pointerOrigin) { - this.pointerOrigins = pointerOrigin; - } - - public ReferenceType getReferenceType() { - return referenceType; - } - - public void setReferenceType(ReferenceType referenceType) { - this.referenceType = referenceType; - } -} diff --git a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/helpers/CommonPath.java b/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/helpers/CommonPath.java deleted file mode 100644 index 8579144c57..0000000000 --- a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/helpers/CommonPath.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (c) 2019, 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.helpers; - -import java.io.File; -import java.util.Collection; -import java.util.Comparator; -import java.util.List; -import java.util.regex.Pattern; -import java.util.stream.Collectors; -import org.jetbrains.annotations.Nullable; - -/** Find the common root path for a list of files */ -public class CommonPath { - - // hide ctor - private CommonPath() {} - - @Nullable - public static File commonPath(Collection paths) { - if (paths.isEmpty()) { - return null; - } - StringBuilder longestPrefix = new StringBuilder(); - List splittedPaths = - paths.stream() - .map(File::getAbsolutePath) - .map(p -> p.split(Pattern.quote(File.separator))) - .sorted(Comparator.comparingInt(s -> s.length)) - .collect(Collectors.toList()); - - String[] shortest = splittedPaths.get(0); - for (int i = 0; i < shortest.length; i++) { - String part = shortest[i]; - int position = i; - if (splittedPaths.stream().allMatch(p -> p[position].equals(part))) { - longestPrefix.append(part).append(File.separator); - } else { - break; - } - } - - File result = new File(longestPrefix.toString()); - if (result.exists()) { - return getNearestDirectory(result); - } else return null; - } - - private static File getNearestDirectory(File file) { - if (file.isDirectory()) { - return file; - } else { - return getNearestDirectory(file.getParentFile()); - } - } -} diff --git a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/helpers/LocationConverter.java b/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/helpers/LocationConverter.java deleted file mode 100644 index 9a5488cd67..0000000000 --- a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/helpers/LocationConverter.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (c) 2019, 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.helpers; - -import static java.lang.Math.toIntExact; - -import de.fraunhofer.aisec.cpg.sarif.PhysicalLocation; -import de.fraunhofer.aisec.cpg.sarif.Region; -import java.net.URI; -import java.util.HashMap; -import java.util.Map; -import org.neo4j.ogm.typeconversion.CompositeAttributeConverter; - -public class LocationConverter implements CompositeAttributeConverter { - - public static final String START_LINE = "startLine"; - public static final String END_LINE = "endLine"; - public static final String START_COLUMN = "startColumn"; - public static final String END_COLUMN = "endColumn"; - public static final String ARTIFACT = "artifact"; - - @Override - public Map toGraphProperties(PhysicalLocation value) { - Map properties = new HashMap<>(); - if (value != null) { - properties.put(ARTIFACT, value.getArtifactLocation().getUri().toString()); - properties.put(START_LINE, value.getRegion().getStartLine()); - properties.put(END_LINE, value.getRegion().getEndLine()); - properties.put(START_COLUMN, value.getRegion().getStartColumn()); - properties.put(END_COLUMN, value.getRegion().getEndColumn()); - } - return properties; - } - - @Override - public PhysicalLocation toEntityAttribute(Map value) { - try { - final int startLine = toInt(value.get(START_LINE)); - final int endLine = toInt(value.get(END_LINE)); - final int startColumn = toInt(value.get(START_COLUMN)); - final int endColumn = toInt(value.get(END_COLUMN)); - final URI uri = URI.create((String) value.get(ARTIFACT)); - - return new PhysicalLocation(uri, new Region(startLine, startColumn, endLine, endColumn)); - } catch (NullPointerException e) { - return null; - } - } - - private int toInt(final Object objectToMap) { - final long value = Long.parseLong(objectToMap.toString()); - return toIntExact(value); - } -} diff --git a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/helpers/package-info.java b/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/helpers/package-info.java deleted file mode 100644 index 02ba7f1ca5..0000000000 --- a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/helpers/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Internal helper classes. */ -package de.fraunhofer.aisec.cpg.helpers; diff --git a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/processing/strategy/Strategy.java b/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/processing/strategy/Strategy.java deleted file mode 100644 index f3df1d613a..0000000000 --- a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/processing/strategy/Strategy.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright (c) 2021, 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.processing.strategy; - -import de.fraunhofer.aisec.cpg.graph.Node; -import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker; -import java.util.*; -import org.jetbrains.annotations.NotNull; - -/** Strategies (iterators) for traversing graphs to be used by visitors. */ -public class Strategy { - - private Strategy() { - // Do not call. - } - - /** - * Do not traverse any nodes. - * - * @param x - * @return - */ - @NotNull - public static Iterator NO_STRATEGY(@NotNull Node x) { - return Collections.emptyIterator(); - } - - /** - * Traverse Evaluation Order Graph in forward direction. - * - * @param x Current node in EOG. - * @return Iterator over successors. - */ - @NotNull - public static Iterator EOG_FORWARD(@NotNull Node x) { - return x.getNextEOG().iterator(); - } - - /** - * Traverse Evaluation Order Graph in backward direction. - * - * @param x Current node in EOG. - * @return Iterator over successors. - */ - @NotNull - public static Iterator EOG_BACKWARD(@NotNull Node x) { - return x.getPrevEOG().iterator(); - } - - /** - * Traverse Data Flow Graph in forward direction. - * - * @param x Current node in DFG. - * @return Iterator over successors. - */ - @NotNull - public static Iterator DFG_FORWARD(@NotNull Node x) { - return x.getNextDFG().iterator(); - } - - /** - * Traverse Data Flow Graph in backward direction. - * - * @param x Current node in DFG. - * @return Iterator over successors. - */ - @NotNull - public static Iterator DFG_BACKWARD(@NotNull Node x) { - return x.getPrevDFG().iterator(); - } - - /** - * Traverse AST in forward direction. - * - * @param x - * @return - */ - @NotNull - public static Iterator AST_FORWARD(@NotNull Node x) { - return SubgraphWalker.getAstChildren(x).iterator(); - } -} diff --git a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/sarif/PhysicalLocation.java b/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/sarif/PhysicalLocation.java deleted file mode 100644 index 226e028f87..0000000000 --- a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/sarif/PhysicalLocation.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright (c) 2019, 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.sarif; - -import java.net.URI; -import java.util.Objects; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -/** A SARIF compatible location referring to a location, i.e. file and region within the file. */ -public class PhysicalLocation { - - @NotNull - public static String locationLink(@Nullable PhysicalLocation location) { - if (location != null) { - return location.getArtifactLocation().getUri().getPath() - + ":" - + location.getRegion().getStartLine() - + ":" - + location.getRegion().getStartColumn(); - } - - return "unknown"; - } - - public static class ArtifactLocation { - - @NotNull private final URI uri; - - public ArtifactLocation(@NotNull URI uri) { - this.uri = uri; - } - - @NotNull - public URI getUri() { - return this.uri; - } - - @Override - public String toString() { - return uri.getPath().substring(uri.getPath().lastIndexOf('/') + 1); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof ArtifactLocation)) return false; - ArtifactLocation that = (ArtifactLocation) o; - return Objects.equals(uri, that.uri); - } - - @Override - public int hashCode() { - return Objects.hashCode(uri); - } - } - - @NotNull private final ArtifactLocation artifactLocation; - - @NotNull private Region region; - - public PhysicalLocation(URI uri, @NotNull Region region) { - this.artifactLocation = new ArtifactLocation(uri); - this.region = region; - } - - public void setRegion(@NotNull Region region) { - this.region = region; - } - - @NotNull - public Region getRegion() { - return this.region; - } - - @NotNull - public ArtifactLocation getArtifactLocation() { - return this.artifactLocation; - } - - @Override - public String toString() { - return artifactLocation + "(" + region + ")"; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof PhysicalLocation)) return false; - PhysicalLocation that = (PhysicalLocation) o; - return Objects.equals(artifactLocation, that.artifactLocation) - && Objects.equals(region, that.region); - } - - @Override - public int hashCode() { - return Objects.hash(artifactLocation, region); - } -} diff --git a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/sarif/Region.java b/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/sarif/Region.java deleted file mode 100644 index 3acf4da542..0000000000 --- a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/sarif/Region.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright (c) 2019, 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.sarif; - -import java.util.Objects; -import org.jetbrains.annotations.NotNull; - -/** Code source location, in a SASP/SARIF-compliant "Region" format. */ -public class Region implements Comparable { - - static final Region UNKNOWN_REGION = new Region(); - private int startLine; - private int startColumn; - private int endLine; - private int endColumn; - - public Region(int startLine, int startColumn, int endLine, int endColumn) { - this.startLine = startLine; - this.startColumn = startColumn; - this.endLine = endLine; - this.endColumn = endColumn; - } - - public Region() { - this(-1, -1, -1, -1); - } - - public int getStartLine() { - return this.startLine; - } - - public void setStartLine(int startLine) { - this.startLine = startLine; - } - - public int getStartColumn() { - return startColumn; - } - - public void setStartColumn(int startColumn) { - this.startColumn = startColumn; - } - - public int getEndLine() { - return endLine; - } - - public void setEndLine(int endLine) { - this.endLine = endLine; - } - - public int getEndColumn() { - return endColumn; - } - - public void setEndColumn(int endColumn) { - this.endColumn = endColumn; - } - - @Override - public String toString() { - String sb = startLine + ":" + startColumn + "-" + endLine + ":" + endColumn; - return sb; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (!(obj instanceof Region)) { - return false; - } - Region that = (Region) obj; - return (this.startLine == that.startLine - && this.startColumn == that.startColumn - && this.endLine == that.endLine - && this.endColumn == that.endColumn); - } - - @Override - public int compareTo(@NotNull Region region) { - int comparisonValue; - if ((comparisonValue = Integer.compare(this.getStartLine(), region.getStartLine())) != 0) - return comparisonValue; - if ((comparisonValue = Integer.compare(this.getStartColumn(), region.getStartColumn())) != 0) - return comparisonValue; - - if ((comparisonValue = Integer.compare(this.getEndLine(), region.getEndLine())) != 0) - return -comparisonValue; - if ((comparisonValue = Integer.compare(this.getEndColumn(), region.getEndColumn())) != 0) - return -comparisonValue; - - return comparisonValue; - } - - @Override - public int hashCode() { - return Objects.hash(this.startColumn, this.startLine, this.endColumn, this.endLine); - } -} diff --git a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/sarif/package-info.java b/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/sarif/package-info.java deleted file mode 100644 index 6a79d17f10..0000000000 --- a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/sarif/package-info.java +++ /dev/null @@ -1,8 +0,0 @@ -/** - * Data structures for SARIF compatible results. - * - *

SARIF is an OASIS standard - * for the specification of a unified result of static analysis tools. - */ -package de.fraunhofer.aisec.cpg.sarif; 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 cb8991f74b..95d50afc49 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 @@ -764,17 +764,17 @@ class ScopeManager : ScopeProvider { val num = AtomicInteger() val typeCache = TypeManager.getInstance().typeCache node.accept( - { Strategy.AST_FORWARD(it) }, - object : IVisitor() { - override fun visit(n: Node) { - if (n is HasType) { - val typeNode = n as HasType - typeCache.getOrDefault(typeNode, emptyList()).forEach { t: Type? -> - (n as HasType).type = + Strategy::AST_FORWARD, + object : IVisitor() { + override fun visit(t: Node) { + if (t is HasType) { + val typeNode = t as HasType + typeCache.getOrDefault(typeNode, emptyList()).forEach { it: Type? -> + (t as HasType).type = TypeManager.getInstance() - .resolvePossibleTypedef(t, this@ScopeManager) + .resolvePossibleTypedef(it, this@ScopeManager) } - typeCache.remove(n as HasType) + typeCache.remove(t as HasType) num.getAndIncrement() } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/Language.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/Language.kt index db8983b943..94cc0abd87 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/Language.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/Language.kt @@ -33,10 +33,10 @@ import com.fasterxml.jackson.databind.ser.std.StdSerializer import de.fraunhofer.aisec.cpg.ScopeManager import de.fraunhofer.aisec.cpg.TranslationConfiguration import de.fraunhofer.aisec.cpg.graph.Node +import de.fraunhofer.aisec.cpg.graph.newUnknownType import de.fraunhofer.aisec.cpg.graph.statements.expressions.BinaryOperator import de.fraunhofer.aisec.cpg.graph.types.* import de.fraunhofer.aisec.cpg.graph.types.Type -import de.fraunhofer.aisec.cpg.graph.types.UnknownType import java.io.File import kotlin.reflect.KClass @@ -131,7 +131,7 @@ abstract class Language : Node() { rhs } } else { - UnknownType.getUnknownType(this) + newUnknownType() } } @@ -144,7 +144,7 @@ abstract class Language : Node() { // A comparison, so we return the type "boolean" return this.builtInTypes.values.firstOrNull { it is BooleanType } ?: this.builtInTypes.values.firstOrNull { it.name.localName.startsWith("bool") } - ?: UnknownType.getUnknownType(this) + ?: newUnknownType() } return when (operation.operatorCode) { @@ -177,9 +177,9 @@ abstract class Language : Node() { // primitive type 1 OP primitive type 2 => primitive type 1 operation.lhs.propagationType } else { - UnknownType.getUnknownType(this) + newUnknownType() } - else -> UnknownType.getUnknownType(this) // We don't know what is this thing + else -> newUnknownType() // We don't know what is this thing } } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CXXHandler.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CXXHandler.kt index ca74e87681..e6373d8e0a 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CXXHandler.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CXXHandler.kt @@ -57,14 +57,12 @@ abstract class CXXHandler( // The language frontend might set a location, which we should respect. Otherwise, we will // set the location here. - if (node != null && node.location == null) { + if (node.location == null) { frontend.setCodeAndLocation(node, ctx) } frontend.setComment(node, ctx) - if (node != null) { - frontend.process(ctx, node) - } + frontend.process(ctx, node) return node } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CXXLanguageFrontend.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CXXLanguageFrontend.kt index 4779342f98..c6d613849d 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CXXLanguageFrontend.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CXXLanguageFrontend.kt @@ -396,7 +396,7 @@ class CXXLanguageFrontend( val expression: Expression = when (token.tokenType) { 1 -> // a variable - newDeclaredReferenceExpression(code, UnknownType.getUnknownType(language), code) + newDeclaredReferenceExpression(code, newUnknownType(), code) 2 -> // an integer newLiteral( code.toInt(), @@ -552,7 +552,7 @@ class CXXLanguageFrontend( TypeParser.createFrom(name, true, this) } else -> { - UnknownType.getUnknownType(language) + newUnknownType() } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/DeclarationHandler.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/DeclarationHandler.kt index 8bf31bbf19..8fb469235d 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/DeclarationHandler.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/DeclarationHandler.kt @@ -133,7 +133,7 @@ class DeclarationHandler(lang: CXXLanguageFrontend) : // Retrieve the type. This should parse as a function type, otherwise it is unknown. val type = frontend.typeOf(ctx.declarator, ctx.declSpecifier, declaration) as? FunctionType - declaration.type = type ?: UnknownType.getUnknownType(language) + declaration.type = type ?: newUnknownType() declaration.isDefinition = true // We also need to set the return type, based on the function type. @@ -403,7 +403,7 @@ class DeclarationHandler(lang: CXXLanguageFrontend) : * @param parameterizedTypes */ private fun addParameterizedTypesToType( - type: Type, + type: Type?, parameterizedTypes: List ) { if (type is ObjectType) { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/DeclaratorHandler.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/DeclaratorHandler.kt index 779852c909..dd1162f62d 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/DeclaratorHandler.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/DeclaratorHandler.kt @@ -118,7 +118,7 @@ class DeclaratorHandler(lang: CXXLanguageFrontend) : val declaration = newVariableDeclaration( ctx.name.toString(), - UnknownType.getUnknownType(language), // Type will be filled out later by + newUnknownType(), // Type will be filled out later by // handleSimpleDeclaration ctx.rawSignature, implicitInitializerAllowed, @@ -152,7 +152,7 @@ class DeclaratorHandler(lang: CXXLanguageFrontend) : val fieldName = rr[rr.size - 1] newFieldDeclaration( fieldName, - UnknownType.getUnknownType(language), + newUnknownType(), emptyList(), ctx.rawSignature, frontend.getLocationFromRawNode(ctx), @@ -162,7 +162,7 @@ class DeclaratorHandler(lang: CXXLanguageFrontend) : } else { newFieldDeclaration( name, - UnknownType.getUnknownType(language), + newUnknownType(), emptyList(), ctx.rawSignature, frontend.getLocationFromRawNode(ctx), @@ -273,9 +273,9 @@ class DeclaratorHandler(lang: CXXLanguageFrontend) : // (probably the global scope), but associate it to the named scope. if (parentScope != null && outsideOfScope) { // Bypass the scope manager and manually add it to the AST parent - val parent = frontend.scopeManager.currentScope?.astNode - if (parent != null && parent is DeclarationHolder) { - parent.addDeclaration(declaration) + val scopeParent = frontend.scopeManager.currentScope?.astNode + if (scopeParent != null && scopeParent is DeclarationHolder) { + scopeParent.addDeclaration(declaration) } // Enter the record scope @@ -345,13 +345,7 @@ class DeclaratorHandler(lang: CXXLanguageFrontend) : // is appended to the original ones. For coherent graph behaviour, we introduce an implicit // declaration that wraps this list if (ctx.takesVarArgs()) { - val varargs = - newParamVariableDeclaration( - "va_args", - UnknownType.getUnknownType(language), - true, - "" - ) + val varargs = newParamVariableDeclaration("va_args", newUnknownType(), true, "") varargs.isImplicit = true varargs.argumentIndex = i frontend.scopeManager.addDeclaration(varargs) @@ -395,11 +389,8 @@ class DeclaratorHandler(lang: CXXLanguageFrontend) : // Create a pointer to the class type (if we know it) val type = - if (recordDeclaration != null) { - recordDeclaration.toType().reference(PointerType.PointerOrigin.POINTER) - } else { - UnknownType.getUnknownType(language) - } + recordDeclaration?.toType()?.reference(PointerType.PointerOrigin.POINTER) + ?: newUnknownType() // Create the receiver. implicitInitializerAllowed must be false, otherwise fixInitializers // will create another implicit constructexpression for this variable, and we don't want @@ -426,13 +417,7 @@ class DeclaratorHandler(lang: CXXLanguageFrontend) : val recordDeclaration = frontend.scopeManager.currentRecord if (recordDeclaration == null) { // variable - result = - newVariableDeclaration( - name, - UnknownType.getUnknownType(language), - ctx.rawSignature, - true - ) + result = newVariableDeclaration(name, newUnknownType(), ctx.rawSignature, true) result.initializer = initializer } else { // field @@ -446,7 +431,7 @@ class DeclaratorHandler(lang: CXXLanguageFrontend) : result = newFieldDeclaration( fieldName, - UnknownType.getUnknownType(language), + newUnknownType(), emptyList(), code, frontend.getLocationFromRawNode(ctx), diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/ExpressionHandler.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/ExpressionHandler.kt index b8976ecf4e..b9716032be 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/ExpressionHandler.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/ExpressionHandler.kt @@ -141,7 +141,7 @@ class ExpressionHandler(lang: CXXLanguageFrontend) : // there are a lot of other constants defined for type traits, but they are not really // parsed as type id expressions var operatorCode = "" - var type: Type = UnknownType.getUnknownType(language) + var type: Type = newUnknownType() when (ctx.operator) { IASTTypeIdExpression.op_sizeof -> { operatorCode = "sizeof" @@ -326,7 +326,7 @@ class ExpressionHandler(lang: CXXLanguageFrontend) : return newMemberExpression( name, base, - UnknownType.getUnknownType(language), + newUnknownType(), if (ctx.isPointerDereference) "->" else ".", ctx.rawSignature ) @@ -395,7 +395,7 @@ class ExpressionHandler(lang: CXXLanguageFrontend) : val reference = handle(ctx.functionNameExpression) val callExpression: CallExpression if (reference is MemberExpression) { - val baseType: Type = reference.base.type.root + val baseType = reference.base.type.root assert(baseType !is SecondOrderType) callExpression = newMemberCallExpression(reference, code = ctx.rawSignature) if ( @@ -464,7 +464,7 @@ class ExpressionHandler(lang: CXXLanguageFrontend) : // TODO: handle this? convert the declared reference expression into a member expression? return newDeclaredReferenceExpression( ctx.name.toString(), - UnknownType.getUnknownType(language), + newUnknownType(), ctx.rawSignature ) } @@ -547,7 +547,7 @@ class ExpressionHandler(lang: CXXLanguageFrontend) : lk_true -> newLiteral(true, parseType("bool"), ctx.rawSignature) lk_false -> newLiteral(false, parseType("bool"), ctx.rawSignature) lk_nullptr -> newLiteral(null, parseType("nullptr_t"), ctx.rawSignature) - else -> newLiteral(String(ctx.value), UnknownType.getUnknownType(), ctx.rawSignature) + else -> newLiteral(String(ctx.value), newUnknownType(), ctx.rawSignature) } } @@ -584,7 +584,7 @@ class ExpressionHandler(lang: CXXLanguageFrontend) : oneLhs = newDeclaredReferenceExpression( des.name.toString(), - UnknownType.getUnknownType(language), + newUnknownType(), des.getRawSignature() ) } @@ -637,7 +637,7 @@ class ExpressionHandler(lang: CXXLanguageFrontend) : oneLhs = newDeclaredReferenceExpression( des.name.toString(), - UnknownType.getUnknownType(language), + newUnknownType(), des.getRawSignature() ) } @@ -790,8 +790,7 @@ class ExpressionHandler(lang: CXXLanguageFrontend) : private fun handleThisLiteral(ctx: IASTLiteralExpression): DeclaredReferenceExpression { // We should be in a record here. However since we are a fuzzy parser, maybe things went // wrong, so we might have an unknown type. - val recordType = - frontend.scopeManager.currentRecord?.toType() ?: UnknownType.getUnknownType() + val recordType = frontend.scopeManager.currentRecord?.toType() ?: newUnknownType() // We do want to make sure that the type of the expression is at least a pointer. val pointerType = recordType.reference(PointerOrigin.POINTER) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/AST.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/AST.kt index 17ecdbbb8a..238a1535f3 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/AST.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/AST.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, Fraunhofer AISEC. All rights reserved. + * Copyright (c) 2023, 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. diff --git a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/ArgumentHolder.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/ArgumentHolder.kt similarity index 100% rename from cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/ArgumentHolder.kt rename to cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/ArgumentHolder.kt diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/DeclarationBuilder.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/DeclarationBuilder.kt index 6fc62696c6..d4b001907e 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/DeclarationBuilder.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/DeclarationBuilder.kt @@ -33,7 +33,6 @@ import de.fraunhofer.aisec.cpg.graph.declarations.* import de.fraunhofer.aisec.cpg.graph.statements.expressions.ArrayCreationExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression import de.fraunhofer.aisec.cpg.graph.types.Type -import de.fraunhofer.aisec.cpg.graph.types.UnknownType import de.fraunhofer.aisec.cpg.sarif.PhysicalLocation /** @@ -131,7 +130,7 @@ fun MetadataProvider.newConstructorDeclaration( @JvmOverloads fun MetadataProvider.newParamVariableDeclaration( name: CharSequence?, - type: Type = UnknownType.getUnknownType(), + type: Type = newUnknownType(), variadic: Boolean = false, code: String? = null, rawNode: Any? = null @@ -155,7 +154,7 @@ fun MetadataProvider.newParamVariableDeclaration( @JvmOverloads fun MetadataProvider.newVariableDeclaration( name: CharSequence?, - type: Type = UnknownType.getUnknownType(), + type: Type = newUnknownType(), code: String? = null, implicitInitializerAllowed: Boolean = false, rawNode: Any? = null @@ -325,7 +324,7 @@ fun MetadataProvider.newEnumConstantDeclaration( @JvmOverloads fun MetadataProvider.newFieldDeclaration( name: CharSequence?, - type: Type = UnknownType.getUnknownType(), + type: Type = newUnknownType(), modifiers: List? = listOf(), code: String? = null, location: PhysicalLocation? = null, diff --git a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/helpers/TriConsumer.java b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/EdgeProperty.kt similarity index 86% rename from cpg-core/src/main/java/de/fraunhofer/aisec/cpg/helpers/TriConsumer.java rename to cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/EdgeProperty.kt index a05eb25f4b..2a6ae6b140 100644 --- a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/helpers/TriConsumer.java +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/EdgeProperty.kt @@ -23,9 +23,8 @@ * \______/ \__| \______/ * */ -package de.fraunhofer.aisec.cpg.helpers; +package de.fraunhofer.aisec.cpg.graph -@FunctionalInterface -public interface TriConsumer { - void accept(A first, B second, C third); -} +@Retention(AnnotationRetention.RUNTIME) +@Target(AnnotationTarget.ANNOTATION_CLASS) +annotation class EdgeProperty(val key: String) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/ExpressionBuilder.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/ExpressionBuilder.kt index 298a02d75e..1057feff2d 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/ExpressionBuilder.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/ExpressionBuilder.kt @@ -31,7 +31,6 @@ import de.fraunhofer.aisec.cpg.graph.Node.Companion.EMPTY_NAME import de.fraunhofer.aisec.cpg.graph.NodeBuilder.log import de.fraunhofer.aisec.cpg.graph.statements.expressions.* import de.fraunhofer.aisec.cpg.graph.types.Type -import de.fraunhofer.aisec.cpg.graph.types.UnknownType /** * Creates a new [Literal]. This is the top-most [Node] that a [LanguageFrontend] or [Handler] @@ -42,7 +41,7 @@ import de.fraunhofer.aisec.cpg.graph.types.UnknownType @JvmOverloads fun MetadataProvider.newLiteral( value: T, - type: Type = UnknownType.getUnknownType(), + type: Type = newUnknownType(), code: String? = null, rawNode: Any? = null, ): Literal { @@ -86,16 +85,16 @@ fun MetadataProvider.newBinaryOperator( */ @JvmOverloads fun MetadataProvider.newUnaryOperator( - operatorType: String, + operatorCode: String, postfix: Boolean, prefix: Boolean, code: String? = null, rawNode: Any? = null ): UnaryOperator { val node = UnaryOperator() - node.applyMetadata(this, operatorType, rawNode, code, true) + node.applyMetadata(this, operatorCode, rawNode, code, true) - node.operatorCode = operatorType + node.operatorCode = operatorCode node.isPostfix = postfix node.isPrefix = prefix @@ -138,7 +137,7 @@ fun MetadataProvider.newAssignExpression( @JvmOverloads fun MetadataProvider.newNewExpression( code: String? = null, - type: Type = UnknownType.getUnknownType(), + type: Type = newUnknownType(), rawNode: Any? = null ): NewExpression { val node = NewExpression() @@ -180,7 +179,7 @@ fun MetadataProvider.newConditionalExpression( condition: Expression, thenExpr: Expression?, elseExpr: Expression?, - type: Type = UnknownType.getUnknownType(), + type: Type = newUnknownType(), code: String? = null, rawNode: Any? = null ): ConditionalExpression { @@ -338,7 +337,7 @@ fun MetadataProvider.newMemberCallExpression( fun MetadataProvider.newMemberExpression( name: CharSequence?, base: Expression, - memberType: Type = UnknownType.getUnknownType(), + memberType: Type = newUnknownType(), operatorCode: String? = ".", code: String? = null, rawNode: Any? = null @@ -378,8 +377,8 @@ fun MetadataProvider.newCastExpression(code: String? = null, rawNode: Any? = nul @JvmOverloads fun MetadataProvider.newTypeIdExpression( operatorCode: String, - type: Type = UnknownType.getUnknownType(), - referencedType: Type = UnknownType.getUnknownType(), + type: Type = newUnknownType(), + referencedType: Type = newUnknownType(), code: String? = null, rawNode: Any? = null ): TypeIdExpression { @@ -439,7 +438,7 @@ fun MetadataProvider.newArrayCreationExpression( @JvmOverloads fun MetadataProvider.newDeclaredReferenceExpression( name: CharSequence?, - type: Type = UnknownType.getUnknownType(), + type: Type = newUnknownType(), code: String? = null, rawNode: Any? = null ): DeclaredReferenceExpression { @@ -554,7 +553,7 @@ fun MetadataProvider.newDesignatedInitializerExpression( @JvmOverloads fun MetadataProvider.newTypeExpression( name: CharSequence?, - type: Type = UnknownType.getUnknownType(), + type: Type = newUnknownType(), rawNode: Any? = null ): TypeExpression { val node = TypeExpression() diff --git a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/Holder.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Holder.kt similarity index 100% rename from cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/Holder.kt rename to cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Holder.kt diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Node.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Node.kt index 4dbfb79181..9c5ccebced 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Node.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Node.kt @@ -39,9 +39,9 @@ import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.unwrap import de.fraunhofer.aisec.cpg.graph.scopes.GlobalScope import de.fraunhofer.aisec.cpg.graph.scopes.RecordScope import de.fraunhofer.aisec.cpg.graph.scopes.Scope -import de.fraunhofer.aisec.cpg.helpers.LocationConverter -import de.fraunhofer.aisec.cpg.helpers.NameConverter import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker +import de.fraunhofer.aisec.cpg.helpers.neo4j.LocationConverter +import de.fraunhofer.aisec.cpg.helpers.neo4j.NameConverter import de.fraunhofer.aisec.cpg.processing.IVisitable import de.fraunhofer.aisec.cpg.sarif.PhysicalLocation import java.util.* diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/NodeBuilder.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/NodeBuilder.kt index 6acf60581e..6e25d2c6c7 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/NodeBuilder.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/NodeBuilder.kt @@ -31,6 +31,7 @@ import de.fraunhofer.aisec.cpg.graph.NodeBuilder.log import de.fraunhofer.aisec.cpg.graph.scopes.Scope import de.fraunhofer.aisec.cpg.graph.statements.expressions.* import de.fraunhofer.aisec.cpg.graph.types.TypeParser +import de.fraunhofer.aisec.cpg.graph.types.UnknownType import de.fraunhofer.aisec.cpg.passes.inference.IsInferredProvider import org.slf4j.LoggerFactory @@ -206,6 +207,18 @@ fun MetadataProvider.newAnnotationMember( return node } +/** + * Creates a new [UnknownType] and sets the appropriate language, if this [MetadataProvider] + * includes a [LanguageProvider]. + */ +fun MetadataProvider?.newUnknownType(): UnknownType { + return if (this is LanguageProvider) { + UnknownType.getUnknownType(language) + } else { + UnknownType.getUnknownType(null) + } +} + /** * Provides a nice alias to [TypeParser.createFrom]. In the future, this should not be used anymore * since we are moving away from the [TypeParser] altogether. diff --git a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/Persistable.java b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Persistable.kt similarity index 93% rename from cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/Persistable.java rename to cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Persistable.kt index 6b9333ea90..01e0b33c36 100644 --- a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/Persistable.java +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Persistable.kt @@ -23,6 +23,6 @@ * \______/ \__| \______/ * */ -package de.fraunhofer.aisec.cpg.graph; +package de.fraunhofer.aisec.cpg.graph -public interface Persistable {} +interface Persistable diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt index 427491f402..4137d3a22e 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt @@ -36,7 +36,6 @@ import de.fraunhofer.aisec.cpg.graph.scopes.RecordScope import de.fraunhofer.aisec.cpg.graph.statements.* import de.fraunhofer.aisec.cpg.graph.statements.expressions.* import de.fraunhofer.aisec.cpg.graph.types.Type -import de.fraunhofer.aisec.cpg.graph.types.UnknownType fun LanguageFrontend.translationResult( config: TranslationConfiguration, @@ -123,7 +122,7 @@ context(DeclarationHolder) fun LanguageFrontend.field( name: CharSequence, - type: Type = UnknownType.getUnknownType(), + type: Type = newUnknownType(), init: FieldDeclaration.() -> Unit ): FieldDeclaration { val node = newFieldDeclaration(name) @@ -145,7 +144,7 @@ context(DeclarationHolder) fun LanguageFrontend.function( name: CharSequence, - returnType: Type = UnknownType.getUnknownType(), + returnType: Type = newUnknownType(), returnTypes: List? = null, init: (FunctionDeclaration.() -> Unit)? = null ): FunctionDeclaration { @@ -175,7 +174,7 @@ context(RecordDeclaration) fun LanguageFrontend.method( name: CharSequence, - returnType: Type = UnknownType.getUnknownType(), + returnType: Type = newUnknownType(), init: MethodDeclaration.() -> Unit ): MethodDeclaration { val node = newMethodDeclaration(name) @@ -240,7 +239,7 @@ context(FunctionDeclaration) fun LanguageFrontend.param( name: CharSequence, - type: Type = UnknownType.getUnknownType(), + type: Type = newUnknownType(), init: (ParamVariableDeclaration.() -> Unit)? = null ): ParamVariableDeclaration { val node = @@ -296,7 +295,7 @@ context(DeclarationStatement) fun LanguageFrontend.variable( name: String, - type: Type = UnknownType.getUnknownType(), + type: Type = newUnknownType(), init: (VariableDeclaration.() -> Unit)? = null ): VariableDeclaration { val node = newVariableDeclaration(name, type) @@ -433,10 +432,7 @@ fun LanguageFrontend.new(init: (NewExpression.() -> Unit)? = null): NewExpressio return node } -fun LanguageFrontend.memberOrRef( - name: Name, - type: Type = UnknownType.getUnknownType() -): Expression { +fun LanguageFrontend.memberOrRef(name: Name, type: Type = newUnknownType()): Expression { val node = if (name.parent != null) { newMemberExpression(name.localName, memberOrRef(name.parent)) @@ -535,7 +531,7 @@ fun LanguageFrontend.elseStmt( */ context(Holder) -fun LanguageFrontend.literal(value: N, type: Type = UnknownType.getUnknownType()): Literal { +fun LanguageFrontend.literal(value: N, type: Type = newUnknownType()): Literal { val node = newLiteral(value, type) // Only add this to an argument holder if the nearest holder is an argument holder @@ -556,7 +552,7 @@ context(Holder) fun LanguageFrontend.ref( name: CharSequence, - type: Type = UnknownType.getUnknownType(), + type: Type = newUnknownType(), init: (DeclaredReferenceExpression.() -> Unit)? = null ): DeclaredReferenceExpression { val node = newDeclaredReferenceExpression(name) @@ -586,13 +582,13 @@ fun LanguageFrontend.member(name: CharSequence, base: Expression? = null): Membe val parsedName = parseName(name) val type = if (parsedName.parent != null) { - UnknownType.getUnknownType() + newUnknownType() } else { var scope = ((this@Holder) as? ScopeProvider)?.scope while (scope != null && scope !is RecordScope) { scope = scope.parent } - val scopeType = scope?.name?.let { t(it) } ?: UnknownType.getUnknownType() + val scopeType = scope?.name?.let { t(it) } ?: newUnknownType() scopeType } val memberBase = base ?: memberOrRef(parsedName.parent ?: parseName("this"), type) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/ClassTemplateDeclaration.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/ClassTemplateDeclaration.kt index 437ac531de..625e5713fa 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/ClassTemplateDeclaration.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/ClassTemplateDeclaration.kt @@ -66,11 +66,11 @@ class ClassTemplateDeclaration : TemplateDeclaration() { } } - override fun equals(o: Any?): Boolean { - if (this === o) return true - if (o == null || javaClass != o.javaClass) return false - if (!super.equals(o)) return false - val that = o as ClassTemplateDeclaration + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || javaClass != other.javaClass) return false + if (!super.equals(other)) return false + val that = other as ClassTemplateDeclaration return realizations == that.realizations && propertyEqualsList(realizationEdges, that.realizationEdges) && parameters == that.parameters && diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/FunctionDeclaration.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/FunctionDeclaration.kt index 445bb91c78..4f6cf72107 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/FunctionDeclaration.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/FunctionDeclaration.kt @@ -32,11 +32,11 @@ import de.fraunhofer.aisec.cpg.graph.edge.Properties import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.propertyEqualsList import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdgeDelegate +import de.fraunhofer.aisec.cpg.graph.newUnknownType import de.fraunhofer.aisec.cpg.graph.statements.CompoundStatement import de.fraunhofer.aisec.cpg.graph.statements.Statement import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression import de.fraunhofer.aisec.cpg.graph.types.Type -import de.fraunhofer.aisec.cpg.graph.types.UnknownType import java.util.* import java.util.stream.Collectors import org.apache.commons.lang3.builder.ToStringBuilder @@ -198,7 +198,7 @@ open class FunctionDeclaration : ValueDeclaration(), DeclarationHolder { if (paramVariableDeclaration.default != null) { signature.add(paramVariableDeclaration.type) } else { - signature.add(UnknownType.getUnknownType(language)) + signature.add(newUnknownType()) } } return signature diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/RecordDeclaration.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/RecordDeclaration.kt index 1755a0e079..37bf64944d 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/RecordDeclaration.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/RecordDeclaration.kt @@ -177,22 +177,22 @@ class RecordDeclaration : Declaration(), DeclarationHolder, StatementHolder { .toString() } - override fun equals(o: Any?): Boolean { - if (this === o) return true - if (o !is RecordDeclaration) return false - return super.equals(o) && - kind == o.kind && - fields == o.fields && - propertyEqualsList(fieldEdges, o.fieldEdges) && - methods == o.methods && - propertyEqualsList(methodEdges, o.methodEdges) && - constructors == o.constructors && - propertyEqualsList(constructorEdges, o.constructorEdges) && - records == o.records && - propertyEqualsList(recordEdges, o.recordEdges) && - superClasses == o.superClasses && - implementedInterfaces == o.implementedInterfaces && - superTypeDeclarations == o.superTypeDeclarations + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is RecordDeclaration) return false + return super.equals(other) && + kind == other.kind && + fields == other.fields && + propertyEqualsList(fieldEdges, other.fieldEdges) && + methods == other.methods && + propertyEqualsList(methodEdges, other.methodEdges) && + constructors == other.constructors && + propertyEqualsList(constructorEdges, other.constructorEdges) && + records == other.records && + propertyEqualsList(recordEdges, other.recordEdges) && + superClasses == other.superClasses && + implementedInterfaces == other.implementedInterfaces && + superTypeDeclarations == other.superTypeDeclarations } override fun hashCode() = super.hashCode() // TODO: Which fields can be safely added? diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/TypedefDeclaration.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/TypedefDeclaration.kt index 1d0452ba2e..a591f7810d 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/TypedefDeclaration.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/TypedefDeclaration.kt @@ -33,10 +33,10 @@ import org.apache.commons.lang3.builder.ToStringBuilder /** Represents a type alias definition as found in C/C++: `typedef unsigned long ulong;` */ class TypedefDeclaration : Declaration() { /** The already existing type that is to be aliased */ - var type: Type = UnknownType.getUnknownType() + var type: Type = UnknownType.getUnknownType(null) /** The newly created alias to be defined */ - var alias: Type = UnknownType.getUnknownType() + var alias: Type = UnknownType.getUnknownType(null) override fun equals(other: Any?): Boolean { if (this === other) return true diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/ValueDeclaration.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/ValueDeclaration.kt index 0f9199cf96..651474ef30 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/ValueDeclaration.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/ValueDeclaration.kt @@ -30,6 +30,7 @@ import de.fraunhofer.aisec.cpg.graph.TypeManager import de.fraunhofer.aisec.cpg.graph.edge.Properties import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.unwrap +import de.fraunhofer.aisec.cpg.graph.newUnknownType import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression import de.fraunhofer.aisec.cpg.graph.types.FunctionPointerType import de.fraunhofer.aisec.cpg.graph.types.ReferenceType @@ -46,7 +47,7 @@ abstract class ValueDeclaration : Declaration(), HasType { * A dedicated backing field, so that [setType] can actually set the type without any loops, * since we are using a custom setter in [type] (which calls [setType]). */ - @Relationship("TYPE") protected var _type: Type = UnknownType.getUnknownType() + @Relationship("TYPE") protected var _type: Type = UnknownType.getUnknownType(null) /** * The type of this declaration. In order to maximize compatibility with Java legacy code @@ -62,9 +63,8 @@ abstract class ValueDeclaration : Declaration(), HasType { TypeManager.getInstance() .typeCache .computeIfAbsent(this) { mutableListOf() } - .stream() - .findAny() - .orElse(UnknownType.getUnknownType()) + .firstOrNull() + ?: newUnknownType() } return result } @@ -126,40 +126,40 @@ abstract class ValueDeclaration : Declaration(), HasType { override val propagationType: Type get() { return if (type is ReferenceType) { - (type as ReferenceType?)?.elementType ?: UnknownType.getUnknownType() + (type as ReferenceType?)?.elementType ?: newUnknownType() } else type } override fun setType(type: Type, root: MutableList?) { - var type: Type? = type - var root: MutableList? = root + var t: Type? = type + var r: MutableList? = root if (!TypeManager.isTypeSystemActive()) { - TypeManager.getInstance().cacheType(this, type) + TypeManager.getInstance().cacheType(this, t) return } - if (root == null) { - root = ArrayList() + if (r == null) { + r = ArrayList() } if ( - type == null || - root.contains(this) || - TypeManager.getInstance().isUnknown(type) || - this._type is FunctionPointerType && type !is FunctionPointerType + t == null || + r.contains(this) || + TypeManager.getInstance().isUnknown(t) || + this._type is FunctionPointerType && t !is FunctionPointerType ) { return } val oldType = this.type - type = type.duplicate() + t = t.duplicate() val subTypes: MutableSet = HashSet() for (t in possibleSubTypes) { - if (!t.isSimilar(type)) { + if (!t.isSimilar(t)) { subTypes.add(t) } } - subTypes.add(type) + subTypes.add(t) this._type = TypeManager.getInstance() - .registerType(TypeManager.getInstance().getCommonType(subTypes, this).orElse(type)) + .registerType(TypeManager.getInstance().getCommonType(subTypes, this).orElse(t)) val newSubtypes: MutableList = ArrayList() for (s in subTypes) { if (TypeManager.getInstance().isSupertypeOf(this.type, s, this)) { @@ -167,28 +167,28 @@ abstract class ValueDeclaration : Declaration(), HasType { } } possibleSubTypes = newSubtypes - if (oldType == type) { + if (oldType == t) { // Nothing changed, so we do not have to notify the listeners. return } - root.add(this) // Add current node to the set of "triggers" to detect potential loops. + r.add(this) // Add current node to the set of "triggers" to detect potential loops. // Notify all listeners about the changed type for (l in typeListeners) { if (l != this) { - l.typeChanged(this, root, oldType) + l.typeChanged(this, r, oldType) } } } override fun setPossibleSubTypes(possibleSubTypes: List, root: MutableList) { - var possibleSubTypes = possibleSubTypes - possibleSubTypes = - possibleSubTypes + var list = possibleSubTypes + list = + list .filterNot { type -> TypeManager.getInstance().isUnknown(type) } .distinct() .toMutableList() if (!TypeManager.isTypeSystemActive()) { - possibleSubTypes.forEach { t -> TypeManager.getInstance().cacheType(this, t) } + list.forEach { t -> TypeManager.getInstance().cacheType(this, t) } return } @@ -196,9 +196,9 @@ abstract class ValueDeclaration : Declaration(), HasType { return } val oldSubTypes = this.possibleSubTypes - this._possibleSubTypes = possibleSubTypes + this._possibleSubTypes = list - if (HashSet(oldSubTypes).containsAll(possibleSubTypes)) { + if (HashSet(oldSubTypes).containsAll(list)) { // Nothing changed, so we do not have to notify the listeners. return } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/scopes/Scope.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/scopes/Scope.kt index 7db16ee9ba..a7255d0165 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/scopes/Scope.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/scopes/Scope.kt @@ -29,7 +29,7 @@ import com.fasterxml.jackson.annotation.JsonBackReference import de.fraunhofer.aisec.cpg.graph.Name import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.statements.LabelStatement -import de.fraunhofer.aisec.cpg.helpers.NameConverter +import de.fraunhofer.aisec.cpg.helpers.neo4j.NameConverter import org.neo4j.ogm.annotation.GeneratedValue import org.neo4j.ogm.annotation.Id import org.neo4j.ogm.annotation.NodeEntity diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/BinaryOperator.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/BinaryOperator.kt index f7a06a3234..8e9f9a078c 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/BinaryOperator.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/BinaryOperator.kt @@ -153,9 +153,7 @@ class BinaryOperator : // function pointer call. setType(src.propagationType, root) } else { - val resultingType = - language?.propagateTypeOfBinaryOperation(this) - ?: UnknownType.getUnknownType(language) + val resultingType = language?.propagateTypeOfBinaryOperation(this) ?: newUnknownType() if (resultingType !is UnknownType) { setType(resultingType, root) } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/CallExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/CallExpression.kt index aadba8dc06..06cb12f263 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/CallExpression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/CallExpression.kt @@ -39,7 +39,6 @@ import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.unwrap import de.fraunhofer.aisec.cpg.graph.types.FunctionPointerType import de.fraunhofer.aisec.cpg.graph.types.TupleType import de.fraunhofer.aisec.cpg.graph.types.Type -import de.fraunhofer.aisec.cpg.graph.types.UnknownType import de.fraunhofer.aisec.cpg.passes.CallResolver import de.fraunhofer.aisec.cpg.passes.VariableUsageResolver import java.util.* @@ -286,7 +285,7 @@ open class CallExpression : Expression(), HasType.TypeListener, SecondaryTypeEdg } null } - val alternative = if (types.isNotEmpty()) types[0] else UnknownType.getUnknownType(language) + val alternative = if (types.isNotEmpty()) types[0] else newUnknownType() val commonType = TypeManager.getInstance().getCommonType(types, this).orElse(alternative) val subTypes: MutableList = ArrayList(possibleSubTypes) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/CastExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/CastExpression.kt index 300fd960d9..13dc0014c1 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/CastExpression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/CastExpression.kt @@ -25,12 +25,8 @@ */ package de.fraunhofer.aisec.cpg.graph.statements.expressions -import de.fraunhofer.aisec.cpg.graph.AST -import de.fraunhofer.aisec.cpg.graph.HasType -import de.fraunhofer.aisec.cpg.graph.Name -import de.fraunhofer.aisec.cpg.graph.TypeManager +import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.types.Type -import de.fraunhofer.aisec.cpg.graph.types.UnknownType import java.util.* import kotlin.collections.ArrayList import org.slf4j.LoggerFactory @@ -38,7 +34,7 @@ import org.slf4j.LoggerFactory class CastExpression : Expression(), HasType.TypeListener { @AST var expression: Expression = ProblemExpression("could not parse inner expression") - var castType: Type = UnknownType.getUnknownType() + var castType: Type = newUnknownType() set(value) { field = value type = value diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ConditionalExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ConditionalExpression.kt index 2445483588..bdac419afd 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ConditionalExpression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ConditionalExpression.kt @@ -28,8 +28,8 @@ package de.fraunhofer.aisec.cpg.graph.statements.expressions import de.fraunhofer.aisec.cpg.graph.AST import de.fraunhofer.aisec.cpg.graph.HasType import de.fraunhofer.aisec.cpg.graph.TypeManager +import de.fraunhofer.aisec.cpg.graph.newUnknownType import de.fraunhofer.aisec.cpg.graph.types.Type -import de.fraunhofer.aisec.cpg.graph.types.UnknownType import java.util.ArrayList import java.util.Objects import org.apache.commons.lang3.builder.ToStringBuilder @@ -70,7 +70,7 @@ class ConditionalExpression : Expression(), HasType.TypeListener { val subTypes: MutableList = ArrayList(possibleSubTypes) subTypes.remove(oldType) subTypes.addAll(types) - val alternative = if (types.isNotEmpty()) types[0] else UnknownType.getUnknownType() + val alternative = if (types.isNotEmpty()) types[0] else newUnknownType() setType(TypeManager.getInstance().getCommonType(types, this).orElse(alternative), root) setPossibleSubTypes(subTypes, root) if (previous != type) { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/Expression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/Expression.kt index 6947ae30a6..5ed5e3ecb1 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/Expression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/Expression.kt @@ -30,7 +30,6 @@ import de.fraunhofer.aisec.cpg.graph.statements.Statement import de.fraunhofer.aisec.cpg.graph.types.FunctionPointerType import de.fraunhofer.aisec.cpg.graph.types.ReferenceType import de.fraunhofer.aisec.cpg.graph.types.Type -import de.fraunhofer.aisec.cpg.graph.types.UnknownType import java.util.* import org.apache.commons.lang3.builder.ToStringBuilder import org.neo4j.ogm.annotation.Relationship @@ -49,7 +48,7 @@ import org.neo4j.ogm.annotation.Transient */ abstract class Expression : Statement(), HasType { - @Relationship("TYPE") private var _type: Type = UnknownType.getUnknownType() + @Relationship("TYPE") private var _type: Type = newUnknownType() /** The type of the value after evaluation. */ override var type: Type @@ -61,9 +60,8 @@ abstract class Expression : Statement(), HasType { TypeManager.getInstance() .typeCache .computeIfAbsent(this) { mutableListOf() } - .stream() - .findAny() - .orElse(UnknownType.getUnknownType()) + .firstOrNull() + ?: newUnknownType() } return result } @@ -89,34 +87,34 @@ abstract class Expression : Statement(), HasType { override val propagationType: Type get() { return if (type is ReferenceType) { - (type as ReferenceType?)?.elementType ?: UnknownType.getUnknownType() + (type as ReferenceType?)?.elementType ?: newUnknownType() } else type } @Override override fun setType(type: Type, root: MutableList?) { - var type: Type = type - var root: MutableList? = root + var t: Type = type + var r: MutableList? = root // TODO Document this method. It is called very often (potentially for each AST node) and // performs less than optimal. if (!TypeManager.isTypeSystemActive()) { - this._type = type - TypeManager.getInstance().cacheType(this, type) + this._type = t + TypeManager.getInstance().cacheType(this, t) return } - if (root == null) { - root = mutableListOf() + if (r == null) { + r = mutableListOf() } // No (or only unknown) type given, loop detected? Stop early because there's nothing we can // do. if ( - root.contains(this) || - TypeManager.getInstance().isUnknown(type) || - TypeManager.getInstance().stopPropagation(this.type, type) || - (this.type is FunctionPointerType && type !is FunctionPointerType) + r.contains(this) || + TypeManager.getInstance().isUnknown(t) || + TypeManager.getInstance().stopPropagation(this.type, t) || + (this.type is FunctionPointerType && t !is FunctionPointerType) ) { return } @@ -124,22 +122,22 @@ abstract class Expression : Statement(), HasType { val oldType = this.type // Backup to check if something changed - type = type.duplicate() + t = t.duplicate() val subTypes = mutableSetOf() // Check all current subtypes and consider only those which are "different enough" to type. - for (t in possibleSubTypes) { - if (!t.isSimilar(type)) { - subTypes.add(t) + for (s in possibleSubTypes) { + if (!s.isSimilar(s)) { + subTypes.add(s) } } - subTypes.add(type) + subTypes.add(t) // Probably tries to get something like the best supertype of all possible subtypes. this._type = TypeManager.getInstance() - .registerType(TypeManager.getInstance().getCommonType(subTypes, this).orElse(type)) + .registerType(TypeManager.getInstance().getCommonType(subTypes, this).orElse(t)) // TODO: Why do we need this loop? Shouldn't the condition be ensured by the previous line // getting the common type?? @@ -152,40 +150,40 @@ abstract class Expression : Statement(), HasType { possibleSubTypes = newSubtypes - if (oldType == type) { + if (oldType == t) { // Nothing changed, so we do not have to notify the listeners. return } // Add current node to the set of "triggers" to detect potential loops. - root.add(this) + r.add(this) // Notify all listeners about the changed type for (l in typeListeners) { if (l != this) { - l.typeChanged(this, root, oldType) + l.typeChanged(this, r, oldType) } } } override fun setPossibleSubTypes(possibleSubTypes: List, root: MutableList) { - var possibleSubTypes = possibleSubTypes - possibleSubTypes = - possibleSubTypes + var list = possibleSubTypes + list = + list .filterNot { type -> TypeManager.getInstance().isUnknown(type) } .distinct() .toMutableList() if (!TypeManager.isTypeSystemActive()) { - possibleSubTypes.forEach { t -> TypeManager.getInstance().cacheType(this, t) } + list.forEach { t -> TypeManager.getInstance().cacheType(this, t) } return } if (root.contains(this)) { return } val oldSubTypes = this.possibleSubTypes - this._possibleSubTypes = possibleSubTypes + this._possibleSubTypes = list - if (HashSet(oldSubTypes).containsAll(possibleSubTypes)) { + if (HashSet(oldSubTypes).containsAll(list)) { // Nothing changed, so we do not have to notify the listeners. return } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ExpressionList.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ExpressionList.kt index 331be3cbb2..29eb3f137a 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ExpressionList.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ExpressionList.kt @@ -94,16 +94,16 @@ class ExpressionList : Expression(), HasType.TypeListener { setPossibleSubTypes(ArrayList(src.possibleSubTypes), root) } - override fun equals(o: Any?): Boolean { - if (this === o) { + override fun equals(other: Any?): Boolean { + if (this === other) { return true } - if (o !is ExpressionList) { + if (other !is ExpressionList) { return false } - return (super.equals(o) && - expressions == o.expressions && - propertyEqualsList(expressionEdges, o.expressionEdges)) + return (super.equals(other) && + expressions == other.expressions && + propertyEqualsList(expressionEdges, other.expressionEdges)) } override fun hashCode(): Int { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/InitializerListExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/InitializerListExpression.kt index 6cba776b9e..e41967162b 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/InitializerListExpression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/InitializerListExpression.kt @@ -32,9 +32,9 @@ import de.fraunhofer.aisec.cpg.graph.edge.Properties import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.propertyEqualsList import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdgeDelegate +import de.fraunhofer.aisec.cpg.graph.newUnknownType import de.fraunhofer.aisec.cpg.graph.types.PointerType.PointerOrigin import de.fraunhofer.aisec.cpg.graph.types.Type -import de.fraunhofer.aisec.cpg.graph.types.UnknownType import java.util.* import org.apache.commons.lang3.builder.ToStringBuilder import org.neo4j.ogm.annotation.Relationship @@ -86,8 +86,7 @@ class InitializerListExpression : Expression(), HasType.TypeListener { .registerType(it.type.reference(PointerOrigin.ARRAY)) } .toSet() - val alternative = - if (types.isNotEmpty()) types.iterator().next() else UnknownType.getUnknownType() + val alternative = if (types.isNotEmpty()) types.iterator().next() else newUnknownType() newType = TypeManager.getInstance().getCommonType(types, this).orElse(alternative) subTypes = ArrayList(possibleSubTypes) subTypes.remove(oldType) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/LambdaExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/LambdaExpression.kt index 136d5358eb..c866e25b2b 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/LambdaExpression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/LambdaExpression.kt @@ -82,7 +82,7 @@ class LambdaExpression : Expression(), HasType.TypeListener { // the incoming "type" is associated to the function and it is only its return type (if it // is known). what we really want is to construct a function type, or rather a function // pointer type, since this is the closest to what we have - val functionType = FunctionPointerType(parameterTypes, returnType, this.language) + val functionType = FunctionPointerType(parameterTypes, this.language, returnType) setType(functionType, root) if (previous != type) { diff --git a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/types/BooleanType.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/BooleanType.kt similarity index 100% rename from cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/types/BooleanType.kt rename to cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/BooleanType.kt diff --git a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/types/FloatingPointType.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/FloatingPointType.kt similarity index 100% rename from cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/types/FloatingPointType.kt rename to cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/FloatingPointType.kt diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/FunctionPointerType.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/FunctionPointerType.kt new file mode 100644 index 0000000000..1a43e19cce --- /dev/null +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/FunctionPointerType.kt @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2021, 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.graph.types + +import de.fraunhofer.aisec.cpg.frontends.Language +import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend +import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge +import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.propertyEqualsList +import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.transformIntoOutgoingPropertyEdgeList +import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.unwrap +import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdgeDelegate +import de.fraunhofer.aisec.cpg.graph.types.PointerType.PointerOrigin +import java.util.* +import org.apache.commons.lang3.builder.ToStringBuilder +import org.neo4j.ogm.annotation.Relationship + +/** + * FunctionPointerType represents function pointers containing a list of parameters and a return + * type. + * + * This class is currently only used in the C++ language frontend. + * + * TODO(oxisto): We want to replace this dedicated type with a simple [PointerType] to a + * [FunctionType] in the future + */ +class FunctionPointerType : Type { + @Relationship(value = "PARAMETERS", direction = Relationship.Direction.OUTGOING) + var parametersPropertyEdge: MutableList> = mutableListOf() + private set + + var returnType: Type + + var parameters by PropertyEdgeDelegate(FunctionPointerType::parametersPropertyEdge) + + constructor( + parameters: List = listOf(), + language: Language? = null, + returnType: Type = UnknownType.getUnknownType(language) + ) : super(EMPTY_NAME, language) { + parametersPropertyEdge = transformIntoOutgoingPropertyEdgeList(parameters, this) + this.returnType = returnType + } + + constructor( + type: Type, + parameters: List = listOf(), + language: Language? = null, + returnType: Type = UnknownType.getUnknownType(language) + ) : super(type) { + parametersPropertyEdge = transformIntoOutgoingPropertyEdgeList(parameters, this) + this.returnType = returnType + this.language = language + } + + override fun reference(pointer: PointerOrigin?): PointerType { + return PointerType(this, pointer) + } + + override fun dereference(): Type { + return this + } + + override fun duplicate(): Type { + val copiedParameters: List = ArrayList(unwrap(parametersPropertyEdge)) + return FunctionPointerType(this, copiedParameters, language, returnType) + } + + override fun isSimilar(t: Type?): Boolean { + return if (t is FunctionPointerType) { + parametersPropertyEdge == t.parametersPropertyEdge && returnType == t.returnType + } else false + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is FunctionPointerType) return false + return super.equals(other) && + parameters == other.parameters && + propertyEqualsList(parametersPropertyEdge, other.parametersPropertyEdge) && + returnType == other.returnType + } + + override fun hashCode() = Objects.hash(super.hashCode(), parametersPropertyEdge, returnType) + + override fun toString(): String { + return ToStringBuilder(this, TO_STRING_STYLE) + .appendSuper(super.toString()) + .append("parameters", parameters) + .append("returnType", returnType) + .append("typeOrigin", typeOrigin) + .toString() + } +} diff --git a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/types/FunctionType.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/FunctionType.kt similarity index 81% rename from cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/types/FunctionType.kt rename to cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/FunctionType.kt index 7cc18683fa..9ec18da196 100644 --- a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/types/FunctionType.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/FunctionType.kt @@ -29,6 +29,7 @@ import de.fraunhofer.aisec.cpg.frontends.Language import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend import de.fraunhofer.aisec.cpg.graph.TypeManager import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration +import de.fraunhofer.aisec.cpg.graph.newUnknownType /** * A type representing a function. It contains a list of parameters and one or more return types. @@ -36,32 +37,23 @@ import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration * It can be referenced into a [FunctionPointerType]. In the future, we will probably change this * and remove the [FunctionPointerType] and directly use a [PointerType]. */ -class FunctionType : Type { - - @JvmOverloads - constructor( - typeName: String, - parameters: List, - returnTypes: List, - language: Language?, - ) : super(typeName, language) { - this.parameters = parameters - this.returnTypes = returnTypes - } - - constructor() : super() - - var parameters: List = listOf() - var returnTypes: List = listOf() +class FunctionType +@JvmOverloads +constructor( + typeName: String = "", + var parameters: List = listOf(), + var returnTypes: List = listOf(), + language: Language? = null +) : Type(typeName, language) { override fun reference(pointer: PointerType.PointerOrigin?): Type { // TODO(oxisto): In the future, we actually could just remove the FunctionPointerType // and just have a regular PointerType here - return FunctionPointerType(parameters.toList(), returnTypes.first(), language) + return FunctionPointerType(parameters.toList(), language, returnTypes.first()) } override fun dereference(): Type { - return UnknownType.getUnknownType(language) + return newUnknownType() } override fun duplicate(): Type { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/HasType.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/HasType.kt similarity index 100% rename from cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/HasType.kt rename to cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/HasType.kt diff --git a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/types/IncompleteType.java b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/IncompleteType.kt similarity index 59% rename from cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/types/IncompleteType.java rename to cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/IncompleteType.kt index 84cf5ad84e..b9b9772dd4 100644 --- a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/types/IncompleteType.java +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/IncompleteType.kt @@ -23,57 +23,40 @@ * \______/ \__| \______/ * */ -package de.fraunhofer.aisec.cpg.graph.types; +package de.fraunhofer.aisec.cpg.graph.types -import java.util.Objects; +import de.fraunhofer.aisec.cpg.graph.types.PointerType.PointerOrigin +import java.util.* /** * IncompleteTypes are defined as object with unknown size. For instance: void, arrays of unknown * length, forward declarated classes in C++ * - *

Right now we are only dealing with void for objects with unknown size, therefore the name is + * Right now we are only dealing with void for objects with unknown size, therefore the name is * fixed to void. However, this can be changed in the future, in order to support other objects with * unknown size apart from void. Therefore, this Type is not called VoidType */ -public class IncompleteType extends Type { +class IncompleteType : Type { + constructor() : super("void", null) + constructor(type: Type?) : super(type!!) - public IncompleteType() { - super("void", null); - } + /** @return PointerType to a IncompleteType, e.g. void* */ + override fun reference(pointer: PointerOrigin?): Type { + return PointerType(this, pointer) + } - public IncompleteType(Type type) { - super(type); - } + /** @return dereferencing void results in void therefore the same type is returned */ + override fun dereference(): Type { + return this + } - /** - * @return PointerType to a IncompleteType, e.g. void* - */ - @Override - public Type reference(PointerType.PointerOrigin pointerOrigin) { - return new PointerType(this, pointerOrigin); - } + override fun duplicate(): Type { + return IncompleteType(this) + } - /** - * @return dereferencing void results in void therefore the same type is returned - */ - @Override - public Type dereference() { - return this; - } + override fun equals(other: Any?): Boolean { + return other is IncompleteType + } - @Override - public Type duplicate() { - return new IncompleteType(this); - } - - @Override - public boolean equals(Object o) { - return o instanceof IncompleteType; - } - - @Override - public int hashCode() { - - return Objects.hash(super.hashCode()); - } + override fun hashCode() = Objects.hash(super.hashCode()) } diff --git a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/types/IntegerType.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/IntegerType.kt similarity index 100% rename from cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/types/IntegerType.kt rename to cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/IntegerType.kt diff --git a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/types/NumericType.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/NumericType.kt similarity index 93% rename from cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/types/NumericType.kt rename to cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/NumericType.kt index a9a3f82871..876cf833b0 100644 --- a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/types/NumericType.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/NumericType.kt @@ -43,8 +43,7 @@ open class NumericType( /** * NumericTypes can have a modifier. The default is signed. Some types (e.g. char in C) may be - * neither of the signed/unsigned option. TODO: maybe replace with a flag "signed" or - * "unsigned"? + * neither of the signed/unsigned option. */ enum class Modifier { SIGNED, @@ -55,5 +54,5 @@ open class NumericType( override fun equals(other: Any?) = super.equals(other) && this.modifier == (other as? NumericType)?.modifier - override fun hashCode() = Objects.hash(super.hashCode(), generics, modifier, primitive) + override fun hashCode() = Objects.hash(super.hashCode(), generics, modifier, isPrimitive) } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/ObjectType.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/ObjectType.kt new file mode 100644 index 0000000000..9876ba71d5 --- /dev/null +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/ObjectType.kt @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2019, 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.graph.types + +import de.fraunhofer.aisec.cpg.frontends.Language +import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend +import de.fraunhofer.aisec.cpg.graph.HasType.SecondaryTypeEdge +import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration +import de.fraunhofer.aisec.cpg.graph.edge.Properties +import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge +import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.propertyEqualsList +import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.transformIntoOutgoingPropertyEdgeList +import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdgeDelegate +import de.fraunhofer.aisec.cpg.graph.types.PointerType.PointerOrigin +import de.fraunhofer.aisec.cpg.graph.types.UnknownType.Companion.getUnknownType +import java.util.* +import org.neo4j.ogm.annotation.Relationship + +/** + * This is the main type in the Type system. ObjectTypes describe objects, as instances of a class. + * This also includes primitive data types. + */ +open class ObjectType : Type, SecondaryTypeEdge { + /** + * Reference from the ObjectType to its class (RecordDeclaration) only if the class is available + */ + var recordDeclaration: RecordDeclaration? = null + + @Relationship(value = "GENERICS", direction = Relationship.Direction.OUTGOING) + var genericsPropertyEdges: MutableList> = mutableListOf() + + var generics by PropertyEdgeDelegate(ObjectType::genericsPropertyEdges) + + constructor( + typeName: CharSequence, + generics: List, + primitive: Boolean, + language: Language? + ) : super(typeName, language) { + this.genericsPropertyEdges = transformIntoOutgoingPropertyEdgeList(generics, this) + isPrimitive = primitive + this.language = language + } + + constructor( + type: Type?, + generics: List, + primitive: Boolean, + language: Language? + ) : super(type) { + this.language = language + this.genericsPropertyEdges = transformIntoOutgoingPropertyEdgeList(generics, this) + isPrimitive = primitive + } + + /** Empty default constructor for use in Neo4J persistence. */ + constructor() : super() { + genericsPropertyEdges = ArrayList() + isPrimitive = false + } + + override fun updateType(typeState: Collection) { + for (t in generics) { + for (t2 in typeState) { + if (t2 == t) { + replaceGenerics(t, t2) + } + } + } + } + + fun replaceGenerics(oldType: Type?, newType: Type) { + for (i in genericsPropertyEdges.indices) { + val propertyEdge = genericsPropertyEdges[i] + if (propertyEdge.end.equals(oldType)) { + propertyEdge.end = newType + } + } + } + + /** @return PointerType to a ObjectType, e.g. int* */ + override fun reference(pointer: PointerOrigin?): PointerType { + return PointerType(this, pointer) + } + + fun reference(): PointerType { + return PointerType(this, PointerOrigin.POINTER) + } + + /** + * @return UnknownType, as we cannot infer any type information when dereferencing an + * ObjectType, as it is just some memory and its interpretation is unknown + */ + override fun dereference(): Type { + return getUnknownType(language) + } + + override fun duplicate(): Type { + return ObjectType(this, generics, isPrimitive, language) + } + + fun addGeneric(generic: Type) { + val propertyEdge = PropertyEdge(this, generic) + propertyEdge.addProperty(Properties.INDEX, genericsPropertyEdges.size) + genericsPropertyEdges.add(propertyEdge) + } + + fun addGenerics(generics: List) { + for (generic in generics) { + addGeneric(generic) + } + } + + override fun isSimilar(t: Type?): Boolean { + return t is ObjectType && generics == t.generics && super.isSimilar(t) + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is ObjectType) return false + if (!super.equals(other)) return false + return generics == other.generics && + propertyEqualsList(genericsPropertyEdges, other.genericsPropertyEdges) && + isPrimitive == other.isPrimitive + } + + override fun hashCode() = Objects.hash(super.hashCode(), generics, isPrimitive) +} diff --git a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/types/ParameterizedType.java b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/ParameterizedType.kt similarity index 62% rename from cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/types/ParameterizedType.java rename to cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/ParameterizedType.kt index b9e4363653..77a9a06953 100644 --- a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/types/ParameterizedType.java +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/ParameterizedType.kt @@ -23,39 +23,34 @@ * \______/ \__| \______/ * */ -package de.fraunhofer.aisec.cpg.graph.types; +package de.fraunhofer.aisec.cpg.graph.types -import de.fraunhofer.aisec.cpg.frontends.Language; -import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend; +import de.fraunhofer.aisec.cpg.frontends.Language +import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend +import de.fraunhofer.aisec.cpg.graph.types.PointerType.PointerOrigin /** * ParameterizedTypes describe types, that are passed as Paramters to Classes E.g. uninitialized * generics in the graph are represented as ParameterizedTypes */ -public class ParameterizedType extends Type { +class ParameterizedType : Type { + constructor(type: Type) : super(type) { + language = type.language + } - public ParameterizedType(Type type) { - super(type); - this.setLanguage(type.getLanguage()); - } + constructor(typeName: String?, language: Language?) : super(typeName) { + this.language = language + } - public ParameterizedType(String typeName, Language language) { - super(typeName); - this.setLanguage(language); - } + override fun reference(pointer: PointerOrigin?): Type { + return PointerType(this, pointer) + } - @Override - public Type reference(PointerType.PointerOrigin pointer) { - return new PointerType(this, pointer); - } + override fun dereference(): Type { + return this + } - @Override - public Type dereference() { - return this; - } - - @Override - public Type duplicate() { - return new ParameterizedType(this); - } + override fun duplicate(): Type { + return ParameterizedType(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 new file mode 100644 index 0000000000..4d608d6b0c --- /dev/null +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/PointerType.kt @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2019, 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.graph.types + +import de.fraunhofer.aisec.cpg.graph.Name +import java.util.* +import org.neo4j.ogm.annotation.Relationship + +/** + * PointerTypes represent all references to other Types. For C/CPP this includes pointers, as well + * as arrays, since technically arrays are pointers. For JAVA the only use case are arrays as there + * is no such pointer concept. + */ +class PointerType : Type, SecondOrderType { + @Relationship(value = "ELEMENT_TYPE") override lateinit var elementType: Type + + enum class PointerOrigin { + POINTER, + ARRAY + } + + var pointerOrigin: PointerOrigin? = null + private set + + constructor() : super() + + constructor(elementType: Type, pointerOrigin: PointerOrigin?) : super() { + language = elementType.language + if (pointerOrigin == PointerOrigin.ARRAY) { + name = elementType.name.append("[]") + } else { + name = elementType.name.append("*") + } + this.pointerOrigin = pointerOrigin + this.elementType = elementType + } + + constructor(type: Type?, elementType: Type, pointerOrigin: PointerOrigin?) : super(type!!) { + language = elementType.language + if (pointerOrigin == PointerOrigin.ARRAY) { + name = elementType.name.append("[]") + } else { + name = elementType.name.append("*") + } + this.pointerOrigin = pointerOrigin + this.elementType = elementType + } + + /** + * @return referencing a PointerType results in another PointerType wrapping the first + * PointerType, e.g. int** + */ + override fun reference(pointer: PointerOrigin?): PointerType { + var origin = pointer + if (origin == null) { + origin = PointerOrigin.ARRAY + } + return PointerType(this, origin) + } + + /** @return dereferencing a PointerType yields the type the pointer was pointing towards */ + override fun dereference(): Type { + return elementType + } + + override fun refreshNames() { + if (elementType is PointerType) { + elementType.refreshNames() + } + var localName = elementType.name.localName + localName += + if (pointerOrigin == PointerOrigin.ARRAY) { + "[]" + } else { + "*" + } + val fullTypeName = Name(localName, elementType.name.parent, elementType.name.delimiter) + name = fullTypeName + } + + override fun duplicate(): Type { + return PointerType(this, elementType.duplicate(), pointerOrigin) + } + + val isArray: Boolean + get() = pointerOrigin == PointerOrigin.ARRAY + + override fun isSimilar(t: Type?): Boolean { + if (t !is PointerType) { + return false + } + return (referenceDepth == t.referenceDepth && + elementType.isSimilar(t.root) && + super.isSimilar(t)) + } + + override val referenceDepth: Int + get() { + var depth = 1 + var containedType = elementType + while (containedType is PointerType) { + depth++ + containedType = containedType.elementType + } + return depth + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is PointerType) return false + return super.equals(other) && + elementType == other.elementType && + pointerOrigin == other.pointerOrigin + } + + override fun hashCode() = Objects.hash(super.hashCode(), elementType, pointerOrigin) +} diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/ReferenceType.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/ReferenceType.kt new file mode 100644 index 0000000000..d424518375 --- /dev/null +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/ReferenceType.kt @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2019, 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.graph.types + +import de.fraunhofer.aisec.cpg.graph.newUnknownType +import de.fraunhofer.aisec.cpg.graph.types.PointerType.PointerOrigin +import java.util.* +import org.apache.commons.lang3.builder.ToStringBuilder + +/** + * ReferenceTypes describe CPP References (int&), which represent an alternative name for a + * variable. It is necessary to make this distinction, and not just rely on the original type as it + * is required for matching parameters in function arguments to discover which implementation is + * called. + */ +class ReferenceType : Type, SecondOrderType { + override var elementType: Type = newUnknownType() + + constructor() : super() + + constructor(reference: Type) : super() { + language = reference.language + name = reference.name.append("&") + this.elementType = reference + } + + constructor(type: Type, reference: Type) : super(type) { + language = reference.language + name = reference.name.append("&") + this.elementType = reference + } + + /** + * @return Referencing a ReferenceType results in a PointerType to the original ReferenceType + */ + override fun reference(pointer: PointerOrigin?): Type { + return PointerType(this, pointer) + } + + /** + * @return Dereferencing a ReferenceType equals to dereferencing the original (non-reference) + * type + */ + override fun dereference(): Type { + return elementType.dereference() + } + + override fun duplicate(): Type { + return ReferenceType(this, elementType) + } + + override fun isSimilar(t: Type?): Boolean { + return t is ReferenceType && t.elementType == this && super.isSimilar(t) + } + + fun refreshName() { + name = elementType.name.append("&") + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is ReferenceType) return false + return super.equals(other) && elementType == other.elementType + } + + override fun hashCode() = Objects.hash(super.hashCode(), elementType) + + override fun toString(): String { + return ToStringBuilder(this, TO_STRING_STYLE) + .appendSuper(super.toString()) + .append("elementType", elementType) + .append("name", name) + .append("typeOrigin", typeOrigin) + .toString() + } +} diff --git a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/types/SecondOrderType.java b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/SecondOrderType.kt similarity index 87% rename from cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/types/SecondOrderType.java rename to cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/SecondOrderType.kt index 9be4ccb20f..061b26a67d 100644 --- a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/types/SecondOrderType.java +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/SecondOrderType.kt @@ -23,11 +23,8 @@ * \______/ \__| \______/ * */ -package de.fraunhofer.aisec.cpg.graph.types; +package de.fraunhofer.aisec.cpg.graph.types -public interface SecondOrderType { - - void setElementType(Type elementType); - - Type getElementType(); +interface SecondOrderType { + var elementType: Type } diff --git a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/types/StringType.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/StringType.kt similarity index 100% rename from cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/types/StringType.kt rename to cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/StringType.kt diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/Type.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/Type.kt new file mode 100644 index 0000000000..5ccb07b95d --- /dev/null +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/Type.kt @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2019, 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.graph.types + +import de.fraunhofer.aisec.cpg.frontends.Language +import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend +import de.fraunhofer.aisec.cpg.graph.Name +import de.fraunhofer.aisec.cpg.graph.Node +import de.fraunhofer.aisec.cpg.graph.parseName +import de.fraunhofer.aisec.cpg.graph.types.PointerType.PointerOrigin +import java.util.* +import org.apache.commons.lang3.builder.ToStringBuilder +import org.neo4j.ogm.annotation.Relationship + +/** + * Abstract Type, describing all possible SubTypes, i.e. all different Subtypes are compliant with + * this class. Contains information which is included in any Type such as name, storage, qualifier + * and origin + */ +abstract class Type : Node { + /** All direct supertypes of this type. */ + @Relationship(value = "SUPER_TYPE", direction = Relationship.Direction.OUTGOING) + var superTypes = mutableSetOf() + protected set + var isPrimitive = false + protected set + open var typeOrigin: Origin? = null + + constructor() { + name = Name(EMPTY_NAME, null, language) + } + + constructor(typeName: String?) { + name = language.parseName(typeName!!) + typeOrigin = Origin.UNRESOLVED + } + + constructor(type: Type?) { + type?.name?.let { name = it.clone() } + typeOrigin = type?.typeOrigin + } + + constructor(typeName: CharSequence, language: Language?) { + if (this is FunctionType) { + name = Name(typeName.toString(), null, language) + } else { + name = language.parseName(typeName) + } + this.language = language + typeOrigin = Origin.UNRESOLVED + } + + constructor(fullTypeName: Name, language: Language?) { + name = fullTypeName.clone() + typeOrigin = Origin.UNRESOLVED + this.language = language + } + + /** Type Origin describes where the Type information came from */ + enum class Origin { + RESOLVED, + DATAFLOW, + GUESSED, + UNRESOLVED + } + + /** + * @param pointer Reason for the reference (array of pointer) + * @return Returns a reference to the current Type. E.g. when creating a pointer to an existing + * ObjectType + */ + abstract fun reference(pointer: PointerOrigin?): Type + + /** + * @return Dereferences the current Type by resolving the reference. E.g. when dereferencing a + * pointer type we obtain the type the pointer is pointing towards + */ + abstract fun dereference(): Type + open fun refreshNames() {} + var root: Type + /** + * Obtain the root Type Element for a Type Chain (follows Pointer and ReferenceTypes until a + * Object-, Incomplete-, or FunctionPtrType is reached). + * + * @return root Type + */ + get() = + if (this is SecondOrderType) { + (this as SecondOrderType).elementType.root + } else { + this + } + set(newRoot) { + if (this is SecondOrderType) { + if ((this as SecondOrderType).elementType is SecondOrderType) { + ((this as SecondOrderType).elementType as SecondOrderType).elementType = newRoot + } else { + (this as SecondOrderType).elementType = newRoot + } + } + } + + /** @return Creates an exact copy of the current type (chain) */ + abstract fun duplicate(): Type + + val typeName: String + get() = name.toString() + open val referenceDepth: Int + /** + * @return number of steps that are required in order to traverse the type chain until the + * root is reached + */ + get() = 0 + val isFirstOrderType: Boolean + /** + * @return True if the Type parameter t is a FirstOrderType (Root of a chain) and not a + * Pointer or ReferenceType + */ + get() = + (this is ObjectType || + this is UnknownType || + this is FunctionType || + this is TupleType // TODO(oxisto): convert FunctionPointerType to second order type + || + this is FunctionPointerType || + this is IncompleteType || + this is ParameterizedType) + + /** + * Required for possibleSubTypes to check if the new Type should be considered a subtype or not + * + * @param t other type the similarity is checked with + * @return True if the parameter t is equal to the current type (this) + */ + open fun isSimilar(t: Type?): Boolean { + return if (this == t) { + true + } else this.root.name == t?.root?.name + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + return if (other !is Type) false else name == other.name && language == other.language + } + + override fun hashCode() = Objects.hash(name, language) + + override fun toString(): String { + return ToStringBuilder(this, TO_STRING_STYLE).append("name", name).toString() + } + + companion object { + const val UNKNOWN_TYPE_STRING = "UNKNOWN" + } +} diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/UnknownType.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/UnknownType.kt new file mode 100644 index 0000000000..7b8a3ac96c --- /dev/null +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/UnknownType.kt @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2019, 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.graph.types + +import de.fraunhofer.aisec.cpg.frontends.Language +import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend +import de.fraunhofer.aisec.cpg.graph.Name +import de.fraunhofer.aisec.cpg.graph.types.PointerType.PointerOrigin +import java.util.* + +/** + * UnknownType describe the case in which it is not possible for the CPG to determine which Type is + * used. E.g.: This occurs when the type is inferred by the compiler automatically when using + * keywords such as auto in cpp + */ +class UnknownType : Type { + private constructor() : super() { + name = Name(UNKNOWN_TYPE_STRING, null, language) + } + + /** + * This is only intended to be used by [TypeParser] for edge cases like distinct unknown types, + * such as "UNKNOWN1", thus the package-private visibility. Other users should see + * [getUnknownType] instead + * + * @param typeName The name of this unknown type, usually a variation of UNKNOWN + */ + internal constructor(typeName: String?) : super(typeName) + + /** + * @return Same UnknownType, as it is makes no sense to obtain a pointer/reference to an + * UnknownType + */ + override fun reference(pointer: PointerOrigin?): Type { + return this + } + + /** @return Same UnknownType, */ + override fun dereference(): Type { + return this + } + + override fun duplicate(): Type { + // We don't duplicate because we cannot change any properties. + return this + } + + override fun hashCode() = Objects.hash(super.hashCode()) + + override fun equals(other: Any?): Boolean { + return other is UnknownType + } + + override fun toString(): String { + return "UNKNOWN" + } + + override var typeOrigin: Origin? = null + + companion object { + /** A map of [UnknownType] and their respective [Language]. */ + private val unknownTypes = mutableMapOf?, UnknownType>() + + /** Use this function to obtain an [UnknownType] for the particular [language]. */ + @JvmStatic + fun getUnknownType(language: Language?): UnknownType { + return unknownTypes.computeIfAbsent(language) { + val unknownType = UnknownType() + unknownType.language = language + unknownType + } + } + } +} diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/WrapState.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/WrapState.kt new file mode 100644 index 0000000000..70dd64dad3 --- /dev/null +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/WrapState.kt @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2021, 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.graph.types + +import de.fraunhofer.aisec.cpg.graph.types.PointerType.PointerOrigin + +/** Stores State for rewrap when typeinformation has been unwrapped */ +class WrapState { + @JvmField var depth = 0 + var isReference = false + @JvmField var pointerOrigins: Array + @JvmField var referenceType: ReferenceType? = null + + init { + pointerOrigins = arrayOf(PointerOrigin.ARRAY) + } + + fun setPointerOrigin(pointerOrigin: Array) { + pointerOrigins = pointerOrigin + } +} diff --git a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/helpers/CommentMatcher.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/CommentMatcher.kt similarity index 100% rename from cpg-core/src/main/java/de/fraunhofer/aisec/cpg/helpers/CommentMatcher.kt rename to cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/CommentMatcher.kt diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/CommonPath.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/CommonPath.kt new file mode 100644 index 0000000000..fab1f5fc00 --- /dev/null +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/CommonPath.kt @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2019, 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.helpers + +import java.io.File +import java.util.regex.Pattern + +/** Find the common root path for a list of files */ +object CommonPath { + fun commonPath(paths: Collection): File? { + if (paths.isEmpty()) { + return null + } + val longestPrefix = StringBuilder() + val splitPaths = + paths + .map { + it.absolutePath + .split(Pattern.quote(File.separator).toRegex()) + .dropLastWhile { it.isEmpty() } + .toTypedArray() + } + .sortedBy { it.size } + val shortest = splitPaths[0] + for (i in shortest.indices) { + val part = shortest[i] + if (splitPaths.all { it[i] == part }) { + longestPrefix.append(part).append(File.separator) + } else { + break + } + } + val result = File(longestPrefix.toString()) + return if (result.exists()) { + getNearestDirectory(result) + } else null + } + + private fun getNearestDirectory(file: File): File { + return if (file.isDirectory) { + file + } else { + getNearestDirectory(file.parentFile) + } + } +} diff --git a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/helpers/IdentitySet.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/IdentitySet.kt similarity index 93% rename from cpg-core/src/main/java/de/fraunhofer/aisec/cpg/helpers/IdentitySet.kt rename to cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/IdentitySet.kt index 68cfe654c4..da1aa76f43 100644 --- a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/helpers/IdentitySet.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/IdentitySet.kt @@ -43,7 +43,7 @@ import java.util.concurrent.atomic.AtomicInteger * be very resource-intensive if nodes are very similar but not the *same*, in a work-list however * we only want just to avoid to place the exact node twice. */ -class IdentitySet : MutableSet { +class IdentitySet : MutableSet { /** * The backing hashmap for our set. The [IdentityHashMap] offers reference-equality for keys and * values. In this case we use it to determine, if a node is already in our set or not. The @@ -60,7 +60,8 @@ class IdentitySet : MutableSet { } override fun equals(other: Any?): Boolean { - val otherSet = other as? Set + if (other !is IdentitySet<*>) return false + val otherSet = other as? IdentitySet<*> return otherSet != null && this.containsAll(otherSet) && otherSet.containsAll(this) } @@ -133,6 +134,15 @@ class IdentitySet : MutableSet { throw UnsupportedOperationException() } + override fun hashCode() = map.hashCode() + override val size: Int get() = map.size } + +fun identitySetOf(vararg elements: T): IdentitySet { + val set = IdentitySet() + for (element in elements) set.add(element) + + return set +} diff --git a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/helpers/MeasurementHolder.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/MeasurementHolder.kt similarity index 100% rename from cpg-core/src/main/java/de/fraunhofer/aisec/cpg/helpers/MeasurementHolder.kt rename to cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/MeasurementHolder.kt diff --git a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/helpers/SubgraphWalker.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/SubgraphWalker.kt similarity index 94% rename from cpg-core/src/main/java/de/fraunhofer/aisec/cpg/helpers/SubgraphWalker.kt rename to cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/SubgraphWalker.kt index ea345affdd..79db52911e 100644 --- a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/helpers/SubgraphWalker.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/SubgraphWalker.kt @@ -38,7 +38,6 @@ import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.checkForPropertyEdge import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.unwrap import de.fraunhofer.aisec.cpg.graph.statements.CompoundStatement -import de.fraunhofer.aisec.cpg.helpers.Util.reverse import de.fraunhofer.aisec.cpg.processing.IVisitor import de.fraunhofer.aisec.cpg.processing.strategy.Strategy import java.lang.annotation.AnnotationFormatError @@ -255,11 +254,11 @@ object SubgraphWalker { fun refreshType(node: Node) { // Using a visitor to avoid loops in the AST node.accept( - { x: Node? -> Strategy.AST_FORWARD(x!!) }, - object : IVisitor() { - override fun visit(child: Node) { - if (child is HasType) { - (child as HasType).refreshType() + Strategy::AST_FORWARD, + object : IVisitor() { + override fun visit(t: Node) { + if (t is HasType) { + (t as HasType).refreshType() } } } @@ -334,8 +333,7 @@ object SubgraphWalker { onScopeExit.forEach(Consumer { c: Consumer -> c.accept(exiting) }) } else { // re-place the current node as a marker for the above check to find out when we - // need to - // exit a scope + // need to exit a scope (todo as ArrayDeque>).push(Pair(current, parent)) onNodeVisit.forEach(Consumer { c: Consumer -> c.accept(current) }) onNodeVisit2.forEach( @@ -347,7 +345,7 @@ object SubgraphWalker { .filter(Predicate.not { o: Node -> seen.contains(o) }) .collect(Collectors.toList()) seen.addAll(unseenChildren) - reverse(unseenChildren.stream()).forEach { child: Node -> + unseenChildren.asReversed().forEach { child: Node -> (todo as ArrayDeque>).push(Pair(child, current)) } (backlog as ArrayDeque).push(current) @@ -408,18 +406,18 @@ object SubgraphWalker { * Callback function(s) getting three arguments: the type of the class we're currently in, * the root node of the current declaration scope, the currently visited node. */ - private val handlers: MutableList> = ArrayList() + private val handlers = mutableListOf>() fun clearCallbacks() { handlers.clear() } - fun registerHandler(handler: TriConsumer) { + fun registerHandler(handler: TriConsumer) { handlers.add(handler) } - fun registerHandler(handler: BiConsumer) { + fun registerHandler(handler: BiConsumer) { handlers.add( - TriConsumer { currClass: RecordDeclaration?, _: Node?, currNode: Node -> + TriConsumer { currClass: RecordDeclaration?, _: Node?, currNode: Node? -> handler.accept(currNode, currClass) } ) @@ -432,18 +430,14 @@ object SubgraphWalker { */ fun iterate(root: Node) { walker = IterativeGraphWalker() - handlers.forEach( - Consumer { h: TriConsumer -> - walker!!.registerOnNodeVisit { n: Node -> handleNode(n, h) } - } - ) + handlers.forEach { h -> walker!!.registerOnNodeVisit { n -> handleNode(n, h) } } walker!!.registerOnScopeExit { exiting: Node -> leaveScope(exiting) } walker!!.iterate(root) } private fun handleNode( current: Node, - handler: TriConsumer + handler: TriConsumer ) { scopeManager.enterScopeIfExists(current) val parent = walker!!.backlog!!.peek() @@ -457,7 +451,9 @@ object SubgraphWalker { scopeManager.leaveScope(exiting) } - fun collectDeclarations(current: Node) { + fun collectDeclarations(current: Node?) { + if (current == null) return + var parentBlock: Node? = null // get containing Record or Compound @@ -500,10 +496,7 @@ object SubgraphWalker { var currentScope = scope // iterate all declarations from the current scope and all its parent scopes - while ( - currentScope != null && - nodeToParentBlockAndContainedValueDeclarations.containsKey(scope) - ) { + while (nodeToParentBlockAndContainedValueDeclarations.containsKey(scope)) { val entry = nodeToParentBlockAndContainedValueDeclarations[currentScope]!! for (`val` in entry.right) { if (predicate.test(`val`)) { diff --git a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/helpers/ShutDownException.java b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/TriConsumer.kt similarity index 89% rename from cpg-core/src/main/java/de/fraunhofer/aisec/cpg/helpers/ShutDownException.java rename to cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/TriConsumer.kt index 10ebfb3359..86cb6455f0 100644 --- a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/helpers/ShutDownException.java +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/TriConsumer.kt @@ -23,6 +23,8 @@ * \______/ \__| \______/ * */ -package de.fraunhofer.aisec.cpg.helpers; +package de.fraunhofer.aisec.cpg.helpers -public class ShutDownException extends Exception {} +fun interface TriConsumer { + fun accept(first: A, second: B, third: C) +} diff --git a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/helpers/Util.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/Util.kt similarity index 90% rename from cpg-core/src/main/java/de/fraunhofer/aisec/cpg/helpers/Util.kt rename to cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/Util.kt index 5bfbd2c075..67ba6dcfd6 100644 --- a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/helpers/Util.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/Util.kt @@ -25,7 +25,6 @@ */ package de.fraunhofer.aisec.cpg.helpers -import de.fraunhofer.aisec.cpg.frontends.Language import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration @@ -40,8 +39,6 @@ import java.nio.charset.StandardCharsets import java.util.* import java.util.function.Function import java.util.function.Predicate -import java.util.stream.IntStream -import java.util.stream.Stream import org.slf4j.Logger object Util { @@ -257,9 +254,9 @@ object Util { currPart.append(c) } else if (delimiters.contains("" + c)) { if (openParentheses == 0) { - val toAdd = currPart.toString().strip() + val toAdd = currPart.toString().trim() if (toAdd.isNotEmpty()) { - result.add(currPart.toString().strip()) + result.add(currPart.toString().trim()) } currPart = StringBuilder() } else { @@ -270,7 +267,7 @@ object Util { } } if (currPart.isNotEmpty()) { - result.add(currPart.toString().strip()) + result.add(currPart.toString().trim()) } return result } @@ -287,9 +284,8 @@ object Util { val result = original.toCharArray() val marker = '\uffff' val openingParentheses: Deque = ArrayDeque() - for (i in 0 until original.length) { - val c = original[i] - when (c) { + for (i in original.indices) { + when (original[i]) { '(' -> openingParentheses.push(i) ')' -> { val matching = openingParentheses.pollFirst() @@ -333,7 +329,7 @@ object Util { * @param target The call's target [FunctionDeclaration] * @param arguments The call's arguments to be connected to the target's parameters */ - fun attachCallParameters(target: FunctionDeclaration, arguments: List) { + fun attachCallParameters(target: FunctionDeclaration, arguments: List) { target.parameterEdges.sortWith(Comparator.comparing { it.end.argumentIndex }) var j = 0 while (j < arguments.size) { @@ -344,7 +340,7 @@ object Util { while (j < arguments.size) { // map all the following arguments to this variadic param - param.addPrevDFG(arguments[j]!!) + param.addPrevDFG(arguments[j]) j++ } break @@ -356,32 +352,8 @@ object Util { } } - // TODO(oxisto): Remove at some point and directly use name class - fun getSimpleName(language: Language?, name: String): String { - var name = name - if (language != null) { - val delimiter = language.namespaceDelimiter - if (name.contains(delimiter)) { - name = name.substring(name.lastIndexOf(delimiter) + delimiter.length) - } - } - return name - } - - // TODO(oxisto): Remove at some point and directly use name class - fun getParentName(language: Language?, name: String): String { - var name = name - if (language != null) { - val delimiter = language.namespaceDelimiter - if (delimiter in name) { - name = name.substring(0, name.lastIndexOf(delimiter)) - } - } - return name - } - /** - * Inverse operation of [.attachCallParameters] + * Inverse operation of [attachCallParameters] * * @param target * @param arguments @@ -393,12 +365,6 @@ object Util { } } - @JvmStatic - fun reverse(input: Stream): Stream { - val temp = input.toArray() - return IntStream.range(0, temp.size).mapToObj { i -> temp[temp.size - i - 1] } as Stream - } - /** * This function returns the set of adjacent DFG nodes that is contained in the nodes subgraph. * diff --git a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/helpers/annotations/FunctionReplacement.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/annotations/FunctionReplacement.kt similarity index 100% rename from cpg-core/src/main/java/de/fraunhofer/aisec/cpg/helpers/annotations/FunctionReplacement.kt rename to cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/annotations/FunctionReplacement.kt diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/neo4j/LocationConverter.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/neo4j/LocationConverter.kt new file mode 100644 index 0000000000..97530c9f44 --- /dev/null +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/neo4j/LocationConverter.kt @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2019, 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.helpers.neo4j + +import de.fraunhofer.aisec.cpg.sarif.PhysicalLocation +import de.fraunhofer.aisec.cpg.sarif.Region +import java.net.URI +import org.neo4j.ogm.typeconversion.CompositeAttributeConverter + +/** + * This class converts a [PhysicalLocation] into the the necessary composite attributes when + * persisting a node into a Neo4J graph database. + */ +class LocationConverter : CompositeAttributeConverter { + override fun toGraphProperties(value: PhysicalLocation?): Map { + val properties: MutableMap = HashMap() + if (value != null) { + properties[ARTIFACT] = value.artifactLocation.uri.toString() + properties[START_LINE] = value.region.startLine + properties[END_LINE] = value.region.endLine + properties[START_COLUMN] = value.region.startColumn + properties[END_COLUMN] = value.region.endColumn + } + return properties + } + + override fun toEntityAttribute(value: Map?): PhysicalLocation? { + return try { + val startLine = toInt(value?.get(START_LINE)) ?: return null + val endLine = toInt(value?.get(END_LINE)) ?: return null + val startColumn = toInt(value?.get(START_COLUMN)) ?: return null + val endColumn = toInt(value?.get(END_COLUMN)) ?: return null + val uri = URI.create(value?.get(ARTIFACT) as? String ?: "") + PhysicalLocation(uri, Region(startLine, startColumn, endLine, endColumn)) + } catch (e: NullPointerException) { + null + } + } + + private fun toInt(objectToMap: Any?): Int? { + val value = objectToMap?.toString()?.toLong() ?: return null + return Math.toIntExact(value) + } + + companion object { + const val START_LINE = "startLine" + const val END_LINE = "endLine" + const val START_COLUMN = "startColumn" + const val END_COLUMN = "endColumn" + const val ARTIFACT = "artifact" + } +} diff --git a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/helpers/NameConverter.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/neo4j/NameConverter.kt similarity index 98% rename from cpg-core/src/main/java/de/fraunhofer/aisec/cpg/helpers/NameConverter.kt rename to cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/neo4j/NameConverter.kt index 458074b04c..3c7b16e607 100644 --- a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/helpers/NameConverter.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/neo4j/NameConverter.kt @@ -23,7 +23,7 @@ * \______/ \__| \______/ * */ -package de.fraunhofer.aisec.cpg.helpers +package de.fraunhofer.aisec.cpg.helpers.neo4j import de.fraunhofer.aisec.cpg.graph.Name import de.fraunhofer.aisec.cpg.graph.parseName diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CXXCallResolverHelper.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CXXCallResolverHelper.kt index 611162f73a..52a1fea81e 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CXXCallResolverHelper.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CXXCallResolverHelper.kt @@ -332,7 +332,7 @@ fun applyTemplateInstantiation( val templateInstantiationParameters = mutableListOf(*orderedInitializationSignature.keys.toTypedArray()) for ((key, value) in orderedInitializationSignature) { - templateInstantiationParameters[value] = initializationSignature[key]!! + initializationSignature[key]?.let { templateInstantiationParameters[value] = it } } templateCall.templateInstantiation = functionTemplateDeclaration diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolver.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolver.kt index 2858265036..91d75fa76b 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolver.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolver.kt @@ -103,14 +103,14 @@ open class CallResolver : SymbolResolverPass() { } } - private fun registerMethods(currentClass: RecordDeclaration?, currentNode: Node) { + private fun registerMethods(currentClass: RecordDeclaration?, currentNode: Node?) { if (currentNode is MethodDeclaration && currentClass != null) { containingType[currentNode] = TypeParser.createFrom(currentClass.name, currentClass.language) } } - private fun fixInitializers(node: Node) { + private fun fixInitializers(node: Node?) { if (node is VariableDeclaration) { // check if we have the corresponding class for this type val typeString = node.type.root.name @@ -142,7 +142,7 @@ open class CallResolver : SymbolResolverPass() { } } - protected fun handleNode(node: Node) { + protected fun handleNode(node: Node?) { when (node) { is TranslationUnitDeclaration -> { currentTU = node @@ -392,7 +392,7 @@ open class CallResolver : SymbolResolverPass() { if (record == null && config?.inferenceConfiguration?.inferRecords == true) { record = it.startInference().inferRecordDeclaration(it, currentTU) // update the record map - if (record != null) recordMap[it.root.name] = record + if (record != null) it.root.name.let { name -> recordMap[name] = record } } record } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt index 01a0360ae2..86fd9e8cd3 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt @@ -38,7 +38,6 @@ import de.fraunhofer.aisec.cpg.graph.scopes.* import de.fraunhofer.aisec.cpg.graph.statements.* import de.fraunhofer.aisec.cpg.graph.statements.expressions.* import de.fraunhofer.aisec.cpg.graph.types.Type -import de.fraunhofer.aisec.cpg.graph.types.TypeParser import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker import de.fraunhofer.aisec.cpg.helpers.Util import de.fraunhofer.aisec.cpg.passes.order.DependsOn @@ -90,7 +89,7 @@ open class EvaluationOrderGraphPass : Pass() { private val intermediateNodes = mutableListOf() init { - map[IncludeDeclaration::class.java] = { doNothing(it) } + map[IncludeDeclaration::class.java] = { doNothing() } map[TranslationUnitDeclaration::class.java] = { handleTranslationUnitDeclaration(it as TranslationUnitDeclaration) } @@ -164,7 +163,7 @@ open class EvaluationOrderGraphPass : Pass() { map[LambdaExpression::class.java] = { handleLambdaExpression(it as LambdaExpression) } } - private fun doNothing(node: Node) { + private fun doNothing() { // Nothing to do for this node type } @@ -534,38 +533,14 @@ open class EvaluationOrderGraphPass : Pass() { protected fun handleUnaryOperator(node: UnaryOperator) { val input = node.input - if (input != null) { - createEOG(input) - } + createEOG(input) if (node.operatorCode == "throw") { val catchingScope = scopeManager.firstScopeOrNull { scope -> scope is TryScope || scope is FunctionScope } - val throwType = - if (input != null) { - input.type - } else { - // do not check via instanceof, since we do not want to allow subclasses of - // DeclarationScope here - val decl = - scopeManager.firstScopeOrNull { scope -> - scope.javaClass == ValueDeclarationScope::class.java - } - - if ( - decl != null && - decl.astNode is CatchClause && - (decl.astNode as CatchClause?)!!.parameter != null - ) { - val param = (decl.astNode as CatchClause?)!!.parameter!! - param.type - } else { - LOGGER.info("Unknown throw type, potentially throw; in a method") - TypeParser.createFrom("UNKNOWN_THROW_TYPE", node.language) - } - } + val throwType = input.type pushToEOG(node) if (catchingScope is TryScope) { catchingScope.catchesOrRelays[throwType] = ArrayList(currentPredecessors) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/FilenameMapper.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/FilenameMapper.kt index 3b4e6dd07a..25936f6bd9 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/FilenameMapper.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/FilenameMapper.kt @@ -44,10 +44,10 @@ class FilenameMapper : Pass() { private fun handle(node: Node, file: String) { // Using a visitor to avoid loops in the AST node.accept( - { Strategy.AST_FORWARD(it) }, - object : IVisitor() { - override fun visit(child: Node) { - child.file = file + Strategy::AST_FORWARD, + object : IVisitor() { + override fun visit(t: Node) { + t.file = file } } ) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/FunctionPointerCallResolver.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/FunctionPointerCallResolver.kt index 226bc77caa..3660bb239b 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/FunctionPointerCallResolver.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/FunctionPointerCallResolver.kt @@ -70,7 +70,7 @@ class FunctionPointerCallResolver : Pass() { scopeManager = t.scopeManager inferDfgForUnresolvedCalls = t.config.inferenceConfiguration.inferDfgForUnresolvedSymbols walker = ScopedWalker(t.scopeManager) - walker.registerHandler { _: RecordDeclaration?, _: Node?, currNode: Node -> + walker.registerHandler { _: RecordDeclaration?, _: Node?, currNode: Node? -> walker.collectDeclarations(currNode) } walker.registerHandler { node, _ -> resolve(node) } 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 4d3afeb017..423b654bce 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 @@ -155,14 +155,14 @@ open class ImportResolver : Pass() { protected fun findImportables(node: Node) { // Using a visitor to avoid loops in the AST node.accept( - { Strategy.AST_FORWARD(it) }, - object : IVisitor() { - override fun visit(child: Node) { - if (child is RecordDeclaration) { - records.add(child) - importables.putIfAbsent(child.name.toString(), child) - } else if (child is EnumDeclaration) { - importables.putIfAbsent(child.name.toString(), child) + Strategy::AST_FORWARD, + object : IVisitor() { + override fun visit(t: Node) { + if (t is RecordDeclaration) { + records.add(t) + importables.putIfAbsent(t.name.toString(), t) + } else if (t is EnumDeclaration) { + importables.putIfAbsent(t.name.toString(), t) } } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/Pass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/Pass.kt index f0abbde00b..7e0ec9ca50 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/Pass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/Pass.kt @@ -28,7 +28,6 @@ package de.fraunhofer.aisec.cpg.passes import de.fraunhofer.aisec.cpg.ScopeManager import de.fraunhofer.aisec.cpg.TranslationConfiguration import de.fraunhofer.aisec.cpg.TranslationResult -import de.fraunhofer.aisec.cpg.frontends.Language import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend import de.fraunhofer.aisec.cpg.passes.order.* import java.util.function.Consumer @@ -93,17 +92,6 @@ abstract class Pass protected constructor() : Consumer { abstract fun cleanup() - /** - * Specifies, whether this pass supports this particular language. This defaults to `true ` * - * and needs to be overridden if a different behaviour is wanted. - * - * @param language the language - * @return truw by default - */ - fun supportsLanguage(language: Language): Boolean { - return true - } - val isLastPass: Boolean get() = try { @@ -111,6 +99,7 @@ abstract class Pass protected constructor() : Consumer { } catch (e: Exception) { false } + val isFirstPass: Boolean get() = try { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/SymbolResolverPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/SymbolResolverPass.kt index b1db052099..273e39d253 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/SymbolResolverPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/SymbolResolverPass.kt @@ -42,14 +42,14 @@ abstract class SymbolResolverPass : Pass() { protected val superTypesMap = mutableMapOf>() /** Maps the name of the type of record declarations to its declaration. */ - protected fun findRecords(node: Node) { + protected fun findRecords(node: Node?) { if (node is RecordDeclaration) { recordMap.putIfAbsent(node.name, node) } } /** Maps the type of enums to its declaration. */ - protected fun findEnums(node: Node) { + protected fun findEnums(node: Node?) { if (node is EnumDeclaration) { // TODO: Use the name instead of the type. val type = TypeParser.createFrom(node.name, node.language) @@ -58,7 +58,7 @@ abstract class SymbolResolverPass : Pass() { } /** Caches all TemplateDeclarations in [templateList] */ - protected fun findTemplates(node: Node) { + protected fun findTemplates(node: Node?) { if (node is TemplateDeclaration) { templateList.add(node) } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TemplateCallResolverHelper.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TemplateCallResolverHelper.kt index 4cd9b87a33..0ae6d95876 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TemplateCallResolverHelper.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TemplateCallResolverHelper.kt @@ -132,10 +132,9 @@ fun applyMissingParams( ) // If template argument is a type add it as a generic to the type as well if (templateParametersExplicitInitialization[missingParam] is TypeExpression) { - (constructExpression.type as ObjectType).addGeneric( - (templateParametersExplicitInitialization[missingParam] as TypeExpression?) - ?.type - ) + (templateParametersExplicitInitialization[missingParam] as? TypeExpression) + ?.type + ?.let { (constructExpression.type as ObjectType).addGeneric(it) } } } else if (missingParam in templateParameterRealDefaultInitialization) { // Add default of template parameter to construct declaration @@ -144,10 +143,9 @@ fun applyMissingParams( TemplateDeclaration.TemplateInitialization.DEFAULT ) if (templateParametersExplicitInitialization[missingParam] is Type) { - (constructExpression.type as ObjectType).addGeneric( - (templateParametersExplicitInitialization[missingParam] as TypeExpression?) - ?.type - ) + (templateParametersExplicitInitialization[missingParam] as? TypeExpression) + ?.type + ?.let { (constructExpression.type as ObjectType).addGeneric(it) } } } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TypeHierarchyResolver.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TypeHierarchyResolver.kt index b42f1cd88c..771f1c2b27 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TypeHierarchyResolver.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TypeHierarchyResolver.kt @@ -81,13 +81,13 @@ open class TypeHierarchyResolver : Pass() { protected fun findRecordsAndEnums(node: Node) { // Using a visitor to avoid loops in the AST node.accept( - { Strategy.AST_FORWARD(it) }, - object : IVisitor() { - override fun visit(child: Node) { - if (child is RecordDeclaration) { - recordMap.putIfAbsent(child.name, child) - } else if (child is EnumDeclaration) { - enums.add(child) + Strategy::AST_FORWARD, + object : IVisitor() { + override fun visit(t: Node) { + if (t is RecordDeclaration) { + recordMap.putIfAbsent(t.name, t) + } else if (t is EnumDeclaration) { + enums.add(t) } } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/VariableUsageResolver.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/VariableUsageResolver.kt index 25ab6b1e2a..7aaa3d4515 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/VariableUsageResolver.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/VariableUsageResolver.kt @@ -110,9 +110,9 @@ open class VariableUsageResolver : SymbolResolverPass() { private fun resolveLocalVarUsage( currentClass: RecordDeclaration?, parent: Node?, - current: Node + current: Node? ) { - val language = current.language + val language = current?.language if (current !is DeclaredReferenceExpression || current is MemberExpression) return @@ -182,15 +182,15 @@ open class VariableUsageResolver : SymbolResolverPass() { private fun getEnclosingTypeOf(current: Node): Type { val language = current.language - if (language != null && language.namespaceDelimiter.isNotEmpty()) { + return if (language != null && language.namespaceDelimiter.isNotEmpty()) { val parentName = (current.name.parent ?: current.name).toString() - return TypeParser.createFrom(parentName, language) + TypeParser.createFrom(parentName, language) } else { - return UnknownType.getUnknownType() + current.language.newUnknownType() } } - private fun resolveFieldUsages(curClass: RecordDeclaration?, parent: Node?, current: Node) { + private fun resolveFieldUsages(curClass: RecordDeclaration?, parent: Node?, current: Node?) { if (current !is MemberExpression) { return } diff --git a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/processing/IStrategy.java b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/processing/IStrategy.kt similarity index 78% rename from cpg-core/src/main/java/de/fraunhofer/aisec/cpg/processing/IStrategy.java rename to cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/processing/IStrategy.kt index e950da73aa..54641d638f 100644 --- a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/processing/IStrategy.java +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/processing/IStrategy.kt @@ -23,20 +23,15 @@ * \______/ \__| \______/ * */ -package de.fraunhofer.aisec.cpg.processing; - -import java.util.Iterator; -import org.jetbrains.annotations.NotNull; +package de.fraunhofer.aisec.cpg.processing /** * The strategy determines the order in which nodes in the structure are traversed. * - *

For each node, the strategy returns a non-null but possibly empty iterator over the - * successors. + * For each node, the strategy returns a non-null but possibly empty iterator over the successors. * - * @param + * @param */ -public interface IStrategy { - @NotNull - Iterator getIterator(V v); +fun interface IStrategy { + fun getIterator(v: V): Iterator } diff --git a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/processing/IVisitable.java b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/processing/IVisitable.kt similarity index 65% rename from cpg-core/src/main/java/de/fraunhofer/aisec/cpg/processing/IVisitable.java rename to cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/processing/IVisitable.kt index 47943e2fe5..d52081d60e 100644 --- a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/processing/IVisitable.java +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/processing/IVisitable.kt @@ -23,29 +23,26 @@ * \______/ \__| \______/ * */ -package de.fraunhofer.aisec.cpg.processing; - -import java.util.Iterator; -import org.jetbrains.annotations.NotNull; +package de.fraunhofer.aisec.cpg.processing /** * An object that can be visited by a visitor. * - * @param + * @param */ -public interface IVisitable { - - /** - * @param strategy Traversal strategy. - * @param visitor Instance of the visitor to call. - */ - default void accept(IStrategy strategy, IVisitor visitor) { - if (visitor.getVisited().add((V) this)) { - visitor.visit((V) this); - @NotNull Iterator it = strategy.getIterator((V) this); - while (it.hasNext()) { - it.next().accept(strategy, visitor); - } +interface IVisitable> { + /** + * @param strategy Traversal strategy. + * @param visitor Instance of the visitor to call. + */ + fun accept(strategy: IStrategy, visitor: IVisitor) { + @Suppress("UNCHECKED_CAST") + if (visitor.visited.add(this as V)) { + visitor.visit(this) + val it = strategy.getIterator(this) + while (it.hasNext()) { + it.next().accept(strategy, visitor) + } + } } - } } diff --git a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/processing/IVisitor.java b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/processing/IVisitor.kt similarity index 59% rename from cpg-core/src/main/java/de/fraunhofer/aisec/cpg/processing/IVisitor.java rename to cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/processing/IVisitor.kt index 7550eed873..2842c6f3fb 100644 --- a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/processing/IVisitor.java +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/processing/IVisitor.kt @@ -23,33 +23,25 @@ * \______/ \__| \______/ * */ -package de.fraunhofer.aisec.cpg.processing; +package de.fraunhofer.aisec.cpg.processing -import de.fraunhofer.aisec.cpg.helpers.IdentitySet; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import org.jetbrains.annotations.NotNull; +import de.fraunhofer.aisec.cpg.helpers.IdentitySet +import java.lang.reflect.InvocationTargetException /** * Reflective visitor that visits the most specific implementation of visit() methods. * - * @param V must implement {@code IVisitable}. + * @param V must implement `IVisitable`. */ -public abstract class IVisitor { - private final IdentitySet visited = new IdentitySet<>(); - - public IdentitySet getVisited() { - return visited; - } - - public void visit(@NotNull V t) { - try { - Method mostSpecificVisit = this.getClass().getMethod("visit", t.getClass()); - - mostSpecificVisit.setAccessible(true); - mostSpecificVisit.invoke(this, t); - } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { - // Nothing to do here +abstract class IVisitor> { + @JvmField val visited = IdentitySet() + open fun visit(t: V) { + try { + val mostSpecificVisit = this.javaClass.getMethod("visit", t::class.java) + mostSpecificVisit.isAccessible = true + mostSpecificVisit.invoke(this, t) + } catch (e: NoSuchMethodException) { + // Nothing to do here + } catch (e: InvocationTargetException) {} catch (e: IllegalAccessException) {} } - } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/processing/strategy/Strategy.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/processing/strategy/Strategy.kt new file mode 100644 index 0000000000..e8f73db852 --- /dev/null +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/processing/strategy/Strategy.kt @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2021, 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.processing.strategy + +import de.fraunhofer.aisec.cpg.graph.Node +import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker.getAstChildren +import java.util.* + +/** Strategies (iterators) for traversing graphs to be used by visitors. */ +object Strategy { + /** + * Do not traverse any nodes. + * + * @param x + * @return + */ + fun NO_STRATEGY(x: Node): Iterator { + return Collections.emptyIterator() + } + + /** + * Traverse Evaluation Order Graph in forward direction. + * + * @param x Current node in EOG. + * @return Iterator over successors. + */ + fun EOG_FORWARD(x: Node): Iterator { + return x.nextEOG.iterator() + } + + /** + * Traverse Evaluation Order Graph in backward direction. + * + * @param x Current node in EOG. + * @return Iterator over successors. + */ + fun EOG_BACKWARD(x: Node): Iterator { + return x.prevEOG.iterator() + } + + /** + * Traverse Data Flow Graph in forward direction. + * + * @param x Current node in DFG. + * @return Iterator over successors. + */ + fun DFG_FORWARD(x: Node): Iterator { + return x.nextDFG.iterator() + } + + /** + * Traverse Data Flow Graph in backward direction. + * + * @param x Current node in DFG. + * @return Iterator over successors. + */ + fun DFG_BACKWARD(x: Node): Iterator { + return x.prevDFG.iterator() + } + + /** + * Traverse AST in forward direction. + * + * @param x + * @return + */ + fun AST_FORWARD(x: Node): Iterator { + return getAstChildren(x).iterator() + } +} diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/sarif/PhysicalLocation.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/sarif/PhysicalLocation.kt new file mode 100644 index 0000000000..3fca276c8f --- /dev/null +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/sarif/PhysicalLocation.kt @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2019, 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.sarif + +import java.net.URI +import java.util.* + +/** A SARIF compatible location referring to a location, i.e. file and region within the file. */ +class PhysicalLocation(uri: URI, region: Region) { + class ArtifactLocation(val uri: URI) { + + override fun toString(): String { + return uri.path.substring(uri.path.lastIndexOf('/') + 1) + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is ArtifactLocation) return false + return uri == other.uri + } + + override fun hashCode() = Objects.hashCode(uri) + } + + val artifactLocation: ArtifactLocation + var region: Region + + init { + artifactLocation = ArtifactLocation(uri) + this.region = region + } + + override fun toString(): String { + return "$artifactLocation($region)" + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is PhysicalLocation) return false + return artifactLocation == other.artifactLocation && region == other.region + } + + override fun hashCode() = Objects.hash(artifactLocation, region) + + companion object { + fun locationLink(location: PhysicalLocation?): String { + return if (location != null) { + (location.artifactLocation.uri.path + + ":" + + location.region.startLine + + ":" + + location.region.startColumn) + } else "unknown" + } + } +} diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/sarif/Region.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/sarif/Region.kt new file mode 100644 index 0000000000..563b4d6fa6 --- /dev/null +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/sarif/Region.kt @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2019, 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.sarif + +import java.util.* + +/** Code source location, in a SASP/SARIF-compliant "Region" format. */ +class Region +constructor( + @JvmField var startLine: Int = -1, + @JvmField var startColumn: Int = -1, + var endLine: Int = -1, + var endColumn: Int = -1 +) : Comparable { + + override fun toString(): String { + return "$startLine:$startColumn-$endLine:$endColumn" + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is Region) return false + return startLine == other.startLine && + startColumn == other.startColumn && + endLine == other.endLine && + endColumn == other.endColumn + } + + override fun compareTo(other: Region): Int { + var comparisonValue: Int + if (startLine.compareTo(other.startLine).also { comparisonValue = it } != 0) + return comparisonValue + if (startColumn.compareTo(other.startColumn).also { comparisonValue = it } != 0) + return comparisonValue + if (endLine.compareTo(other.endLine).also { comparisonValue = it } != 0) + return -comparisonValue + return if (endColumn.compareTo(other.endColumn).also { comparisonValue = it } != 0) + -comparisonValue + else comparisonValue + } + + override fun hashCode() = Objects.hash(startColumn, startLine, endColumn, endLine) +} diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/templates/FunctionTemplateTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/templates/FunctionTemplateTest.kt index 2a195f3255..bc988cadd3 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/templates/FunctionTemplateTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/templates/FunctionTemplateTest.kt @@ -57,16 +57,16 @@ internal class FunctionTemplateTest : BaseTest() { ) val variableDeclarations = result.variables val x = findByUniqueName(variableDeclarations, "x") - assertEquals(UnknownType.getUnknownType(), x.type) + assertEquals(UnknownType.getUnknownType(CPPLanguage()), x.type) val declaredReferenceExpressions = result.refs val xDeclaredReferenceExpression = findByUniqueName(declaredReferenceExpressions, "x") - assertEquals(UnknownType.getUnknownType(), xDeclaredReferenceExpression.type) + assertEquals(UnknownType.getUnknownType(CPPLanguage()), xDeclaredReferenceExpression.type) val binaryOperators = result.allChildren() val dependentOperation = findByUniquePredicate(binaryOperators) { b: BinaryOperator -> b.code == "val * N" } - assertEquals(UnknownType.getUnknownType(), dependentOperation.type) + assertEquals(UnknownType.getUnknownType(CPPLanguage()), dependentOperation.type) } private fun testFunctionTemplateArguments( @@ -583,7 +583,7 @@ internal class FunctionTemplateTest : BaseTest() { ) // Check return values - assertEquals(UnknownType.getUnknownType(), callInt2.type) - assertEquals(UnknownType.getUnknownType(), callDouble3.type) + assertEquals(UnknownType.getUnknownType(CPPLanguage()), callInt2.type) + assertEquals(UnknownType.getUnknownType(CPPLanguage()), callDouble3.type) } } diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CXXLanguageFrontendTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CXXLanguageFrontendTest.kt index b17c1f1b74..55e606dfc9 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CXXLanguageFrontendTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CXXLanguageFrontendTest.kt @@ -1204,15 +1204,14 @@ internal class CXXLanguageFrontendTest : BaseTest() { assertNotNull(returnStatement) // we need to assert, that we have a consistent chain of EOG edges from the first statement - // to - // the return statement. otherwise, the EOG chain is somehow broken + // to the return statement. otherwise, the EOG chain is somehow broken val eogEdges = ArrayList() main.accept( - { x: Node? -> Strategy.EOG_FORWARD(x!!) }, + Strategy::EOG_FORWARD, object : IVisitor() { - override fun visit(n: Node) { - println(n) - eogEdges.add(n) + override fun visit(t: Node) { + println(t) + eogEdges.add(t) } } ) @@ -1290,7 +1289,7 @@ internal class CXXLanguageFrontendTest : BaseTest() { fun testEnum() { val file = File("src/test/resources/c/enum.c") val tu = analyzeAndGetFirstTU(listOf(file), file.parentFile.toPath(), true) - // TU should only contains two AST declarations (EnumDeclaration and FunctionDeclaration), + // TU should only contain two AST declarations (EnumDeclaration and FunctionDeclaration), // but NOT any EnumConstantDeclarations assertEquals(2, tu.declarations.size) diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/types/TypeTests.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/types/TypeTests.kt index aed2585c2c..dbe059af6d 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/types/TypeTests.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/types/TypeTests.kt @@ -47,7 +47,7 @@ internal class TypeTests : BaseTest() { val parameterList = listOf(IntegerType("int", 32, CPPLanguage(), NumericType.Modifier.SIGNED)) val functionPointerType: Type = - FunctionPointerType(parameterList, IncompleteType(), CPPLanguage()) + FunctionPointerType(parameterList, CPPLanguage(), IncompleteType()) // Test 1: ObjectType becomes PointerType containing the original ObjectType as ElementType assertEquals( @@ -86,7 +86,7 @@ internal class TypeTests : BaseTest() { val parameterList = listOf(IntegerType("int", 32, CPPLanguage(), NumericType.Modifier.SIGNED)) val functionPointerType: Type = - FunctionPointerType(parameterList, IncompleteType(), CPPLanguage()) + FunctionPointerType(parameterList, CPPLanguage(), IncompleteType()) // Test 1: Dereferencing an ObjectType results in an UnknownType, since we cannot track the // type @@ -115,7 +115,7 @@ internal class TypeTests : BaseTest() { result = TypeParser.createFrom(typeString, CPPLanguage()) val parameterList = listOf(IntegerType("int", 32, CPPLanguage(), NumericType.Modifier.SIGNED)) - var expected: Type = FunctionPointerType(parameterList, IncompleteType(), CPPLanguage()) + var expected: Type = FunctionPointerType(parameterList, CPPLanguage(), IncompleteType()) assertEquals(expected, result) // Test 1.1: interleaved brackets in function pointer @@ -218,7 +218,7 @@ internal class TypeTests : BaseTest() { // Test 8: Generics typeString = "Array array" result = TypeParser.createFrom(typeString, CPPLanguage()) - var generics: MutableList = ArrayList() + var generics = mutableListOf() generics.add(IntegerType("int", 32, CPPLanguage(), NumericType.Modifier.SIGNED)) expected = ObjectType("Array", generics, false, CPPLanguage()) assertEquals(expected, result) @@ -303,12 +303,12 @@ internal class TypeTests : BaseTest() { val topLevel = Path.of("src", "test", "resources", "types") val tu = analyzeAndGetFirstTU(listOf(topLevel.resolve("fptr_type.cpp").toFile()), topLevel, true) - val noParamType = FunctionPointerType(emptyList(), IncompleteType(), CPPLanguage()) + val noParamType = FunctionPointerType(emptyList(), CPPLanguage(), IncompleteType()) val oneParamType = FunctionPointerType( listOf(IntegerType("int", 32, CPPLanguage(), NumericType.Modifier.SIGNED)), - IncompleteType(), - CPPLanguage() + CPPLanguage(), + IncompleteType() ) val twoParamType = FunctionPointerType( @@ -316,8 +316,8 @@ internal class TypeTests : BaseTest() { IntegerType("int", 32, CPPLanguage(), NumericType.Modifier.SIGNED), IntegerType("unsigned long", 64, CPPLanguage(), NumericType.Modifier.UNSIGNED) ), - IntegerType("int", 32, CPPLanguage(), NumericType.Modifier.SIGNED), - CPPLanguage() + CPPLanguage(), + IntegerType("int", 32, CPPLanguage(), NumericType.Modifier.SIGNED) ) val variables = tu.variables val localTwoParam = findByUniqueName(variables, "local_two_param") diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/helpers/IdentitySetTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/helpers/IdentitySetTest.kt index f74f302714..84b1a582fb 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/helpers/IdentitySetTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/helpers/IdentitySetTest.kt @@ -25,11 +25,7 @@ */ package de.fraunhofer.aisec.cpg.helpers -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFalse -import kotlin.test.assertNotEquals -import kotlin.test.assertTrue +import kotlin.test.* class IdentitySetTest { @Test @@ -66,15 +62,15 @@ class IdentitySetTest { set.add(1) set.add(2) - assertTrue(set.equals(setOf(1, 2))) - assertFalse(set.equals(setOf(1, 2, 3))) - assertFalse(set.equals(setOf("1", "2", 3))) - assertFalse(set.equals(setOf(1))) + assertEquals(set, identitySetOf(1, 2)) + assertNotEquals(set, identitySetOf(1, 2, 3)) + assertFalse(set == identitySetOf("1", "2", 3)) + assertNotEquals(set, identitySetOf(1)) - assertEquals(setOf(1, 2), set) - assertNotEquals(setOf(1, 2, 3), set) - assertNotEquals(setOf(1), set) - assertNotEquals(setOf("1", "2", 3), set) + assertEquals(identitySetOf(1, 2), set) + assertNotEquals(identitySetOf(1, 2, 3), set) + assertNotEquals(identitySetOf(1), set) + assertFalse(set == identitySetOf("1", "2", 3)) } @Test diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/helpers/LocationConverterTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/helpers/LocationConverterTest.kt index a32846e139..7dad04981b 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/helpers/LocationConverterTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/helpers/LocationConverterTest.kt @@ -27,6 +27,7 @@ package de.fraunhofer.aisec.cpg.helpers import com.google.common.base.Objects import de.fraunhofer.aisec.cpg.BaseTest +import de.fraunhofer.aisec.cpg.helpers.neo4j.LocationConverter import de.fraunhofer.aisec.cpg.sarif.PhysicalLocation import de.fraunhofer.aisec.cpg.sarif.Region import java.net.URI @@ -37,7 +38,7 @@ import kotlin.test.assertNull import org.neo4j.ogm.typeconversion.CompositeAttributeConverter internal class LocationConverterTest : BaseTest() { - private val sut: CompositeAttributeConverter + private val sut: CompositeAttributeConverter get() { return LocationConverter() } @@ -45,7 +46,7 @@ internal class LocationConverterTest : BaseTest() { @Test fun toEntityAttributeWithNullValueLine() { // arrange - val sut: CompositeAttributeConverter = sut + val sut: CompositeAttributeConverter = sut // act val have = sut.toEntityAttribute(null) // assert @@ -55,7 +56,7 @@ internal class LocationConverterTest : BaseTest() { @Test fun toEntityAttributeWithNullStartLine() { // arrange - val sut: CompositeAttributeConverter = sut + val sut: CompositeAttributeConverter = sut val value = mutableMapOf() value[LocationConverter.START_LINE] = null // act @@ -67,7 +68,7 @@ internal class LocationConverterTest : BaseTest() { @Test fun toEntityAttributeWithInteger() { // arrange - val sut: CompositeAttributeConverter = sut + val sut: CompositeAttributeConverter = sut val value: MutableMap = HashMap() val startLineValue = 1 value[LocationConverter.START_LINE] = startLineValue // autoboxing to Integer @@ -88,7 +89,7 @@ internal class LocationConverterTest : BaseTest() { @Test fun toEntityAttributeWithNullGraph() { - val sut: CompositeAttributeConverter = sut + val sut: CompositeAttributeConverter = sut val have = sut.toGraphProperties(null) assertEquals(mapOf(), have) } @@ -96,7 +97,7 @@ internal class LocationConverterTest : BaseTest() { @Test fun toEntityAttributeWithIntegerGraph() { // arrange - val sut: CompositeAttributeConverter = sut + val sut: CompositeAttributeConverter = sut val value: MutableMap = HashMap() val startLineValue = 1 value[LocationConverter.START_LINE] = startLineValue // autoboxing to Integer @@ -118,7 +119,7 @@ internal class LocationConverterTest : BaseTest() { @Test fun toEntityAttributeWithLong() { // arrange - val sut: CompositeAttributeConverter = sut + val sut: CompositeAttributeConverter = sut val value: MutableMap = HashMap() val startLineValue: Long = 1 value[LocationConverter.START_LINE] = startLineValue // autoboxing to Long @@ -146,7 +147,7 @@ internal class LocationConverterTest : BaseTest() { @Test fun toEntityAttributeWithIntegerAndLong() { // arrange - val sut: CompositeAttributeConverter = sut + val sut: CompositeAttributeConverter = sut val value: MutableMap = HashMap() val startLineValue = 1 value[LocationConverter.START_LINE] = startLineValue // autoboxing to Integer @@ -169,7 +170,7 @@ internal class LocationConverterTest : BaseTest() { @Test fun toEntityAttributeWithStrings() { // arrange - val sut: CompositeAttributeConverter = sut + val sut: CompositeAttributeConverter = sut val value: MutableMap = HashMap() val startLineValue = "1" value[LocationConverter.START_LINE] = startLineValue @@ -197,7 +198,7 @@ internal class LocationConverterTest : BaseTest() { @Test fun toEntityAttributeWithCustomTypes() { // arrange - val sut: CompositeAttributeConverter = sut + val sut: CompositeAttributeConverter = sut val value: MutableMap = HashMap() val startLineValue: Any = CustomNumber(1) value[LocationConverter.START_LINE] = startLineValue @@ -225,7 +226,7 @@ internal class LocationConverterTest : BaseTest() { @Test fun toEntityAttributeWithMixedTypes() { // arrange - val sut: CompositeAttributeConverter = sut + val sut: CompositeAttributeConverter = sut val value: MutableMap = HashMap() val startLineValue: Any = 1 value[LocationConverter.START_LINE] = startLineValue @@ -253,7 +254,7 @@ internal class LocationConverterTest : BaseTest() { @Test fun toEntityAttributeWithValueBiggerMaxIntBooms() { // arrange - val sut: CompositeAttributeConverter = sut + val sut: CompositeAttributeConverter = sut val value: MutableMap = HashMap() val startLineValue = Int.MAX_VALUE.toLong() + 1 value[LocationConverter.START_LINE] = startLineValue @@ -265,7 +266,7 @@ internal class LocationConverterTest : BaseTest() { @Test fun toEntityAttributeWithValueSmallerMinIntBooms() { // arrange - val sut: CompositeAttributeConverter = sut + val sut: CompositeAttributeConverter = sut val value: MutableMap = HashMap() val startLineValue = Int.MIN_VALUE.toLong() - 1 value[LocationConverter.START_LINE] = startLineValue @@ -277,7 +278,7 @@ internal class LocationConverterTest : BaseTest() { @Test fun toEntityAttributeWithAFloatBooms() { // arrange - val sut: CompositeAttributeConverter = sut + val sut: CompositeAttributeConverter = sut val value: MutableMap = HashMap() val startLineValue = 1.0.toFloat() value[LocationConverter.START_LINE] = startLineValue @@ -289,7 +290,7 @@ internal class LocationConverterTest : BaseTest() { @Test fun toEntityAttributeWithADoubleBooms() { // arrange - val sut: CompositeAttributeConverter = sut + val sut: CompositeAttributeConverter = sut val value: MutableMap = HashMap() val startLineValue = 1.0 value[LocationConverter.START_LINE] = startLineValue @@ -301,7 +302,7 @@ internal class LocationConverterTest : BaseTest() { @Test fun toEntityAttributeWithAStringBooms() { // arrange - val sut: CompositeAttributeConverter = sut + val sut: CompositeAttributeConverter = sut val value: MutableMap = HashMap() val startLineValue: Any = "TEST STRING" value[LocationConverter.START_LINE] = startLineValue @@ -313,7 +314,7 @@ internal class LocationConverterTest : BaseTest() { @Test fun toEntityAttributeWithAObjectBooms() { // arrange - val sut: CompositeAttributeConverter = sut + val sut: CompositeAttributeConverter = sut val value: MutableMap = HashMap() val startLineValue = Any() value[LocationConverter.START_LINE] = startLineValue diff --git a/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/DeclarationHandler.kt b/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/DeclarationHandler.kt index 2859156161..4bdd678251 100644 --- a/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/DeclarationHandler.kt +++ b/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/DeclarationHandler.kt @@ -49,7 +49,6 @@ import de.fraunhofer.aisec.cpg.graph.scopes.RecordScope 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.Type -import de.fraunhofer.aisec.cpg.graph.types.UnknownType import java.util.function.Supplier import java.util.stream.Collectors import kotlin.collections.set @@ -178,8 +177,7 @@ open class DeclarationHandler(lang: JavaLanguageFrontend) : val receiver = this.newVariableDeclaration( "this", - if (recordDecl != null) this.parseType(recordDecl.name) - else UnknownType.getUnknownType(language), + if (recordDecl != null) this.parseType(recordDecl.name) else newUnknownType(), "this", false ) 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 a4663a48c3..338462f5de 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 @@ -44,7 +44,6 @@ import de.fraunhofer.aisec.cpg.graph.types.FunctionType import de.fraunhofer.aisec.cpg.graph.types.PointerType import de.fraunhofer.aisec.cpg.graph.types.Type import de.fraunhofer.aisec.cpg.graph.types.TypeParser -import de.fraunhofer.aisec.cpg.graph.types.UnknownType import java.util.function.Supplier import kotlin.collections.set import org.slf4j.LoggerFactory @@ -178,14 +177,14 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : if (s != null) { this.parseType(s) } else { - UnknownType.getUnknownType(this.language) + newUnknownType() } } catch (e: NoClassDefFoundError) { val s = frontend.recoverTypeFromUnsolvedException(e) if (s != null) { this.parseType(s) } else { - UnknownType.getUnknownType(this.language) + newUnknownType() } } val condition = @@ -296,7 +295,7 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : this.parseType(qualifiedNameFromImports) } else { log.info("Unknown base type 1 for {}", fieldAccessExpr) - UnknownType.getUnknownType(language) + newUnknownType() } } } catch (ex: NoClassDefFoundError) { @@ -319,7 +318,7 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : this.parseType(qualifiedNameFromImports) } else { log.info("Unknown base type 1 for {}", fieldAccessExpr) - UnknownType.getUnknownType(language) + newUnknownType() } } } @@ -362,7 +361,7 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : this.parseType(qualifiedNameFromImports) } else { log.info("Unknown base type 2 for {}", fieldAccessExpr) - UnknownType.getUnknownType(language) + newUnknownType() } base = this.newDeclaredReferenceExpression( @@ -397,7 +396,7 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : this.parseType("int") } else { log.info("Unknown field type for {}", fieldAccessExpr) - UnknownType.getUnknownType(language) + newUnknownType() } val memberExpression = this.newMemberExpression( @@ -417,7 +416,7 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : this.parseType("int") } else { log.info("Unknown field type for {}", fieldAccessExpr) - UnknownType.getUnknownType(language) + newUnknownType() } val memberExpression = this.newMemberExpression(fieldAccessExpr.name.identifier, base!!, fieldType, ".") @@ -514,11 +513,7 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : // about the inheritance structure. Thus, we delay the resolving to the variable resolving // process val superExpression = - this.newDeclaredReferenceExpression( - expr.toString(), - UnknownType.getUnknownType(language), - expr.toString() - ) + this.newDeclaredReferenceExpression(expr.toString(), newUnknownType(), expr.toString()) frontend.setCodeAndLocation(superExpression, expr) return superExpression } @@ -616,7 +611,7 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : } val t: Type if (typeString == null) { - t = UnknownType.getUnknownType(language) + t = newUnknownType() } else { t = this.parseType(typeString) t.typeOrigin = Type.Origin.GUESSED @@ -712,7 +707,7 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : if (name.contains(".")) { name = name.substring(name.lastIndexOf('.') + 1) } - var typeString = UnknownType.UNKNOWN_TYPE_STRING + var typeString: String? = null var isStatic = false var resolved: ResolvedMethodDeclaration? = null try { @@ -774,15 +769,14 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : base = createImplicitThis() } } - val member = - this.newMemberExpression(name, base!!, UnknownType.getUnknownType(language), ".") + val member = this.newMemberExpression(name, base!!, newUnknownType(), ".") frontend.setCodeAndLocation( member, methodCallExpr.name ) // This will also overwrite the code set to the empty string set above callExpression = this.newMemberCallExpression(member, isStatic, methodCallExpr.toString(), expr) - callExpression.type = this.parseType(typeString) + callExpression.type = typeString?.let { this.parseType(it) } ?: newUnknownType() val arguments = methodCallExpr.arguments // handle the arguments @@ -806,7 +800,7 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : val base: de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression val thisType = (frontend.scopeManager.currentFunction as MethodDeclaration?)?.receiver?.type - ?: UnknownType.getUnknownType(language) + ?: newUnknownType() base = this.newDeclaredReferenceExpression("this", thisType, "this") base.isImplicit = true return base 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 60fdafb7b2..dbdf6b8349 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 @@ -56,7 +56,6 @@ import de.fraunhofer.aisec.cpg.graph.declarations.NamespaceDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration import de.fraunhofer.aisec.cpg.graph.scopes.Scope import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression -import de.fraunhofer.aisec.cpg.graph.types.UnknownType import de.fraunhofer.aisec.cpg.helpers.Benchmark import de.fraunhofer.aisec.cpg.helpers.CommonPath import de.fraunhofer.aisec.cpg.passes.JavaExternalTypeHierarchyResolver @@ -218,7 +217,7 @@ open class JavaLanguageFrontend( return try { val type = nodeWithType.typeAsString if (type == "var") { - UnknownType.getUnknownType(language) + newUnknownType() } else parseType(resolved.type.describe()) } catch (ex: RuntimeException) { getTypeFromImportIfPossible(nodeWithType.type) @@ -230,7 +229,7 @@ open class JavaLanguageFrontend( fun getTypeAsGoodAsPossible(type: Type): de.fraunhofer.aisec.cpg.graph.types.Type { return try { if (type.toString() == "var") { - UnknownType.getUnknownType(language) + newUnknownType() } else parseType(type.resolve().describe()) } catch (ex: RuntimeException) { getTypeFromImportIfPossible(type) @@ -495,9 +494,9 @@ open class JavaLanguageFrontend( var root = config.topLevel if (root == null && config.softwareComponents.size == 1) { root = - CommonPath.commonPath( - config.softwareComponents[config.softwareComponents.keys.first()] - ) + config.softwareComponents[config.softwareComponents.keys.first()]?.let { + CommonPath.commonPath(it) + } } if (root == null) { log.warn("Could not determine source root for {}", config.softwareComponents) diff --git a/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/StatementHandler.kt b/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/StatementHandler.kt index f736fae4eb..2ff8f50882 100644 --- a/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/StatementHandler.kt +++ b/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/StatementHandler.kt @@ -132,7 +132,7 @@ class StatementHandler(lang: JavaLanguageFrontend?) : assertStatement.condition = frontend.expressionHandler.handle(conditionExpression) as de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression - thenStatement.ifPresent { statement: Expression? -> + thenStatement.ifPresent { assertStatement.message = frontend.expressionHandler.handle(thenStatement.get()) } return assertStatement diff --git a/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/JavaExternalTypeHierarchyResolver.kt b/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/JavaExternalTypeHierarchyResolver.kt index ed17eb1e5d..36c54d0d77 100644 --- a/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/JavaExternalTypeHierarchyResolver.kt +++ b/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/JavaExternalTypeHierarchyResolver.kt @@ -51,10 +51,9 @@ class JavaExternalTypeHierarchyResolver : Pass() { var root = translationResult.config.topLevel if (root == null && translationResult.config.softwareComponents.size == 1) { root = - CommonPath.commonPath( - translationResult.config.softwareComponents[ - translationResult.config.softwareComponents.keys.first()] - ) + translationResult.config.softwareComponents[ + translationResult.config.softwareComponents.keys.first()] + ?.let { CommonPath.commonPath(it) } } if (root == null) { log.warn( diff --git a/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/types/TypeTests.kt b/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/types/TypeTests.kt index 04dd3d2c3a..df42ee4248 100644 --- a/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/types/TypeTests.kt +++ b/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/types/TypeTests.kt @@ -94,7 +94,7 @@ internal class TypeTests : BaseTest() { // Test 8: generics typeString = "List list" result = TypeParser.createFrom(typeString, JavaLanguage()) - var generics: MutableList = ArrayList() + var generics = mutableListOf() generics.add(StringType("java.lang.String", JavaLanguage())) expected = ObjectType("List", generics, false, JavaLanguage()) assertEquals(expected, result) diff --git a/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/helpers/CommentMatcherTest.kt b/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/helpers/CommentMatcherTest.kt index b96ceeeb72..8673c8c951 100644 --- a/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/helpers/CommentMatcherTest.kt +++ b/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/helpers/CommentMatcherTest.kt @@ -59,7 +59,7 @@ class CommentMatcherTest { // The class should have 2 comments: The javadoc and "Class comment" val tu = result.translationUnits.first() val classDeclaration = tu.declarations.first() as RecordDeclaration - classDeclaration.comment = "" // Reset the comment of the ClassDerclaration + classDeclaration.comment = "" // Reset the comment of the ClassDeclaration val comment = "This comment clearly belongs to the class." CommentMatcher().matchCommentToNode(comment, Region(2, 4, 2, 46), tu) @@ -101,11 +101,10 @@ class CommentMatcherTest { assertEquals( comment6, forLoop.comment - ) // It doesn't put the whole comment, only the part that amtches + ) // It doesn't put the whole comment, only the part that matches // TODO IMHO the comment "i decl" should belong to the declaration statement of i. But - // somehow, - // the comment matcher puts it to the loop condition. + // somehow, the comment matcher puts it to the loop condition. val comment7 = "i decl" CommentMatcher().matchCommentToNode(comment7, Region(16, 26, 16, 32), tu) // assertEquals(comment7, forLoop.initializerStatement.comment) diff --git a/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/ExpressionHandler.kt b/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/ExpressionHandler.kt index a4f8d08e40..22dbd1b177 100644 --- a/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/ExpressionHandler.kt +++ b/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/ExpressionHandler.kt @@ -33,7 +33,6 @@ import de.fraunhofer.aisec.cpg.graph.statements.expressions.* import de.fraunhofer.aisec.cpg.graph.types.ObjectType import de.fraunhofer.aisec.cpg.graph.types.PointerType import de.fraunhofer.aisec.cpg.graph.types.Type -import de.fraunhofer.aisec.cpg.graph.types.UnknownType import de.fraunhofer.aisec.cpg.passes.VariableUsageResolver import org.bytedeco.javacpp.IntPointer import org.bytedeco.javacpp.SizeTPointer @@ -65,11 +64,7 @@ class ExpressionHandler(lang: LLVMIRLanguageFrontend) : newDeclaredReferenceExpression("poison", frontend.typeOf(value), "poison") } LLVMConstantTokenNoneValueKind -> - newLiteral( - null, - UnknownType.getUnknownType(language), - frontend.getCodeFromRawNode(value) - ) + newLiteral(null, newUnknownType(), frontend.getCodeFromRawNode(value)) LLVMUndefValueValueKind -> initializeAsUndef(frontend.typeOf(value), frontend.getCodeFromRawNode(value)!!) LLVMConstantAggregateZeroValueKind -> @@ -524,17 +519,11 @@ class ExpressionHandler(lang: LLVMIRLanguageFrontend) : } // our new base-type is the type of the field - baseType = field?.type ?: UnknownType.getUnknownType(language) + baseType = field?.type ?: newUnknownType() // construct our member expression expr = - newMemberExpression( - fieldName, - base, - field?.type ?: UnknownType.getUnknownType(), - ".", - "" - ) + newMemberExpression(fieldName, base, field?.type ?: newUnknownType(), ".", "") log.info("{}", expr) // the current expression is the new base diff --git a/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/LLVMIRLanguageFrontend.kt b/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/LLVMIRLanguageFrontend.kt index 1f5ba20567..5066e43252 100644 --- a/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/LLVMIRLanguageFrontend.kt +++ b/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/LLVMIRLanguageFrontend.kt @@ -33,6 +33,7 @@ import de.fraunhofer.aisec.cpg.frontends.TranslationException import de.fraunhofer.aisec.cpg.graph.declarations.Declaration import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration +import de.fraunhofer.aisec.cpg.graph.newUnknownType import de.fraunhofer.aisec.cpg.graph.parseType import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression @@ -194,7 +195,7 @@ class LLVMIRLanguageFrontend( return alreadyVisited[typeRef]!! } else if (typeRef in alreadyVisited) { // Recursive call but we can't resolve it. - return UnknownType.getUnknownType(language) + return newUnknownType() } alreadyVisited[typeRef] = null val res: Type = diff --git a/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/StatementHandler.kt b/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/StatementHandler.kt index b4b3c355bb..dfca2cc049 100644 --- a/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/StatementHandler.kt +++ b/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/StatementHandler.kt @@ -35,7 +35,6 @@ import de.fraunhofer.aisec.cpg.graph.statements.* import de.fraunhofer.aisec.cpg.graph.statements.expressions.* import de.fraunhofer.aisec.cpg.graph.types.ObjectType import de.fraunhofer.aisec.cpg.graph.types.PointerType -import de.fraunhofer.aisec.cpg.graph.types.UnknownType import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker import de.fraunhofer.aisec.cpg.helpers.annotations.FunctionReplacement import java.util.function.BiConsumer @@ -646,8 +645,7 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : base = newDeclaredReferenceExpression( copy.singleDeclaration?.name?.localName, - (copy.singleDeclaration as? VariableDeclaration)?.type - ?: UnknownType.getUnknownType(this.language), + (copy.singleDeclaration as? VariableDeclaration)?.type ?: newUnknownType(), frontend.getCodeFromRawNode(instr) ) } @@ -697,7 +695,7 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : val field = record.fields["field_$index"] // our new base-type is the type of the field - baseType = field?.type ?: UnknownType.getUnknownType(language) + baseType = field?.type ?: newUnknownType() // construct our member expression expr = newMemberExpression(field?.name?.localName, base, baseType, ".", "") @@ -1175,7 +1173,7 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : catchClause.parameter = newVariableDeclaration( "e_${gotoCatch.labelName}", - UnknownType.getUnknownType(language), + newUnknownType(), instrStr, true, frontend.language @@ -1256,7 +1254,7 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : arrayExpr.arrayExpression = newDeclaredReferenceExpression( decl?.name?.toString() ?: Node.EMPTY_NAME, - decl?.type ?: UnknownType.getUnknownType(this.language), + decl?.type ?: newUnknownType(), instrStr ) arrayExpr.subscriptExpression = frontend.getOperandValueAtIndex(instr, 2) diff --git a/cpg-language-python/src/main/python/CPGPython/_expressions.py b/cpg-language-python/src/main/python/CPGPython/_expressions.py index c2060cb52c..dd642a561b 100644 --- a/cpg-language-python/src/main/python/CPGPython/_expressions.py +++ b/cpg-language-python/src/main/python/CPGPython/_expressions.py @@ -63,7 +63,7 @@ def handle_expression_impl(self, expr): return ExpressionBuilderKt( self.frontend, None, - UnknownType.getUnknownType(), + UnknownType.getUnknownType(self.frontend.getLanguage()), self.get_src_code(expr), expr) # we got a complex number @@ -100,7 +100,8 @@ def handle_expression_impl(self, expr): body = self.handle_expression(expr.body) orelse = self.handle_expression(expr.orelse) r = ExpressionBuilderKt.newConditionalExpression( - self.frontend, test, body, orelse, UnknownType.getUnknownType()) + self.frontend, test, body, orelse, + UnknownType.getUnknownType(self.frontend.getLanguage())) return r elif isinstance(expr, ast.Dict): ile = ExpressionBuilderKt.newInitializerListExpression( @@ -294,7 +295,7 @@ def handle_expression_impl(self, expr): "Found unexpected type - using a dummy: %s" % (type(expr.value)), loglevel="ERROR") - tpe = UnknownType.getUnknownType() + tpe = UnknownType.getUnknownType(self.frontend.getLanguage()) lit = ExpressionBuilderKt.newLiteral( self.frontend, resultvalue, tpe, self.get_src_code(expr)) @@ -312,7 +313,8 @@ def handle_expression_impl(self, expr): self.frontend, value.getName(), value.getType(), value.getCode()) mem = ExpressionBuilderKt.newMemberExpression( - self.frontend, expr.attr, value, UnknownType.getUnknownType(), + self.frontend, expr.attr, value, + UnknownType.getUnknownType(self.frontend.getLanguage()), ".", self.get_src_code(expr)) return mem @@ -330,7 +332,8 @@ def handle_expression_impl(self, expr): return r elif isinstance(expr, ast.Name): r = ExpressionBuilderKt.newDeclaredReferenceExpression( - self.frontend, expr.id, UnknownType.getUnknownType(), + self.frontend, expr.id, + UnknownType.getUnknownType(self.frontend.getLanguage()), self.get_src_code(expr)) # Take a little shortcut and set refersTo, in case this is a method diff --git a/cpg-language-python/src/main/python/CPGPython/_statements.py b/cpg-language-python/src/main/python/CPGPython/_statements.py index be4f9be6bb..2a10a329fe 100644 --- a/cpg-language-python/src/main/python/CPGPython/_statements.py +++ b/cpg-language-python/src/main/python/CPGPython/_statements.py @@ -170,7 +170,7 @@ def handle_statement_impl(self, stmt): else: name = s.name src = name - tpe = UnknownType.getUnknownType() + tpe = UnknownType.getUnknownType(self.frontend.getLanguage()) v = DeclarationBuilderKt.newVariableDeclaration(self.frontend, name, tpe, src, False) @@ -200,7 +200,7 @@ def handle_statement_impl(self, stmt): else: name = s.name src = name - tpe = UnknownType.getUnknownType() + tpe = UnknownType.getUnknownType(self.frontend.getLanguage()) v = DeclarationBuilderKt.newVariableDeclaration( self.frontend, name, tpe, src, False) # inaccurate but ast.alias does not hold location information @@ -408,7 +408,7 @@ def handle_argument(self, arg: ast.arg): if arg.annotation is not None: tpe = NodeBuilderKt.parseType(self.frontend, arg.annotation.id) else: - tpe = UnknownType.getUnknownType() + tpe = UnknownType.getUnknownType(self.frontend.getLanguage()) # TODO variadic pvd = DeclarationBuilderKt.newParamVariableDeclaration( self.frontend, arg.arg, tpe, False, self.get_src_code(arg)) @@ -578,7 +578,8 @@ class Foo: None, rhs, False) # TODO None -> add infos else: v = DeclarationBuilderKt.newFieldDeclaration( - self.frontend, name, UnknownType.getUnknownType(), + self.frontend, name, + UnknownType.getUnknownType(self.frontend.getLanguage()), None, self.get_src_code(stmt), None, None, False) # TODO None -> add infos self.scopemanager.addDeclaration(v) @@ -603,7 +604,8 @@ def bar(self): else: v = DeclarationBuilderKt.newVariableDeclaration( self.frontend, lhs.getName(), - UnknownType.getUnknownType(), + UnknownType.getUnknownType( + self.frontend.getLanguage()), self.get_src_code(stmt), False) if rhs is not None: @@ -641,7 +643,8 @@ def bar(self): else: v = DeclarationBuilderKt.newFieldDeclaration( self.frontend, lhs.getName(), - UnknownType.getUnknownType(), + UnknownType.getUnknownType( + self.frontend.getLanguage()), None, self.get_src_code(stmt), None, None, False) self.scopemanager.addDeclaration(v) @@ -663,7 +666,7 @@ def bar(self): else: v = DeclarationBuilderKt.newVariableDeclaration( self.frontend, lhs.getName(), - UnknownType.getUnknownType(), + UnknownType.getUnknownType(self.frontend.getLanguage()), self.get_src_code(stmt), False) if rhs is not None: diff --git a/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/DeclarationHandler.kt b/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/DeclarationHandler.kt index cc0d660635..8e00976c53 100644 --- a/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/DeclarationHandler.kt +++ b/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/DeclarationHandler.kt @@ -28,7 +28,6 @@ package de.fraunhofer.aisec.cpg.frontends.typescript import de.fraunhofer.aisec.cpg.frontends.Handler import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.declarations.* -import de.fraunhofer.aisec.cpg.graph.types.UnknownType class DeclarationHandler(lang: TypeScriptLanguageFrontend) : Handler(::ProblemDeclaration, lang) { @@ -58,8 +57,7 @@ class DeclarationHandler(lang: TypeScriptLanguageFrontend) : private fun handlePropertySignature(node: TypeScriptNode): FieldDeclaration { val name = this.frontend.getIdentifierName(node) val type = - node.typeChildNode?.let { this.frontend.typeHandler.handle(it) } - ?: UnknownType.getUnknownType(language) + node.typeChildNode?.let { this.frontend.typeHandler.handle(it) } ?: newUnknownType() val field = newFieldDeclaration( @@ -113,8 +111,7 @@ class DeclarationHandler(lang: TypeScriptLanguageFrontend) : private fun handleParameter(node: TypeScriptNode): Declaration { val name = this.frontend.getIdentifierName(node) val type = - node.typeChildNode?.let { this.frontend.typeHandler.handle(it) } - ?: UnknownType.getUnknownType(language) + node.typeChildNode?.let { this.frontend.typeHandler.handle(it) } ?: newUnknownType() val param = newParamVariableDeclaration(name, type, false, this.frontend.getCodeFromRawNode(node)) @@ -176,8 +173,7 @@ class DeclarationHandler(lang: TypeScriptLanguageFrontend) : } node.typeChildNode?.let { - func.type = - this.frontend.typeHandler.handle(it) ?: UnknownType.getUnknownType(this.language) + func.type = this.frontend.typeHandler.handle(it) ?: newUnknownType() } this.frontend.scopeManager.enterScope(func) @@ -220,7 +216,7 @@ class DeclarationHandler(lang: TypeScriptLanguageFrontend) : val `var` = newVariableDeclaration( name, - UnknownType.getUnknownType(language), + newUnknownType(), this.frontend.getCodeFromRawNode(node), false ) diff --git a/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/ExpressionHandler.kt b/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/ExpressionHandler.kt index 2a40f45ead..abda442ac7 100644 --- a/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/ExpressionHandler.kt +++ b/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/ExpressionHandler.kt @@ -30,7 +30,6 @@ import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration import de.fraunhofer.aisec.cpg.graph.statements.ReturnStatement import de.fraunhofer.aisec.cpg.graph.statements.expressions.* -import de.fraunhofer.aisec.cpg.graph.types.UnknownType class ExpressionHandler(lang: TypeScriptLanguageFrontend) : Handler(::ProblemExpression, lang) { @@ -116,7 +115,7 @@ class ExpressionHandler(lang: TypeScriptLanguageFrontend) : // the function will (probably) not have a defined return type, so we try to deduce this // from a return statement - if (func?.type == UnknownType.getUnknownType(language)) { + if (func?.type == newUnknownType()) { val returnValue = func.bodyOrNull()?.returnValue /*if (returnValue == null) { @@ -124,7 +123,7 @@ class ExpressionHandler(lang: TypeScriptLanguageFrontend) : func.type = TypeParser.createFrom("void", false) } else {*/ - val returnType = returnValue?.type ?: UnknownType.getUnknownType(language) + val returnType = returnValue?.type ?: newUnknownType() func.type = returnType // } @@ -177,7 +176,7 @@ class ExpressionHandler(lang: TypeScriptLanguageFrontend) : val ref = newDeclaredReferenceExpression( name, - UnknownType.getUnknownType(language), + newUnknownType(), this.frontend.getCodeFromRawNode(node) ) @@ -195,7 +194,7 @@ class ExpressionHandler(lang: TypeScriptLanguageFrontend) : newMemberExpression( name, base, - UnknownType.getUnknownType(language), + newUnknownType(), ".", this.frontend.getCodeFromRawNode(node) ) diff --git a/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/TypeHandler.kt b/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/TypeHandler.kt index 778979d02a..e1f83be9ec 100644 --- a/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/TypeHandler.kt +++ b/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/TypeHandler.kt @@ -26,6 +26,7 @@ package de.fraunhofer.aisec.cpg.frontends.typescript import de.fraunhofer.aisec.cpg.frontends.Handler +import de.fraunhofer.aisec.cpg.graph.newUnknownType import de.fraunhofer.aisec.cpg.graph.parseType import de.fraunhofer.aisec.cpg.graph.types.PointerType import de.fraunhofer.aisec.cpg.graph.types.Type @@ -50,13 +51,11 @@ class TypeHandler(frontend: TypeScriptLanguageFrontend) : "ArrayType" -> return handleArrayType(node) } - return UnknownType.getUnknownType(language) + return newUnknownType() } private fun handleArrayType(node: TypeScriptNode): Type { - val type = - node.firstChild("TypeReference")?.let { this.handle(it) } - ?: UnknownType.getUnknownType(language) + val type = node.firstChild("TypeReference")?.let { this.handle(it) } ?: newUnknownType() return type.reference(PointerType.PointerOrigin.ARRAY) } @@ -78,6 +77,6 @@ class TypeHandler(frontend: TypeScriptLanguageFrontend) : return parseType(this.frontend.getIdentifierName(node)) } - return UnknownType.getUnknownType(language) + return newUnknownType() } } diff --git a/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/TypeScriptLanguageFrontend.kt b/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/TypeScriptLanguageFrontend.kt index 25458bb421..9772d64b2a 100644 --- a/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/TypeScriptLanguageFrontend.kt +++ b/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/TypeScriptLanguageFrontend.kt @@ -127,17 +127,15 @@ class TypeScriptLanguageFrontend( // acceptable. val matches: Sequence = Regex("(?:/\\*((?:[^*]|(?:\\*+[^*/]))*)\\*+/)|(?://(.*))").findAll(currentFileContent!!) - matches.toList().forEach { - val groups = it.groups + matches.toList().forEach { result -> + val groups = result.groups groups[0]?.let { - var comment = it.value - val commentRegion = getRegionFromStartEnd(file, it.range.first, it.range.last) // We only want the actual comment text and therefore take the value we captured in // the first, or second group. // Only as a last resort we take the entire match, although this should never occurs - comment = groups[1]?.value ?: (groups[2]?.value ?: it.value) + var comment = groups[1]?.value ?: (groups[2]?.value ?: it.value) comment = comment.trim() From b6cb944cc7e4255816f5d479af68db28e0106364 Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Sat, 11 Mar 2023 22:40:04 +0100 Subject: [PATCH 004/143] Fixed JitPack build (#1123) --- build.gradle.kts | 20 ++++++++++---------- jitpack.yml | 3 ++- settings.gradle.kts | 20 ++++++++++---------- 3 files changed, 22 insertions(+), 21 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 46e4f14eca..c0130136d2 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -65,32 +65,32 @@ sonarqube { // Load the properties that define which frontends to include // // this code block also exists in settings.gradle.kts -val enableJavaFrontend by extra { - val enableJavaFrontend: String by project +val enableJavaFrontend: Boolean by extra { + val enableJavaFrontend: String? by project enableJavaFrontend.toBoolean() } project.logger.lifecycle("Java frontend is ${if (enableJavaFrontend) "enabled" else "disabled"}") -val enableGoFrontend by extra { - val enableGoFrontend: String by project +val enableGoFrontend: Boolean by extra { + val enableGoFrontend: String? by project enableGoFrontend.toBoolean() } project.logger.lifecycle("Go frontend is ${if (enableGoFrontend) "enabled" else "disabled"}") -val enablePythonFrontend by extra { - val enablePythonFrontend: String by project +val enablePythonFrontend: Boolean by extra { + val enablePythonFrontend: String? by project enablePythonFrontend.toBoolean() } project.logger.lifecycle("Python frontend is ${if (enablePythonFrontend) "enabled" else "disabled"}") -val enableLLVMFrontend by extra { - val enableLLVMFrontend: String by project +val enableLLVMFrontend: Boolean by extra { + val enableLLVMFrontend: String? by project enableLLVMFrontend.toBoolean() } project.logger.lifecycle("LLVM frontend is ${if (enableLLVMFrontend) "enabled" else "disabled"}") -val enableTypeScriptFrontend by extra { - val enableTypeScriptFrontend: String by project +val enableTypeScriptFrontend: Boolean by extra { + val enableTypeScriptFrontend: String? by project enableTypeScriptFrontend.toBoolean() } project.logger.lifecycle("TypeScript frontend is ${if (enableTypeScriptFrontend) "enabled" else "disabled"}") diff --git a/jitpack.yml b/jitpack.yml index 4fce533b52..0299ff9ce2 100644 --- a/jitpack.yml +++ b/jitpack.yml @@ -6,4 +6,5 @@ before_install: - ls -l ~/go install: - export PATH="$PATH:$HOME/go/bin" - - ./gradlew build -xtest -Pgroup=com.github.Fraunhofer-AISEC -PnodeDownload=true publishToMavenLocal + - cp gradle.properties.example gradle.properties + - ./gradlew build -xtest -Pgroup=com.github.Fraunhofer-AISEC -PnodeDownload=true -PenableJavaFrontend=true -PenableGoFrontend=true -PenablePythonFrontend=true -PenableLLVMFrontend=true -PenableTypeScriptFrontend=true publishToMavenLocal diff --git a/settings.gradle.kts b/settings.gradle.kts index 3144aa7173..b2c4fcaa36 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -20,24 +20,24 @@ include(":cpg-neo4j") include(":cpg-console") // this code block also exists in the root build.gradle.kts -val enableJavaFrontend by extra { - val enableJavaFrontend: String by settings +val enableJavaFrontend: Boolean by extra { + val enableJavaFrontend: String? by settings enableJavaFrontend.toBoolean() } -val enableGoFrontend by extra { - val enableGoFrontend: String by settings +val enableGoFrontend: Boolean by extra { + val enableGoFrontend: String? by settings enableGoFrontend.toBoolean() } -val enablePythonFrontend by extra { - val enablePythonFrontend: String by settings +val enablePythonFrontend: Boolean by extra { + val enablePythonFrontend: String? by settings enablePythonFrontend.toBoolean() } -val enableLLVMFrontend by extra { - val enableLLVMFrontend: String by settings +val enableLLVMFrontend: Boolean by extra { + val enableLLVMFrontend: String? by settings enableLLVMFrontend.toBoolean() } -val enableTypeScriptFrontend by extra { - val enableTypeScriptFrontend: String by settings +val enableTypeScriptFrontend: Boolean by extra { + val enableTypeScriptFrontend: String? by settings enableTypeScriptFrontend.toBoolean() } From 5f984731d9380f92306fa8abcaf0f84e4e484d1f Mon Sep 17 00:00:00 2001 From: Tobias Specht Date: Mon, 13 Mar 2023 07:42:35 +0100 Subject: [PATCH 005/143] Set loadIncludes and addIncludesToGraph in sync (#1121) set loadIncludes and addIncludesToGraph in sync --- .../main/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/Application.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/cpg-neo4j/src/main/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/Application.kt b/cpg-neo4j/src/main/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/Application.kt index 02a11fa207..92277f2ab7 100644 --- a/cpg-neo4j/src/main/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/Application.kt +++ b/cpg-neo4j/src/main/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/Application.kt @@ -318,6 +318,7 @@ class Application : Callable { .optionalLanguage("de.fraunhofer.aisec.cpg.frontends.python.PythonLanguage") .optionalLanguage("de.fraunhofer.aisec.cpg.frontends.typescript.TypeScriptLanguage") .loadIncludes(loadIncludes) + .addIncludesToGraph(loadIncludes) .debugParser(DEBUG_PARSER) .useUnityBuild(useUnityBuild) From c90af364648d9bdef27de8d4136e9558809f7a9b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 13 Mar 2023 10:13:32 +0100 Subject: [PATCH 006/143] Update dependency org.mockito:mockito-core to v5.2.0 (#1125) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ead5f5c3be..a22a8598bc 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -36,7 +36,7 @@ llvm = { module = "org.bytedeco:llvm-platform", version = "15.0.3-1.5.8"} # test junit-params = { module = "org.junit.jupiter:junit-jupiter-params", version = "5.9.1"} -mockito = { module = "org.mockito:mockito-core", version = "5.1.1"} +mockito = { module = "org.mockito:mockito-core", version = "5.2.0"} # plugins needed for build.gradle.kts in buildSrc kotlin-gradle = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } From ae67e2b1233249834ab3f9cba154c537ad0809a3 Mon Sep 17 00:00:00 2001 From: keremc <5321759+keremc@users.noreply.github.com> Date: Mon, 13 Mar 2023 10:27:58 +0100 Subject: [PATCH 007/143] Update list of built-in types to handle cases where "int" is optional (#1122) Update list of built-in types --- .../aisec/cpg/frontends/cpp/CLanguage.kt | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CLanguage.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CLanguage.kt index 5bf4022de7..474fdfed5e 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CLanguage.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CLanguage.kt @@ -57,14 +57,22 @@ open class CLanguage : "char" to IntegerType("char", 8, this, NumericType.Modifier.NOT_APPLICABLE), "byte" to IntegerType("byte", 8, this, NumericType.Modifier.SIGNED), "short" to IntegerType("short", 16, this, NumericType.Modifier.SIGNED), + "short int" to IntegerType("short", 16, this, NumericType.Modifier.SIGNED), "int" to IntegerType("int", 32, this, NumericType.Modifier.SIGNED), "long" to IntegerType("long", 64, this, NumericType.Modifier.SIGNED), + "long int" to IntegerType("long", 64, this, NumericType.Modifier.SIGNED), + "long long" to IntegerType("long long int", 64, this, NumericType.Modifier.SIGNED), "long long int" to IntegerType("long long int", 64, this, NumericType.Modifier.SIGNED), "signed char" to IntegerType("signed char", 8, this, NumericType.Modifier.SIGNED), "signed byte" to IntegerType("byte", 8, this, NumericType.Modifier.SIGNED), "signed short" to IntegerType("short", 16, this, NumericType.Modifier.SIGNED), + "signed short int" to IntegerType("short", 16, this, NumericType.Modifier.SIGNED), + "signed" to IntegerType("int", 32, this, NumericType.Modifier.SIGNED), "signed int" to IntegerType("int", 32, this, NumericType.Modifier.SIGNED), "signed long" to IntegerType("long", 64, this, NumericType.Modifier.SIGNED), + "signed long int" to IntegerType("long", 64, this, NumericType.Modifier.SIGNED), + "signed long long" to + IntegerType("long long int", 64, this, NumericType.Modifier.SIGNED), "signed long long int" to IntegerType("long long int", 64, this, NumericType.Modifier.SIGNED), "float" to FloatingPointType("float", 32, this, NumericType.Modifier.SIGNED), @@ -73,9 +81,16 @@ open class CLanguage : "unsigned byte" to IntegerType("unsigned byte", 8, this, NumericType.Modifier.UNSIGNED), "unsigned short" to IntegerType("unsigned short", 16, this, NumericType.Modifier.UNSIGNED), + "unsigned short int" to + IntegerType("unsigned short", 16, this, NumericType.Modifier.UNSIGNED), + "unsigned" to IntegerType("unsigned int", 32, this, NumericType.Modifier.UNSIGNED), "unsigned int" to IntegerType("unsigned int", 32, this, NumericType.Modifier.UNSIGNED), "unsigned long" to IntegerType("unsigned long", 64, this, NumericType.Modifier.UNSIGNED), + "unsigned long int" to + IntegerType("unsigned long", 64, this, NumericType.Modifier.UNSIGNED), + "unsigned long long" to + IntegerType("unsigned long long int", 64, this, NumericType.Modifier.UNSIGNED), "unsigned long long int" to IntegerType("unsigned long long int", 64, this, NumericType.Modifier.UNSIGNED) ) From b697e8bd5f43effbff8dedc397170e5d8bf98148 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 14 Mar 2023 09:11:56 +0100 Subject: [PATCH 008/143] Update dependency @types/node to v18.15.3 (#1124) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- cpg-language-typescript/src/main/nodejs/package.json | 2 +- cpg-language-typescript/src/main/nodejs/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cpg-language-typescript/src/main/nodejs/package.json b/cpg-language-typescript/src/main/nodejs/package.json index 331c9a48b4..be7068c0bf 100644 --- a/cpg-language-typescript/src/main/nodejs/package.json +++ b/cpg-language-typescript/src/main/nodejs/package.json @@ -6,7 +6,7 @@ "start": "node src/parser.js" }, "dependencies": { - "@types/node": "18.14.0", + "@types/node": "18.15.3", "typescript": "4.8.2" }, "license": "Apache-2.0", diff --git a/cpg-language-typescript/src/main/nodejs/yarn.lock b/cpg-language-typescript/src/main/nodejs/yarn.lock index 1123c36d79..863849540f 100644 --- a/cpg-language-typescript/src/main/nodejs/yarn.lock +++ b/cpg-language-typescript/src/main/nodejs/yarn.lock @@ -78,10 +78,10 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.0.tgz#f38c7139247a1d619f6cc6f27b072606af7c289d" integrity sha512-IOXCvVRToe7e0ny7HpT/X9Rb2RYtElG1a+VshjwT00HxrM2dWBApHQoqsI6WiY7Q03vdf2bCrIGzVrkF/5t10w== -"@types/node@18.14.0": - version "18.14.0" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.14.0.tgz#94c47b9217bbac49d4a67a967fdcdeed89ebb7d0" - integrity sha512-5EWrvLmglK+imbCJY0+INViFWUHg1AHel1sq4ZVSfdcNqGy9Edv3UB9IIzzg+xPaUcAgZYcfVs2fBcwDeZzU0A== +"@types/node@18.15.3": + version "18.15.3" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.15.3.tgz#f0b991c32cfc6a4e7f3399d6cb4b8cf9a0315014" + integrity sha512-p6ua9zBxz5otCmbpb5D3U4B5Nanw6Pk3PPyX05xnxbB/fRv71N7CPmORg7uAD5P70T0xmx1pzAx/FUfa5X+3cw== "@webassemblyjs/ast@1.11.1": version "1.11.1" From 6e65b24116bf9a8c63e72cd53778176fad3860ac Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Wed, 15 Mar 2023 07:22:32 +0100 Subject: [PATCH 009/143] Optionally enable `JavaLanguage` for neo4j (#1128) Optionally enable `JavaLanguage` for neo4j since it is not part of `defaultLanguages()` anymore --- .../main/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/Application.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/cpg-neo4j/src/main/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/Application.kt b/cpg-neo4j/src/main/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/Application.kt index 92277f2ab7..5f27fec557 100644 --- a/cpg-neo4j/src/main/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/Application.kt +++ b/cpg-neo4j/src/main/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/Application.kt @@ -313,6 +313,7 @@ class Application : Callable { TranslationConfiguration.builder() .topLevel(topLevel) .defaultLanguages() + .optionalLanguage("de.fraunhofer.aisec.cpg.frontends.java.JavaLanguage") .optionalLanguage("de.fraunhofer.aisec.cpg.frontends.golang.GoLanguage") .optionalLanguage("de.fraunhofer.aisec.cpg.frontends.llvm.LLVMIRLanguage") .optionalLanguage("de.fraunhofer.aisec.cpg.frontends.python.PythonLanguage") From 04a63276070dd47120649ab761ed438adfa302ce Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 15 Mar 2023 12:00:17 +0000 Subject: [PATCH 010/143] Update dependency webpack to v5.76.0 [SECURITY] (#1130) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- cpg-language-typescript/src/main/nodejs/package.json | 2 +- cpg-language-typescript/src/main/nodejs/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cpg-language-typescript/src/main/nodejs/package.json b/cpg-language-typescript/src/main/nodejs/package.json index be7068c0bf..cfd2d6fa0b 100644 --- a/cpg-language-typescript/src/main/nodejs/package.json +++ b/cpg-language-typescript/src/main/nodejs/package.json @@ -11,7 +11,7 @@ }, "license": "Apache-2.0", "devDependencies": { - "webpack": "5.75.0", + "webpack": "5.76.0", "webpack-cli": "5.0.0" } } \ No newline at end of file diff --git a/cpg-language-typescript/src/main/nodejs/yarn.lock b/cpg-language-typescript/src/main/nodejs/yarn.lock index 863849540f..80ee041d09 100644 --- a/cpg-language-typescript/src/main/nodejs/yarn.lock +++ b/cpg-language-typescript/src/main/nodejs/yarn.lock @@ -758,10 +758,10 @@ webpack-sources@^3.2.3: resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== -webpack@5.75.0: - version "5.75.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.75.0.tgz#1e440468647b2505860e94c9ff3e44d5b582c152" - integrity sha512-piaIaoVJlqMsPtX/+3KTTO6jfvrSYgauFVdt8cr9LTHKmcq/AMd4mhzsiP7ZF/PGRNPGA8336jldh9l2Kt2ogQ== +webpack@5.76.0: + version "5.76.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.76.0.tgz#f9fb9fb8c4a7dbdcd0d56a98e56b8a942ee2692c" + integrity sha512-l5sOdYBDunyf72HW8dF23rFtWq/7Zgvt/9ftMof71E/yUb1YLOBmTgA2K4vQthB3kotMrSj609txVE0dnr2fjA== dependencies: "@types/eslint-scope" "^3.7.3" "@types/estree" "^0.0.51" From f17909534b23861a5629c91d00354766bdfc141d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 16 Mar 2023 14:44:40 +0100 Subject: [PATCH 011/143] Update spotless to v6.17.0 (#1132) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a22a8598bc..85e5c95096 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,7 +3,7 @@ kotlin = "1.8.0" neo4j = "4.0.2" log4j = "2.20.0" sonarqube = "4.0.0.2929" -spotless = "6.15.0" +spotless = "6.17.0" [libraries] kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin"} From aa9b93f2be0b1bfa3b24989bd89e3c48b7a8f9a9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 16 Mar 2023 14:00:10 +0000 Subject: [PATCH 012/143] Update module golang.org/x/mod to v0.9.0 (#1131) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- cpg-language-go/src/main/golang/go.mod | 2 +- cpg-language-go/src/main/golang/go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/cpg-language-go/src/main/golang/go.mod b/cpg-language-go/src/main/golang/go.mod index 6074e03f76..e197d08b9d 100644 --- a/cpg-language-go/src/main/golang/go.mod +++ b/cpg-language-go/src/main/golang/go.mod @@ -3,6 +3,6 @@ module cpg go 1.19 require ( - golang.org/x/mod v0.8.0 + golang.org/x/mod v0.9.0 tekao.net/jnigi v0.0.0-20221227053512-56e0101fa996 ) diff --git a/cpg-language-go/src/main/golang/go.sum b/cpg-language-go/src/main/golang/go.sum index 11c27b29a9..ad7da18d55 100644 --- a/cpg-language-go/src/main/golang/go.sum +++ b/cpg-language-go/src/main/golang/go.sum @@ -2,6 +2,8 @@ golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA= golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs= +golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= tekao.net/jnigi v0.0.0-20220921102452-ce6d0be0c331 h1:p5apvrQZPCacG+Ux6GMzLWX4mUZOPlguj0MrONXutrQ= tekao.net/jnigi v0.0.0-20220921102452-ce6d0be0c331/go.mod h1:SmVvXetJ8N0ov5c2eOC+IxmkdYGEyuXghTuBq5HWZ/Y= tekao.net/jnigi v0.0.0-20221227053512-56e0101fa996 h1:Vl0GEBxRKyS1+/fjd9H6ptV7t/CAmfgxtsanvqsCob8= From f2c596d006c028d2beea29a4ca2707012b451477 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 16 Mar 2023 14:10:58 +0000 Subject: [PATCH 013/143] Update dependency typescript to v4.9.5 (#973) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- cpg-language-typescript/src/main/nodejs/package.json | 2 +- cpg-language-typescript/src/main/nodejs/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cpg-language-typescript/src/main/nodejs/package.json b/cpg-language-typescript/src/main/nodejs/package.json index cfd2d6fa0b..ef36cccbd7 100644 --- a/cpg-language-typescript/src/main/nodejs/package.json +++ b/cpg-language-typescript/src/main/nodejs/package.json @@ -7,7 +7,7 @@ }, "dependencies": { "@types/node": "18.15.3", - "typescript": "4.8.2" + "typescript": "4.9.5" }, "license": "Apache-2.0", "devDependencies": { diff --git a/cpg-language-typescript/src/main/nodejs/yarn.lock b/cpg-language-typescript/src/main/nodejs/yarn.lock index 80ee041d09..2d2a0143b6 100644 --- a/cpg-language-typescript/src/main/nodejs/yarn.lock +++ b/cpg-language-typescript/src/main/nodejs/yarn.lock @@ -706,10 +706,10 @@ terser@^5.7.0: commander "^2.20.0" source-map-support "~0.5.20" -typescript@4.8.2: - version "4.8.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.2.tgz#e3b33d5ccfb5914e4eeab6699cf208adee3fd790" - integrity sha512-C0I1UsrrDHo2fYI5oaCGbSejwX4ch+9Y5jTQELvovfmFkK3HHSZJB8MSJcWLmCUBzQBchCrZ9rMRV6GuNrvGtw== +typescript@4.9.5: + version "4.9.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a" + integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== uri-js@^4.2.2: version "4.4.1" From 7543d8ce3e004baade4909df0422d881884176b3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 20 Mar 2023 10:18:26 +0100 Subject: [PATCH 014/143] Update actions/setup-go action to v4 (#1133) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 03f9d6e7b2..5c735e6f3a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -18,7 +18,7 @@ jobs: with: fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of SonarQube analysis - name: Setup Go - uses: actions/setup-go@v3 + uses: actions/setup-go@v4 with: go-version: 1.18 - uses: actions/setup-java@v3 @@ -66,7 +66,7 @@ jobs: run: | docker run -d --env NEO4J_AUTH=neo4j/password -p7474:7474 -p7687:7687 neo4j - name: Setup Go - uses: actions/setup-go@v3 + uses: actions/setup-go@v4 with: go-version: 1.18 - name: Cache SonarCloud packages From 812a7718309daa21b81d3cf5497897e4b94b106e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 20 Mar 2023 09:31:48 +0000 Subject: [PATCH 015/143] Update dependency typescript to v5 (#1134) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- cpg-language-typescript/src/main/nodejs/package.json | 2 +- cpg-language-typescript/src/main/nodejs/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cpg-language-typescript/src/main/nodejs/package.json b/cpg-language-typescript/src/main/nodejs/package.json index ef36cccbd7..039c53fdf4 100644 --- a/cpg-language-typescript/src/main/nodejs/package.json +++ b/cpg-language-typescript/src/main/nodejs/package.json @@ -7,7 +7,7 @@ }, "dependencies": { "@types/node": "18.15.3", - "typescript": "4.9.5" + "typescript": "5.0.2" }, "license": "Apache-2.0", "devDependencies": { diff --git a/cpg-language-typescript/src/main/nodejs/yarn.lock b/cpg-language-typescript/src/main/nodejs/yarn.lock index 2d2a0143b6..4413559619 100644 --- a/cpg-language-typescript/src/main/nodejs/yarn.lock +++ b/cpg-language-typescript/src/main/nodejs/yarn.lock @@ -706,10 +706,10 @@ terser@^5.7.0: commander "^2.20.0" source-map-support "~0.5.20" -typescript@4.9.5: - version "4.9.5" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a" - integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== +typescript@5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.0.2.tgz#891e1a90c5189d8506af64b9ef929fca99ba1ee5" + integrity sha512-wVORMBGO/FAs/++blGNeAVdbNKtIh1rbBL2EyQ1+J9lClJ93KiiKe8PmFIVdXhHcyv44SL9oglmfeSsndo0jRw== uri-js@^4.2.2: version "4.4.1" From 65f9116441d01c8c6b22b6e4af920436b65e0401 Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Tue, 21 Mar 2023 14:39:23 +0100 Subject: [PATCH 016/143] Inferring missing functions in namespace declarations (#1114) * Inferring function declarations in namespace declarations Previously, we put all inferred function declarations in the global scope (the current TU). However, there are cases where the function is actually residing in a namespace declaration and we need to put it there. This will not (yet) infer the namespace declaration if it is missing. This might even something that is specific to the frontend, e.g., generate namespace declarations out of import statements. * Less functions because of less duplicates * Added wrapper around jumpTo * Made jumpTo private --- .../fraunhofer/aisec/cpg/PleaseBeCareful.kt | 32 +++ .../de/fraunhofer/aisec/cpg/ScopeManager.kt | 49 ++++- .../aisec/cpg/frontends/cpp/CPPLanguage.kt | 3 +- .../aisec/cpg/passes/CallResolver.kt | 29 ++- .../aisec/cpg/passes/VariableUsageResolver.kt | 5 +- .../aisec/cpg/passes/inference/Inference.kt | 190 +++++++++++------- .../aisec/cpg/passes/CallResolverTest.kt | 1 + .../aisec/cpg/frontends/golang/GoLanguage.kt | 4 +- .../golang/GoLanguageFrontendTest.kt | 14 +- .../aisec/cpg_vis_neo4j/ApplicationTest.kt | 5 +- 10 files changed, 240 insertions(+), 92 deletions(-) create mode 100644 cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/PleaseBeCareful.kt diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/PleaseBeCareful.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/PleaseBeCareful.kt new file mode 100644 index 0000000000..2a93f319e0 --- /dev/null +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/PleaseBeCareful.kt @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2023, 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 + +/** + * Annotation used to mark functions that could potentially be dangerous, in a way that could modify + * or alter the CPG tree structure that one does not intent to. + */ +annotation class PleaseBeCareful 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 95d50afc49..86663a010e 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 @@ -441,7 +441,9 @@ class ScopeManager : ScopeProvider { /** This function returns the [Scope] associated with a node. */ fun lookupScope(node: Node): Scope? { - return scopeMap[node] + return if (node is TranslationUnitDeclaration) { + globalScope + } else scopeMap[node] } /** This function looks up scope by its FQN. This only works for [NameScope]s */ @@ -635,13 +637,19 @@ class ScopeManager : ScopeProvider { call: CallExpression, scope: Scope? = currentScope ): List { + val s = extractScope(call.name, scope) + + return resolve(s) { it.name.lastPartsMatch(call.name) && it.hasSignature(call.signature) } + } + + fun extractScope(name: Name, scope: Scope? = currentScope): Scope? { var s = scope // First, we need to check, whether we have some kind of scoping. - if (call.language != null && call.name.parent != null) { + if (name.parent != null) { // extract the scope name, it is usually a name space, but could probably be something // else as well in other languages - val scopeName = call.name.parent + val scopeName = name.parent // TODO: proper scope selection @@ -650,17 +658,44 @@ class ScopeManager : ScopeProvider { s = if (scopes.isEmpty()) { LOGGER.error( - "Could not find the scope {} needed to resolve the call {}. Falling back to the current scope", + "Could not find the scope {} needed to resolve the call {}. Falling back to the default (current) scope", scopeName, - call.name + name ) - currentScope + scope } else { scopes[0] } } - return resolve(s) { it.name.lastPartsMatch(call.name) && it.hasSignature(call.signature) } + return s + } + + /** + * Directly jumps to a given scope. Returns the previous scope. Do not forget to set the scope + * back to the old scope after performing the actions inside this scope. + * + * Handle with care, here be dragons. Should not be exposed outside of the cpg-core module. + */ + @PleaseBeCareful + private fun jumpTo(scope: Scope?): Scope? { + val oldScope = currentScope + currentScope = scope + return oldScope + } + + /** + * This function can be used to execute multiple statements contained in [init] in the scope of + * [scope]. The specified scope will be selected using [jumpTo]. The last expression in [init] + * will also be used as a return value of this function. This can be useful, if you create + * objects, such as a [Node] inside this scope and want to return it to the calling function. + */ + fun withScope(scope: Scope?, init: () -> T): T { + val oldScope = jumpTo(scope) + val ret = init() + jumpTo(oldScope) + + return ret } fun resolveFunctionStopScopeTraversalOnDefinition( diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CPPLanguage.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CPPLanguage.kt index 1d8c477cf4..d817ee0d79 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CPPLanguage.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CPPLanguage.kt @@ -48,6 +48,7 @@ class CPPLanguage : HasDefaultArguments, HasTemplates, HasComplexCallResolution, + HasStructs, HasClasses, HasUnknownType { override val fileExtensions = listOf("cpp", "cc", "cxx", "hpp", "hh") @@ -300,7 +301,7 @@ class CPPLanguage : // If we want to use an inferred functionTemplateDeclaration, this needs to be provided. // Otherwise, we could not resolve to a template and no modifications are made val functionTemplateDeclaration = - holder.startInference().createInferredFunctionTemplate(templateCall) + holder.startInference(scopeManager).createInferredFunctionTemplate(templateCall) templateCall.templateInstantiation = functionTemplateDeclaration val edges = templateCall.templateParameterEdges // Set instantiation propertyEdges diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolver.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolver.kt index 91d75fa76b..7fe3e0dadf 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolver.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolver.kt @@ -201,7 +201,26 @@ open class CallResolver : SymbolResolverPass() { val suitableBases = getPossibleContainingTypes(call) candidates = if (suitableBases.isEmpty()) { - listOf(currentTU.inferFunction(call)) + // This is not really the most ideal place, but for now this will do. While this + // is definitely a function, it could still be a function inside a namespace. In + // this case, we want to start inference in that particular namespace and not in + // the TU. It is also a little bit redundant, since ScopeManager.resolveFunction + // (which gets called before) already extracts the scope, but this information + // gets lost. + val scope = scopeManager.extractScope(call.name, scopeManager.globalScope) + + // We have two possible start points, a namespace declaration or a translation + // unit. Nothing else is allowed (fow now) + val func = + when (val start = scope?.astNode) { + is TranslationUnitDeclaration -> + start.inferFunction(call, scopeManager = scopeManager) + is NamespaceDeclaration -> + start.inferFunction(call, scopeManager = scopeManager) + else -> null + } + + listOfNotNull(func) } else { createMethodDummies(suitableBases, call) } @@ -390,13 +409,13 @@ open class CallResolver : SymbolResolverPass() { .mapNotNull { var record = recordMap[it.root.name] if (record == null && config?.inferenceConfiguration?.inferRecords == true) { - record = it.startInference().inferRecordDeclaration(it, currentTU) + record = it.startInference(scopeManager).inferRecordDeclaration(it, currentTU) // update the record map if (record != null) it.root.name.let { name -> recordMap[name] = record } } record } - .map { record -> record.inferMethod(call) } + .map { record -> record.inferMethod(call, scopeManager = scopeManager) } } /** @@ -597,7 +616,7 @@ open class CallResolver : SymbolResolverPass() { return constructorCandidate ?: recordDeclaration - .startInference() + .startInference(scopeManager) .createInferredConstructor(constructExpression.signature) } @@ -606,7 +625,7 @@ open class CallResolver : SymbolResolverPass() { recordDeclaration: RecordDeclaration ): ConstructorDeclaration { return recordDeclaration.constructors.firstOrNull { it.hasSignature(signature) } - ?: recordDeclaration.startInference().createInferredConstructor(signature) + ?: recordDeclaration.startInference(scopeManager).createInferredConstructor(signature) } companion object { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/VariableUsageResolver.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/VariableUsageResolver.kt index 7aaa3d4515..2fe86031d3 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/VariableUsageResolver.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/VariableUsageResolver.kt @@ -345,7 +345,8 @@ open class VariableUsageResolver : SymbolResolverPass() { } else { "class" } - val record = base.startInference().inferRecordDeclaration(base, currentTU, kind) + val record = + base.startInference(scopeManager).inferRecordDeclaration(base, currentTU, kind) // update the record map if (record != null) recordMap[base.name] = record } @@ -404,7 +405,7 @@ open class VariableUsageResolver : SymbolResolverPass() { // If we didn't find anything, we create a new function or method declaration return target ?: (declarationHolder ?: currentTU) - .startInference() + .startInference(scopeManager) .createInferredFunctionDeclaration( name, null, diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/Inference.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/Inference.kt index bfd414284a..b148a8f6ed 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/Inference.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/Inference.kt @@ -25,11 +25,13 @@ */ package de.fraunhofer.aisec.cpg.passes.inference +import de.fraunhofer.aisec.cpg.ScopeManager import de.fraunhofer.aisec.cpg.frontends.HasClasses import de.fraunhofer.aisec.cpg.frontends.Language import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.declarations.* +import de.fraunhofer.aisec.cpg.graph.scopes.Scope import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression import de.fraunhofer.aisec.cpg.graph.statements.expressions.TypeExpression @@ -48,12 +50,19 @@ import org.slf4j.LoggerFactory * Since this class implements [IsInferredProvider], all nodes that are created using the node * builder functions, will automatically have [Node.isInferred] set to true. */ -class Inference(val start: Node) : LanguageProvider, IsInferredProvider { +class Inference(val start: Node, val scopeManager: ScopeManager) : + LanguageProvider, ScopeProvider, IsInferredProvider { val log: Logger = LoggerFactory.getLogger(Inference::class.java) override val language: Language? get() = start.language + override val isInferred: Boolean + get() = true + + override val scope: Scope? + get() = scopeManager.currentScope + fun createInferredFunctionDeclaration( name: CharSequence?, code: String?, @@ -61,82 +70,103 @@ class Inference(val start: Node) : LanguageProvider, IsInferredProvider { signature: List, returnType: Type?, ): FunctionDeclaration { - // We assume that the start is either a record or the translation unit + // We assume that the start is either a record, a namespace or the translation unit val record = start as? RecordDeclaration + val namespace = start as? NamespaceDeclaration val tu = start as? TranslationUnitDeclaration - // If both are null, we have the wrong type - if (record == null && tu == null) { + // If all are null, we have the wrong type + if (record == null && namespace == null && tu == null) { throw UnsupportedOperationException( "Starting inference with the wrong type of start node" ) } - log.debug( - "Inferring a new function declaration $name with parameter types ${signature.map { it?.name }}" - ) + return inferInScopeOf(start) { + log.debug( + "Inferring a new function declaration $name with parameter types ${signature.map { it?.name }}" + ) - if ( - record?.isInferred == true && record.kind == "struct" && record.language is HasClasses - ) { // "upgrade" our struct to a class, if it was inferred by us, since we are calling - // methods on it - record.kind = "class" - } + // methods on it. But only if the language supports classes in the first place. + if ( + record?.isInferred == true && + record.kind == "struct" && + record.language is HasClasses + ) { + record.kind = "class" + } - val declarationHolder = (record ?: tu) - val parameters = createInferredParameters(signature) - val inferred: FunctionDeclaration = + val inferred: FunctionDeclaration = + if (record != null) { + newMethodDeclaration(name ?: "", code, isStatic, record) + } else { + newFunctionDeclaration(name ?: "", code) + } + + createInferredParameters(inferred, signature) + + // Set the type and return type(s) + returnType?.let { inferred.returnTypes = listOf(it) } + inferred.type = FunctionType.computeType(inferred) + + // Add it to the scope + scopeManager.addDeclaration(inferred) + + // Some magic that adds it to static imports. Not sure if this really needed if (record != null) { - newMethodDeclaration(name ?: "", code, isStatic, record) - } else { - newFunctionDeclaration(name ?: "", code) + if (isStatic) { + record.staticImports.add(inferred) + } } - inferred.parameters = parameters - // TODO: Once, we used inferred.type = returnType and once the two following statements: - // Why? What's the "right way"? - returnType?.let { - inferred.returnTypes = listOf(it) - inferred.type = returnType + inferred } + } - // TODO: Handle multiple return values? - if (declarationHolder is RecordDeclaration) { - declarationHolder.addMethod(inferred as MethodDeclaration) - if (isStatic) { - declarationHolder.staticImports.add(inferred) - } - } else { - declarationHolder?.addDeclaration(inferred) + fun createInferredConstructor(signature: List): ConstructorDeclaration { + return inferInScopeOf(start) { + val inferred = + newConstructorDeclaration( + start.name.localName, + "", + start as? RecordDeclaration, + ) + createInferredParameters(inferred, signature) + + scopeManager.addDeclaration(inferred) + + inferred } + } - return inferred + /** + * This wrapper should be used around any kind of inference code that actually creates a + * [Declaration]. It takes cares of "jumping" to the appropriate scope of the [start] node, + * executing the commands in [init] (which needs to create an inferred node of [T]) as well as + * restoring the previous scope. + */ + private fun inferInScopeOf(start: Node, init: () -> T): T { + return scopeManager.withScope(scopeManager.lookupScope(start), init) } - fun createInferredConstructor(signature: List): ConstructorDeclaration { - val inferred = - newConstructorDeclaration( - start.name.localName, - "", - start as? RecordDeclaration, - ) - inferred.parameters = createInferredParameters(signature) + private fun createInferredParameters(function: FunctionDeclaration, signature: List) { + // To save some unnecessary scopes, we only want to "enter" the function if it is necessary, + // e.g., if we need to create parameters + if (signature.isNotEmpty()) { + scopeManager.enterScope(function) - (start as? RecordDeclaration)?.addConstructor(inferred) - return inferred - } + for (i in signature.indices) { + val targetType = signature[i] + val paramName = generateParamName(i, targetType!!) + val param = newParamVariableDeclaration(paramName, targetType, false, "") + param.argumentIndex = i + + scopeManager.addDeclaration(param) + } - fun createInferredParameters(signature: List): List { - val params: MutableList = ArrayList() - for (i in signature.indices) { - val targetType = signature[i] - val paramName = generateParamName(i, targetType!!) - val param = newParamVariableDeclaration(paramName, targetType, false, "") - param.argumentIndex = i - params.add(param) + scopeManager.leaveScope(function) } - return params } /** Generates a name for an inferred function parameter based on the type. */ @@ -183,7 +213,7 @@ class Inference(val start: Node) : LanguageProvider, IsInferredProvider { return paramName.toString() } - fun inferNonTypeTemplateParameter(name: String): ParamVariableDeclaration { + private fun inferNonTypeTemplateParameter(name: String): ParamVariableDeclaration { val expr = start as? Expression ?: throw UnsupportedOperationException( @@ -194,7 +224,7 @@ class Inference(val start: Node) : LanguageProvider, IsInferredProvider { return newParamVariableDeclaration(name, expr.type, false, name) } - fun inferTemplateParameter( + private fun inferTemplateParameter( name: String, ): TypeParamDeclaration { val parameterizedType = ParameterizedType(name, language) @@ -211,7 +241,6 @@ class Inference(val start: Node) : LanguageProvider, IsInferredProvider { * Create an inferred FunctionTemplateDeclaration if a call to an FunctionTemplate could not be * resolved * - * @param containingRecord * @param call * @return inferred FunctionTemplateDeclaration which can be invoked by the call */ @@ -235,10 +264,10 @@ class Inference(val start: Node) : LanguageProvider, IsInferredProvider { val inferredRealization: FunctionDeclaration = if (record != null) { record.addDeclaration(inferred) - record.inferMethod(call) + record.inferMethod(call, scopeManager = scopeManager) } else { tu!!.addDeclaration(inferred) - tu.inferFunction(call) + tu.inferFunction(call, scopeManager = scopeManager) } inferred.addRealization(inferredRealization) @@ -250,13 +279,17 @@ class Inference(val start: Node) : LanguageProvider, IsInferredProvider { // Template Parameter val inferredTypeIdentifier = "T$typeCounter" val typeParamDeclaration = - inferred.startInference().inferTemplateParameter(inferredTypeIdentifier) + inferred + .startInference(scopeManager) + .inferTemplateParameter(inferredTypeIdentifier) typeCounter++ inferred.addParameter(typeParamDeclaration) } else if (node is Expression) { val inferredNonTypeIdentifier = "N$nonTypeCounter" val paramVariableDeclaration = - node.startInference().inferNonTypeTemplateParameter(inferredNonTypeIdentifier) + node + .startInference(scopeManager) + .inferNonTypeTemplateParameter(inferredNonTypeIdentifier) paramVariableDeclaration.addPrevDFG(node) node.addNextDFG(paramVariableDeclaration) @@ -269,8 +302,7 @@ class Inference(val start: Node) : LanguageProvider, IsInferredProvider { /** * Infers a record declaration for the given type. [type] is the object type representing a - * record that we want to infer, the [recordToUpdate] is either the type's name or the type's - * root name. The [kind] specifies if we create a class or a struct. + * record that we want to infer. The [kind] specifies if we create a class or a struct. */ fun inferRecordDeclaration( type: Type, @@ -299,9 +331,6 @@ class Inference(val start: Node) : LanguageProvider, IsInferredProvider { currentTU.addDeclaration(declaration) return declaration } - - override val isInferred: Boolean - get() = true } /** Provides information about the inference status of a node. */ @@ -310,14 +339,15 @@ interface IsInferredProvider : MetadataProvider { } /** Returns a new [Inference] object starting from this node. */ -fun Node.startInference() = Inference(this) +fun Node.startInference(scopeManager: ScopeManager) = Inference(this, scopeManager) /** Tries to infer a [FunctionDeclaration] from a [CallExpression]. */ fun TranslationUnitDeclaration.inferFunction( call: CallExpression, - isStatic: Boolean = false + isStatic: Boolean = false, + scopeManager: ScopeManager, ): FunctionDeclaration { - return Inference(this) + return Inference(this, scopeManager) .createInferredFunctionDeclaration( call.name.localName, call.code, @@ -328,12 +358,30 @@ fun TranslationUnitDeclaration.inferFunction( ) } +/** Tries to infer a [FunctionDeclaration] from a [CallExpression]. */ +fun NamespaceDeclaration.inferFunction( + call: CallExpression, + isStatic: Boolean = false, + scopeManager: ScopeManager, +): FunctionDeclaration { + return Inference(this, scopeManager) + .createInferredFunctionDeclaration( + call.name, + call.code, + isStatic, + call.signature, + // TODO: Is the call's type the return value's type? + call.type + ) +} + /** Tries to infer a [MethodDeclaration] from a [CallExpression]. */ fun RecordDeclaration.inferMethod( call: CallExpression, - isStatic: Boolean = false + isStatic: Boolean = false, + scopeManager: ScopeManager ): MethodDeclaration { - return Inference(this) + return Inference(this, scopeManager) .createInferredFunctionDeclaration( call.name.localName, call.code, diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolverTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolverTest.kt index 495dc17549..13faeffb4a 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolverTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolverTest.kt @@ -117,6 +117,7 @@ class CallResolverTest : BaseTest() { val inferenceSignature = listOf(intType, intType, intType) for (inferredCall in calls.filter { c: CallExpression -> c.signature == inferenceSignature }) { + val inferredTarget = findByUniquePredicate(methods) { m: FunctionDeclaration -> m.hasSignature(inferenceSignature) diff --git a/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoLanguage.kt b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoLanguage.kt index 7e25efa95f..62a1e748ca 100644 --- a/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoLanguage.kt +++ b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoLanguage.kt @@ -29,12 +29,14 @@ import de.fraunhofer.aisec.cpg.ScopeManager import de.fraunhofer.aisec.cpg.TranslationConfiguration import de.fraunhofer.aisec.cpg.frontends.HasGenerics import de.fraunhofer.aisec.cpg.frontends.HasShortCircuitOperators +import de.fraunhofer.aisec.cpg.frontends.HasStructs import de.fraunhofer.aisec.cpg.frontends.Language import de.fraunhofer.aisec.cpg.graph.types.* import org.neo4j.ogm.annotation.Transient /** The Go language. */ -class GoLanguage : Language(), HasShortCircuitOperators, HasGenerics { +class GoLanguage : + Language(), HasShortCircuitOperators, HasGenerics, HasStructs { override val fileExtensions = listOf("go") override val namespaceDelimiter = "." @Transient override val frontend = GoLanguageFrontend::class diff --git a/cpg-language-go/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoLanguageFrontendTest.kt b/cpg-language-go/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoLanguageFrontendTest.kt index 2ffdb45db9..ff69b57ded 100644 --- a/cpg-language-go/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoLanguageFrontendTest.kt +++ b/cpg-language-go/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoLanguageFrontendTest.kt @@ -37,10 +37,7 @@ import de.fraunhofer.aisec.cpg.graph.statements.expressions.* import de.fraunhofer.aisec.cpg.graph.types.FunctionType import de.fraunhofer.aisec.cpg.graph.types.TypeParser import java.nio.file.Path -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertNotNull -import kotlin.test.assertTrue +import kotlin.test.* class GoLanguageFrontendTest : BaseTest() { @@ -679,6 +676,15 @@ class GoLanguageFrontendTest : BaseTest() { val call = (a.singleDeclaration as? VariableDeclaration)?.initializer as? CallExpression assertNotNull(call) assertTrue(call.invokes.contains(newAwesome)) + + val util = result.namespaces["util"] + assertNotNull(util) + + // Check, if we correctly inferred this function in the namespace + val doSomethingElse = util.functions["DoSomethingElse"] + assertNotNull(doSomethingElse) + assertTrue(doSomethingElse.isInferred) + assertSame(util, doSomethingElse.scope?.astNode) } @Test diff --git a/cpg-neo4j/src/test/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/ApplicationTest.kt b/cpg-neo4j/src/test/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/ApplicationTest.kt index 0f9a88e3b4..2d1d442867 100644 --- a/cpg-neo4j/src/test/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/ApplicationTest.kt +++ b/cpg-neo4j/src/test/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/ApplicationTest.kt @@ -29,6 +29,7 @@ import de.fraunhofer.aisec.cpg.TranslationConfiguration import de.fraunhofer.aisec.cpg.TranslationManager import de.fraunhofer.aisec.cpg.TranslationResult import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration +import de.fraunhofer.aisec.cpg.graph.functions import java.io.File import java.nio.file.Paths import kotlin.test.Test @@ -60,6 +61,8 @@ class ApplicationTest { TranslationManager.builder().config(translationConfiguration).build() translationResult = translationManager.analyze().get() + assertEquals(31, translationResult.functions.size) + val application = Application() application.pushToNeo4j(translationResult!!) @@ -71,7 +74,7 @@ class ApplicationTest { val functions = session.loadAll(FunctionDeclaration::class.java) assertNotNull(functions) - assertEquals(38, functions.size) + assertEquals(31, functions.size) transaction.commit() } From 32a9ebecbf950f058612984a9980828f1695da7e Mon Sep 17 00:00:00 2001 From: keremc <5321759+keremc@users.noreply.github.com> Date: Fri, 24 Mar 2023 21:41:09 +0300 Subject: [PATCH 017/143] Consider implicit casts in C call resolution (#1138) * Move resolveWithImplicitCastFunc to CLanguage * Implement HasComplexCallResolution in CLanguage * ./gradlew :cpg-core:spotlessApply --- .../aisec/cpg/frontends/cpp/CLanguage.kt | 55 ++++++++++++++++++- .../aisec/cpg/frontends/cpp/CPPLanguage.kt | 18 ------ 2 files changed, 54 insertions(+), 19 deletions(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CLanguage.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CLanguage.kt index 474fdfed5e..511dc5fa2c 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CLanguage.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CLanguage.kt @@ -29,13 +29,24 @@ import com.fasterxml.jackson.annotation.JsonIgnore import de.fraunhofer.aisec.cpg.ScopeManager import de.fraunhofer.aisec.cpg.TranslationConfiguration import de.fraunhofer.aisec.cpg.frontends.* -import de.fraunhofer.aisec.cpg.graph.types.* +import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration +import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration +import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration +import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression +import de.fraunhofer.aisec.cpg.graph.types.FloatingPointType +import de.fraunhofer.aisec.cpg.graph.types.IntegerType +import de.fraunhofer.aisec.cpg.graph.types.NumericType +import de.fraunhofer.aisec.cpg.graph.types.Type +import de.fraunhofer.aisec.cpg.passes.CallResolver +import de.fraunhofer.aisec.cpg.passes.resolveWithImplicitCast +import java.util.regex.Pattern import kotlin.reflect.KClass import org.neo4j.ogm.annotation.Transient /** The C language. */ open class CLanguage : Language(), + HasComplexCallResolution, HasStructs, HasFunctionPointers, HasQualifier, @@ -101,4 +112,46 @@ open class CLanguage : ): CXXLanguageFrontend { return CXXLanguageFrontend(this, config, scopeManager) } + + override fun refineNormalCallResolution( + call: CallExpression, + scopeManager: ScopeManager, + currentTU: TranslationUnitDeclaration + ): List { + val invocationCandidates = scopeManager.resolveFunction(call).toMutableList() + if (invocationCandidates.isEmpty()) { + // Check for implicit casts + invocationCandidates.addAll(resolveWithImplicitCastFunc(call, scopeManager)) + } + return invocationCandidates + } + + override fun refineMethodCallResolution( + curClass: RecordDeclaration?, + possibleContainingTypes: Set, + call: CallExpression, + scopeManager: ScopeManager, + currentTU: TranslationUnitDeclaration, + callResolver: CallResolver + ): List = emptyList() + + override fun refineInvocationCandidatesFromRecord( + recordDeclaration: RecordDeclaration, + call: CallExpression, + namePattern: Pattern + ): List = emptyList() + + /** + * @param call we want to find invocation targets for by performing implicit casts + * @param scopeManager the scope manager used + * @return list of invocation candidates by applying implicit casts + */ + protected fun resolveWithImplicitCastFunc( + call: CallExpression, + scopeManager: ScopeManager + ): List { + val initialInvocationCandidates = + listOf(*scopeManager.resolveFunctionStopScopeTraversalOnDefinition(call).toTypedArray()) + return resolveWithImplicitCast(call, initialInvocationCandidates) + } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CPPLanguage.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CPPLanguage.kt index d817ee0d79..09af4db425 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CPPLanguage.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CPPLanguage.kt @@ -27,10 +27,6 @@ package de.fraunhofer.aisec.cpg.frontends.cpp import de.fraunhofer.aisec.cpg.ScopeManager import de.fraunhofer.aisec.cpg.frontends.* -import de.fraunhofer.aisec.cpg.frontends.HasClasses -import de.fraunhofer.aisec.cpg.frontends.HasComplexCallResolution -import de.fraunhofer.aisec.cpg.frontends.HasDefaultArguments -import de.fraunhofer.aisec.cpg.frontends.HasTemplates import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.declarations.* import de.fraunhofer.aisec.cpg.graph.edge.Properties @@ -317,18 +313,4 @@ class CPPLanguage : return Pair(false, listOf()) } - - /** - * @param call we want to find invocation targets for by performing implicit casts - * @param scopeManager the scope manager used - * @return list of invocation candidates by applying implicit casts - */ - private fun resolveWithImplicitCastFunc( - call: CallExpression, - scopeManager: ScopeManager - ): List { - val initialInvocationCandidates = - listOf(*scopeManager.resolveFunctionStopScopeTraversalOnDefinition(call).toTypedArray()) - return resolveWithImplicitCast(call, initialInvocationCandidates) - } } From 85d81208315e3c93c6734f55a214dd1a983120c0 Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Sat, 25 Mar 2023 11:36:06 +0100 Subject: [PATCH 018/143] Made the `FunctionPointerCallResolver` compatible with the `ControlFlowSensitiveDFGPass` (#1136) * Made the `FunctionPointerCallResolver` compatible with the `ControlFlowSensitiveDFGPass` The function pointer call resolver pass had a hidden blocking dependency on the `ControlFlowSensitiveDFGPass`, in a way that it needed to be executed BEFORE the control flow sensitive DFG was built. This was/is because of inconsistencies between both the "regular" DFG pass and the control-flow sensitive one (which we need to tackle in a seperate PR). This PR makes resolving function pointers a little bit cleaner, since basically it tries to resolve every `callee` of a `CallExpression` that is of `FunctionPointerType`, regardless of any AST structure or other weird resolving. This also moves the `VariableUsageResovler` a little bit closer to resolving not just variables, but also functions (since in the long term we want to merge it with the `CallResolver`) * Addressed review comment * Duplicated test with and without control-flow-DFG. Can be merged into one at some point again. * Trying to simplify handleFunctionPointerCall --- .../aisec/cpg/TranslationConfiguration.kt | 2 +- .../cpg/passes/FunctionPointerCallResolver.kt | 53 +++------- .../aisec/cpg/passes/VariableUsageResolver.kt | 25 ++++- .../frontends/cpp/CXXLanguageFrontendTest.kt | 97 +++++++++++++++++++ cpg-core/src/test/resources/c/func_ptr_call.c | 24 +++++ 5 files changed, 156 insertions(+), 45 deletions(-) create mode 100644 cpg-core/src/test/resources/c/func_ptr_call.c diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationConfiguration.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationConfiguration.kt index dc2d737edb..437da671e9 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationConfiguration.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationConfiguration.kt @@ -439,10 +439,10 @@ private constructor( registerPass(VariableUsageResolver()) registerPass(CallResolver()) // creates CG registerPass(DFGPass()) - registerPass(FunctionPointerCallResolver()) registerPass(EvaluationOrderGraphPass()) // creates EOG registerPass(TypeResolver()) registerPass(ControlFlowSensitiveDFGPass()) + registerPass(FunctionPointerCallResolver()) registerPass(FilenameMapper()) return this } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/FunctionPointerCallResolver.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/FunctionPointerCallResolver.kt index 3660bb239b..483a13601e 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/FunctionPointerCallResolver.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/FunctionPointerCallResolver.kt @@ -27,20 +27,13 @@ package de.fraunhofer.aisec.cpg.passes import de.fraunhofer.aisec.cpg.TranslationResult import de.fraunhofer.aisec.cpg.frontends.cpp.CXXLanguageFrontend -import de.fraunhofer.aisec.cpg.graph.HasType import de.fraunhofer.aisec.cpg.graph.Node -import de.fraunhofer.aisec.cpg.graph.TypeManager import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration -import de.fraunhofer.aisec.cpg.graph.declarations.ValueDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration -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.LambdaExpression -import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberCallExpression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.* import de.fraunhofer.aisec.cpg.graph.types.FunctionPointerType -import de.fraunhofer.aisec.cpg.graph.types.IncompleteType -import de.fraunhofer.aisec.cpg.graph.types.Type +import de.fraunhofer.aisec.cpg.graph.types.PointerType import de.fraunhofer.aisec.cpg.helpers.IdentitySet import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker.ScopedWalker import de.fraunhofer.aisec.cpg.passes.order.DependsOn @@ -88,21 +81,13 @@ class FunctionPointerCallResolver : Pass() { } /** - * Resolves function pointers in a [CallExpression] node. We could be referring to a function - * pointer even though it is not a member call if the usual function pointer syntax (*fp)() has - * been omitted: fp(). Looks like a normal call, but it isn't. + * Resolves function pointers in a [CallExpression] node. As long as the [CallExpression.callee] + * has a [FunctionPointerType], we should be able to resolve it. */ private fun handleCallExpression(call: CallExpression) { - // Since we are using a scoped walker, we can access the current scope here and try to - // resolve the call expression to a declaration that contains the pointer. - val pointer = - scopeManager - .resolve(scopeManager.currentScope, true) { - it.type is FunctionPointerType && it.name.lastPartsMatch(call.name) - } - .firstOrNull() - if (pointer != null) { - handleFunctionPointerCall(call, pointer) + val callee = call.callee + if (callee?.type is FunctionPointerType) { + handleFunctionPointerCall(call, callee) } } @@ -118,8 +103,8 @@ class FunctionPointerCallResolver : Pass() { } } - private fun handleFunctionPointerCall(call: CallExpression, pointer: Node?) { - val pointerType = (pointer as HasType).type as FunctionPointerType + private fun handleFunctionPointerCall(call: CallExpression, pointer: Expression) { + val pointerType = pointer.type as FunctionPointerType val invocationCandidates: MutableList = ArrayList() val work: Deque = ArrayDeque() val seen = IdentitySet() @@ -129,6 +114,7 @@ class FunctionPointerCallResolver : Pass() { if (!seen.add(curr)) { continue } + val isLambda = curr is VariableDeclaration && curr.initializer is LambdaExpression val currentFunction = if (isLambda) { @@ -141,17 +127,8 @@ class FunctionPointerCallResolver : Pass() { // Even if it is a function declaration, the dataflow might just come from a // situation where the target of a fptr is passed through via a return value. Keep // searching if return type or signature don't match - - // In some languages, there might be no explicit return type. In this case we are - // using a single void return type. - val returnType: Type = - if (currentFunction.returnTypes.isEmpty()) { - IncompleteType() - } else { - // TODO(oxisto): support multiple return types - currentFunction.returnTypes[0] - } - + val functionPointerType = + currentFunction.type.reference(PointerType.PointerOrigin.POINTER) if ( isLambda && currentFunction.returnTypes.isEmpty() && @@ -159,11 +136,7 @@ class FunctionPointerCallResolver : Pass() { ) { invocationCandidates.add(currentFunction) continue - } else if ( - TypeManager.getInstance() - .isSupertypeOf(pointerType.returnType, returnType, call) && - currentFunction.hasSignature(pointerType.parameters) - ) { + } else if (functionPointerType == pointerType) { invocationCandidates.add(currentFunction) // We have found a target. Don't follow this path any further, but still // continue the other paths that might be left, as we could have several diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/VariableUsageResolver.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/VariableUsageResolver.kt index 2fe86031d3..e94f81b0dc 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/VariableUsageResolver.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/VariableUsageResolver.kt @@ -118,13 +118,30 @@ open class VariableUsageResolver : SymbolResolverPass() { // 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 - // property to actually resolve the function call. + // property to actually resolve the function call. However, there is a special case that + // we want to catch already, that is if we are "calling" a reference to a variable. This + // can be done in several languages, e.g., in C/C++ as function pointers or in Go as + // function references. In this case, we want to resolve the declared reference expression + // of this call expression back to its original variable declaration. In the future, we want + // to extend this particular code to resolve all callee references to their declarations, + // i.e., their function definitions and get rid of the separate CallResolver. + var wouldResolveTo: Declaration? = null if (parent is CallExpression && parent.callee === current) { - return + // Peek into the declaration, and if it is a variable, we can proceed normally, as we + // are running into the special case explained above. Otherwise, we abort here (for + // now). + wouldResolveTo = scopeManager.resolveReference(current, current.scope) + if (wouldResolveTo !is VariableDeclaration) { + return + } } - // only consider resolving, if the language frontend did not specify a resolution - var refersTo = current.refersTo ?: scopeManager.resolveReference(current, current.scope) + // Only consider resolving, if the language frontend did not specify a resolution. If we + // already have populated the wouldResolveTo variable, we can re-use this instead of + // resolving again + var refersTo = + current.refersTo + ?: wouldResolveTo ?: scopeManager.resolveReference(current, current.scope) var recordDeclType: Type? = null if (currentClass != null) { diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CXXLanguageFrontendTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CXXLanguageFrontendTest.kt index 55e606dfc9..e7c46e5c51 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CXXLanguageFrontendTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CXXLanguageFrontendTest.kt @@ -40,6 +40,7 @@ import de.fraunhofer.aisec.cpg.graph.declarations.* import de.fraunhofer.aisec.cpg.graph.statements.* import de.fraunhofer.aisec.cpg.graph.statements.expressions.* import de.fraunhofer.aisec.cpg.graph.types.* +import de.fraunhofer.aisec.cpg.passes.* import de.fraunhofer.aisec.cpg.processing.IVisitor import de.fraunhofer.aisec.cpg.processing.strategy.Strategy import de.fraunhofer.aisec.cpg.sarif.Region @@ -1413,6 +1414,102 @@ internal class CXXLanguageFrontendTest : BaseTest() { assertRefersTo(callee.rhs, singleParam) } + @Test + @Throws(Exception::class) + fun testFunctionPointerCallWithCDFG() { + val file = File("src/test/resources/c/func_ptr_call.c") + val tu = + analyzeAndGetFirstTU(listOf(file), file.parentFile.toPath(), false) { + it.registerPass(TypeHierarchyResolver()) + it.registerPass(ImportResolver()) + it.registerPass(VariableUsageResolver()) + it.registerPass(CallResolver()) // creates CG + it.registerPass(DFGPass()) + it.registerPass(EvaluationOrderGraphPass()) // creates EOG + it.registerPass(TypeResolver()) + it.registerPass(ControlFlowSensitiveDFGPass()) + it.registerPass(FunctionPointerCallResolver()) + it.registerPass(FilenameMapper()) + } + + val target = tu.functions["target"] + assertNotNull(target) + + val main = tu.functions["main"] + assertNotNull(main) + + // We do not want any inferred functions + assertTrue(tu.functions.none { it.isInferred }) + + val noParamPointerCall = tu.calls("no_param").firstOrNull { it.callee is UnaryOperator } + assertInvokes(assertNotNull(noParamPointerCall), target) + + val noParamNoInitPointerCall = + tu.calls("no_param_uninitialized").firstOrNull { it.callee is UnaryOperator } + assertInvokes(assertNotNull(noParamNoInitPointerCall), target) + + val noParamCall = + tu.calls("no_param").firstOrNull { it.callee is DeclaredReferenceExpression } + assertInvokes(assertNotNull(noParamCall), target) + + val noParamNoInitCall = + tu.calls("no_param_uninitialized").firstOrNull { + it.callee is DeclaredReferenceExpression + } + assertInvokes(assertNotNull(noParamNoInitCall), target) + + val targetCall = tu.calls["target"] + assertInvokes(assertNotNull(targetCall), target) + } + + @Test + @Throws(Exception::class) + fun testFunctionPointerCallWithNormalDFG() { + val file = File("src/test/resources/c/func_ptr_call.c") + val tu = + analyzeAndGetFirstTU(listOf(file), file.parentFile.toPath(), false) { + it.registerPass(TypeHierarchyResolver()) + it.registerPass(ImportResolver()) + it.registerPass(VariableUsageResolver()) + it.registerPass(CallResolver()) // creates CG + it.registerPass(DFGPass()) + it.registerPass(EvaluationOrderGraphPass()) // creates EOG + it.registerPass(TypeResolver()) + it.registerPass(FunctionPointerCallResolver()) + it.registerPass(ControlFlowSensitiveDFGPass()) + it.registerPass(FilenameMapper()) + } + + val target = tu.functions["target"] + assertNotNull(target) + + val main = tu.functions["main"] + assertNotNull(main) + + // We do not want any inferred functions + assertTrue(tu.functions.none { it.isInferred }) + + val noParamPointerCall = tu.calls("no_param").firstOrNull { it.callee is UnaryOperator } + assertInvokes(assertNotNull(noParamPointerCall), target) + + val noParamNoInitPointerCall = + tu.calls("no_param_uninitialized").firstOrNull { it.callee is UnaryOperator } + assertInvokes(assertNotNull(noParamNoInitPointerCall), target) + + val noParamCall = + tu.calls("no_param").firstOrNull { it.callee is DeclaredReferenceExpression } + assertInvokes(assertNotNull(noParamCall), target) + + val noParamNoInitCall = + tu.calls("no_param_uninitialized").firstOrNull { + it.callee is DeclaredReferenceExpression + } + assertInvokes(assertNotNull(noParamNoInitCall), target) + + val targetCall = tu.calls["target"] + assertInvokes(assertNotNull(targetCall), target) + } + @Test @Throws(Exception::class) fun testNamespacedFunction() { diff --git a/cpg-core/src/test/resources/c/func_ptr_call.c b/cpg-core/src/test/resources/c/func_ptr_call.c new file mode 100644 index 0000000000..85abddcac3 --- /dev/null +++ b/cpg-core/src/test/resources/c/func_ptr_call.c @@ -0,0 +1,24 @@ +void target() {} + +int main() { + // Declares a function pointer with an initializer set to a function + void (*no_param)() = ⌖ + // Declares a function pointer without an initial value + void (*no_param_uninitialized) (); + + // Sets the second function pointer to the function + no_param_uninitialized = ⌖ + + // The following syntax is the "normal" syntax that is usually + // used by de-referencing the pointer and calling it + (*no_param)(); + (*no_param_uninitialized)(); + + // However, C/C++ also allows us to directly call the function pointer + // with a syntax that is indistinguishable from a regular function call + no_param(); + no_param_uninitialized(); + + // We can of course also just directly call our target function + target(); +} From b661682a1b48fb9fc9cdd65ab8786c8d5d42b30a Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Sat, 1 Apr 2023 21:35:59 +0200 Subject: [PATCH 019/143] Trying to merge code coverage (#1144) --- .github/workflows/build.yml | 8 ++++---- build.gradle.kts | 14 +++++--------- cpg-all/build.gradle.kts | 10 +++++++++- 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5c735e6f3a..81e9ed4cac 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -111,7 +111,7 @@ jobs: run: | if [ "$SONAR_TOKEN" != "" ] then - ./gradlew --no-daemon --parallel -Pversion=$VERSION -Pexperimental -PenableJavaFrontend=true -PenableGoFrontend=true -PenablePythonFrontend=true -PenableLLVMFrontend=true -PenableTypeScriptFrontend=true -Pintegration spotlessCheck -x spotlessApply build -x distZip -x distTar sonar \ + ./gradlew --no-daemon --parallel -Pversion=$VERSION -Pexperimental -PenableJavaFrontend=true -PenableGoFrontend=true -PenablePythonFrontend=true -PenableLLVMFrontend=true -PenableTypeScriptFrontend=true -Pintegration spotlessCheck -x spotlessApply build -x distZip -x distTar testCodeCoverageReport sonar \ -Dsonar.projectKey=Fraunhofer-AISEC_cpg \ -Dsonar.organization=fraunhofer-aisec \ -Dsonar.host.url=https://sonarcloud.io \ @@ -124,12 +124,12 @@ jobs: VERSION: ${{ env.version }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Archive test reports + - name: Archive test and coverage reports if: ${{ always() }} uses: actions/upload-artifact@v3 with: - name: test - path: "**/build/reports/tests" + name: reports + path: "**/build/reports" - name: Publish if: startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, 'beta') && !contains(github.ref, 'alpha') run: | diff --git a/build.gradle.kts b/build.gradle.kts index c0130136d2..1ba55a7509 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -46,21 +46,17 @@ tasks.dokkaHtmlMultiModule { // // Configure sonarqube for the whole cpg project // -// the submodules do not configure sonarqube -// this makes sure that jacoco reports are generated when executing the top-level 'sonar' task -// so that the whole cpg project gets one combined coverage report -tasks.sonar { - subprojects.forEach { - dependsOn(it.tasks.withType()) - } -} - sonarqube { properties { property("sonar.sourceEncoding", "UTF-8") + // The report part is either relative to the submodules or the main module. We want to specify our + // aggregated jacoco report here + property("sonar.coverage.jacoco.xmlReportPaths", "../cpg-all/build/reports/jacoco/testCodeCoverageReport/testCodeCoverageReport.xml,cpg-all/build/reports/jacoco/testCodeCoverageReport/testCodeCoverageReport.xml") } } + + // // Load the properties that define which frontends to include // diff --git a/cpg-all/build.gradle.kts b/cpg-all/build.gradle.kts index 5ae6e85442..3a421d3d18 100644 --- a/cpg-all/build.gradle.kts +++ b/cpg-all/build.gradle.kts @@ -1,6 +1,7 @@ plugins { id("cpg.library-conventions") id("cpg.frontend-dependency-conventions") + id("jacoco-report-aggregation") } publishing { @@ -16,9 +17,16 @@ publishing { } } +// Make the sonarqube task depend on the aggregated code coverage report +tasks.getByPath(":sonar").dependsOn(tasks.named("testCodeCoverageReport")) dependencies { // this exposes all of our (published) modules as dependency api(projects.cpgCore) api(projects.cpgAnalysis) -} \ No newline at end of file + api(projects.cpgNeo4j) + + jacocoAggregation(projects.cpgCore) + jacocoAggregation(projects.cpgAnalysis) + jacocoAggregation(projects.cpgNeo4j) +} From c91fad667fe2dde8013b4716925d1a10267041cd Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 2 Apr 2023 04:28:33 +0200 Subject: [PATCH 020/143] Update dependency webpack to v5.77.0 (#1145) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- cpg-language-typescript/src/main/nodejs/package.json | 2 +- cpg-language-typescript/src/main/nodejs/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cpg-language-typescript/src/main/nodejs/package.json b/cpg-language-typescript/src/main/nodejs/package.json index 039c53fdf4..2aa9af733f 100644 --- a/cpg-language-typescript/src/main/nodejs/package.json +++ b/cpg-language-typescript/src/main/nodejs/package.json @@ -11,7 +11,7 @@ }, "license": "Apache-2.0", "devDependencies": { - "webpack": "5.76.0", + "webpack": "5.77.0", "webpack-cli": "5.0.0" } } \ No newline at end of file diff --git a/cpg-language-typescript/src/main/nodejs/yarn.lock b/cpg-language-typescript/src/main/nodejs/yarn.lock index 4413559619..4a2c679206 100644 --- a/cpg-language-typescript/src/main/nodejs/yarn.lock +++ b/cpg-language-typescript/src/main/nodejs/yarn.lock @@ -758,10 +758,10 @@ webpack-sources@^3.2.3: resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== -webpack@5.76.0: - version "5.76.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.76.0.tgz#f9fb9fb8c4a7dbdcd0d56a98e56b8a942ee2692c" - integrity sha512-l5sOdYBDunyf72HW8dF23rFtWq/7Zgvt/9ftMof71E/yUb1YLOBmTgA2K4vQthB3kotMrSj609txVE0dnr2fjA== +webpack@5.77.0: + version "5.77.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.77.0.tgz#dea3ad16d7ea6b84aa55fa42f4eac9f30e7eb9b4" + integrity sha512-sbGNjBr5Ya5ss91yzjeJTLKyfiwo5C628AFjEa6WSXcZa4E+F57om3Cc8xLb1Jh0b243AWuSYRf3dn7HVeFQ9Q== dependencies: "@types/eslint-scope" "^3.7.3" "@types/estree" "^0.0.51" From deb75bebf2abcb1e83060b75688ff86d87e72100 Mon Sep 17 00:00:00 2001 From: KuechA <31155350+KuechA@users.noreply.github.com> Date: Sun, 2 Apr 2023 04:50:44 +0200 Subject: [PATCH 021/143] Annotate properties with PopulatedByPass (#1143) --- .../de/fraunhofer/aisec/cpg/PopulatedByPass.kt | 2 +- .../kotlin/de/fraunhofer/aisec/cpg/graph/Node.kt | 16 ++++++++++++++-- .../cpg/graph/declarations/ValueDeclaration.kt | 7 +++++-- .../statements/expressions/CallExpression.kt | 4 +++- .../expressions/DeclaredReferenceExpression.kt | 3 +++ .../de/fraunhofer/aisec/cpg/graph/types/Type.kt | 3 +++ .../aisec/cpg/passes/TypeHierarchyResolver.kt | 2 +- 7 files changed, 30 insertions(+), 7 deletions(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/PopulatedByPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/PopulatedByPass.kt index d6121a0f51..594efcd9a4 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/PopulatedByPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/PopulatedByPass.kt @@ -32,4 +32,4 @@ import kotlin.reflect.KClass * This annotation denotes that, this property is populates by a pass. Optionally, also specifying * which Pass class is responsible. */ -annotation class PopulatedByPass(val value: KClass) +annotation class PopulatedByPass(vararg val value: KClass) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Node.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Node.kt index 9c5ccebced..05f4144bb7 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Node.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Node.kt @@ -26,6 +26,7 @@ package de.fraunhofer.aisec.cpg.graph import com.fasterxml.jackson.annotation.JsonBackReference +import de.fraunhofer.aisec.cpg.PopulatedByPass import de.fraunhofer.aisec.cpg.frontends.Handler import de.fraunhofer.aisec.cpg.frontends.Language import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend @@ -42,6 +43,10 @@ import de.fraunhofer.aisec.cpg.graph.scopes.Scope import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker import de.fraunhofer.aisec.cpg.helpers.neo4j.LocationConverter import de.fraunhofer.aisec.cpg.helpers.neo4j.NameConverter +import de.fraunhofer.aisec.cpg.passes.ControlFlowSensitiveDFGPass +import de.fraunhofer.aisec.cpg.passes.DFGPass +import de.fraunhofer.aisec.cpg.passes.EvaluationOrderGraphPass +import de.fraunhofer.aisec.cpg.passes.FilenameMapper import de.fraunhofer.aisec.cpg.processing.IVisitable import de.fraunhofer.aisec.cpg.sarif.PhysicalLocation import java.util.* @@ -99,14 +104,16 @@ open class Node : IVisitable, Persistable, LanguageProvider, ScopeProvider * Name of the containing file. It can be null for artificially created nodes or if just * analyzing snippets of code without an associated file name. */ - var file: String? = null + @PopulatedByPass(FilenameMapper::class) var file: String? = null /** Incoming control flow edges. */ + @PopulatedByPass(EvaluationOrderGraphPass::class) @Relationship(value = "EOG", direction = Relationship.Direction.INCOMING) var prevEOGEdges: MutableList> = ArrayList() protected set /** outgoing control flow edges. */ + @PopulatedByPass(EvaluationOrderGraphPass::class) @Relationship(value = "EOG", direction = Relationship.Direction.OUTGOING) var nextEOGEdges: MutableList> = ArrayList() protected set @@ -126,6 +133,7 @@ open class Node : IVisitable, Persistable, LanguageProvider, ScopeProvider get() = SubgraphWalker.getAstChildren(this) /** Virtual property for accessing [prevEOGEdges] without property edges. */ + @PopulatedByPass(EvaluationOrderGraphPass::class) var prevEOG: List get() = unwrap(prevEOGEdges, false) set(value) { @@ -141,6 +149,7 @@ open class Node : IVisitable, Persistable, LanguageProvider, ScopeProvider } /** Virtual property for accessing [nextEOGEdges] without property edges. */ + @PopulatedByPass(EvaluationOrderGraphPass::class) var nextEOG: List get() = unwrap(nextEOGEdges) set(value) { @@ -148,9 +157,12 @@ open class Node : IVisitable, Persistable, LanguageProvider, ScopeProvider } @Relationship(value = "DFG", direction = Relationship.Direction.INCOMING) + @PopulatedByPass(DFGPass::class, ControlFlowSensitiveDFGPass::class) var prevDFG: MutableSet = HashSet() - @Relationship(value = "DFG") var nextDFG: MutableSet = HashSet() + @PopulatedByPass(DFGPass::class, ControlFlowSensitiveDFGPass::class) + @Relationship(value = "DFG") + var nextDFG: MutableSet = HashSet() var typedefs: MutableSet = HashSet() diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/ValueDeclaration.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/ValueDeclaration.kt index 651474ef30..f48bc6d454 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/ValueDeclaration.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/ValueDeclaration.kt @@ -25,6 +25,7 @@ */ package de.fraunhofer.aisec.cpg.graph.declarations +import de.fraunhofer.aisec.cpg.PopulatedByPass import de.fraunhofer.aisec.cpg.graph.HasType import de.fraunhofer.aisec.cpg.graph.TypeManager import de.fraunhofer.aisec.cpg.graph.edge.Properties @@ -36,6 +37,7 @@ import de.fraunhofer.aisec.cpg.graph.types.FunctionPointerType import de.fraunhofer.aisec.cpg.graph.types.ReferenceType import de.fraunhofer.aisec.cpg.graph.types.Type import de.fraunhofer.aisec.cpg.graph.types.UnknownType +import de.fraunhofer.aisec.cpg.passes.VariableUsageResolver import java.util.stream.Collectors import org.apache.commons.lang3.builder.ToStringBuilder import org.neo4j.ogm.annotation.Relationship @@ -74,7 +76,6 @@ abstract class ValueDeclaration : Declaration(), HasType { } @Relationship("POSSIBLE_SUB_TYPES") protected var _possibleSubTypes = mutableListOf() - override var possibleSubTypes: List get() { return if (!TypeManager.isTypeSystemActive()) { @@ -91,13 +92,15 @@ abstract class ValueDeclaration : Declaration(), HasType { * Links to all the [DeclaredReferenceExpression]s accessing the variable and the respective * access value (read, write, readwrite). */ + @PopulatedByPass(VariableUsageResolver::class) @Relationship(value = "USAGE") var usageEdges: MutableList> = ArrayList() /** All usages of the variable/field. */ - /** Set all usages of the variable/field and assembles the access properties. */ + @PopulatedByPass(VariableUsageResolver::class) var usages: List get() = unwrap(usageEdges, true) + /** Set all usages of the variable/field and assembles the access properties. */ set(usages) { usageEdges = usages diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/CallExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/CallExpression.kt index 06cb12f263..37fdd0abb6 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/CallExpression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/CallExpression.kt @@ -40,6 +40,7 @@ import de.fraunhofer.aisec.cpg.graph.types.FunctionPointerType import de.fraunhofer.aisec.cpg.graph.types.TupleType import de.fraunhofer.aisec.cpg.graph.types.Type import de.fraunhofer.aisec.cpg.passes.CallResolver +import de.fraunhofer.aisec.cpg.passes.FunctionPointerCallResolver import de.fraunhofer.aisec.cpg.passes.VariableUsageResolver import java.util.* import org.apache.commons.lang3.builder.ToStringBuilder @@ -51,8 +52,8 @@ import org.neo4j.ogm.annotation.Relationship */ open class CallExpression : Expression(), HasType.TypeListener, SecondaryTypeEdge, ArgumentHolder { /** Connection to its [FunctionDeclaration]. This will be populated by the [CallResolver]. */ + @PopulatedByPass(CallResolver::class, FunctionPointerCallResolver::class) @Relationship(value = "INVOKES", direction = Relationship.Direction.OUTGOING) - @PopulatedByPass(CallResolver::class) var invokeEdges = mutableListOf>() protected set @@ -60,6 +61,7 @@ open class CallExpression : Expression(), HasType.TypeListener, SecondaryTypeEdg * A virtual property to quickly access the list of declarations that this call invokes without * property edges. */ + @PopulatedByPass(CallResolver::class, FunctionPointerCallResolver::class) var invokes: List get(): List { val targets: MutableList = ArrayList() diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/DeclaredReferenceExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/DeclaredReferenceExpression.kt index 524678b130..08a0006d5e 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/DeclaredReferenceExpression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/DeclaredReferenceExpression.kt @@ -25,6 +25,7 @@ */ package de.fraunhofer.aisec.cpg.graph.statements.expressions +import de.fraunhofer.aisec.cpg.PopulatedByPass import de.fraunhofer.aisec.cpg.graph.AccessValues import de.fraunhofer.aisec.cpg.graph.HasType import de.fraunhofer.aisec.cpg.graph.TypeManager @@ -32,6 +33,7 @@ import de.fraunhofer.aisec.cpg.graph.declarations.Declaration import de.fraunhofer.aisec.cpg.graph.declarations.ValueDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration import de.fraunhofer.aisec.cpg.graph.types.Type +import de.fraunhofer.aisec.cpg.passes.VariableUsageResolver import java.util.* import kotlin.collections.ArrayList import org.apache.commons.lang3.builder.ToStringBuilder @@ -44,6 +46,7 @@ import org.neo4j.ogm.annotation.Relationship */ open class DeclaredReferenceExpression : Expression(), HasType.TypeListener { /** The [Declaration]s this expression might refer to. */ + @PopulatedByPass(VariableUsageResolver::class) @Relationship(value = "REFERS_TO") var refersTo: Declaration? = null set(value) { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/Type.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/Type.kt index 5ccb07b95d..7bf5067224 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/Type.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/Type.kt @@ -25,12 +25,14 @@ */ package de.fraunhofer.aisec.cpg.graph.types +import de.fraunhofer.aisec.cpg.PopulatedByPass import de.fraunhofer.aisec.cpg.frontends.Language import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend import de.fraunhofer.aisec.cpg.graph.Name import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.parseName import de.fraunhofer.aisec.cpg.graph.types.PointerType.PointerOrigin +import de.fraunhofer.aisec.cpg.passes.TypeHierarchyResolver import java.util.* import org.apache.commons.lang3.builder.ToStringBuilder import org.neo4j.ogm.annotation.Relationship @@ -42,6 +44,7 @@ import org.neo4j.ogm.annotation.Relationship */ abstract class Type : Node { /** All direct supertypes of this type. */ + @PopulatedByPass(TypeHierarchyResolver::class) @Relationship(value = "SUPER_TYPE", direction = Relationship.Direction.OUTGOING) var superTypes = mutableSetOf() protected set diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TypeHierarchyResolver.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TypeHierarchyResolver.kt index 771f1c2b27..fdf5957138 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TypeHierarchyResolver.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TypeHierarchyResolver.kt @@ -38,7 +38,7 @@ import de.fraunhofer.aisec.cpg.processing.strategy.Strategy import java.util.* /** - * Transitively [RecordDeclaration] nodes with their supertypes' records. + * Transitively connect [RecordDeclaration] nodes with their supertypes' records. * * Supertypes are all interfaces a class implements and the superclass it inherits from (including * all of their respective supertypes). The JavaParser provides us with initial info about direct From 0e22ec2d520777318de477996940cebe409d0d51 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 6 Apr 2023 09:08:29 +0200 Subject: [PATCH 022/143] Update dependency webpack to v5.78.0 (#1149) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- cpg-language-typescript/src/main/nodejs/package.json | 2 +- cpg-language-typescript/src/main/nodejs/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cpg-language-typescript/src/main/nodejs/package.json b/cpg-language-typescript/src/main/nodejs/package.json index 2aa9af733f..ef5834e83d 100644 --- a/cpg-language-typescript/src/main/nodejs/package.json +++ b/cpg-language-typescript/src/main/nodejs/package.json @@ -11,7 +11,7 @@ }, "license": "Apache-2.0", "devDependencies": { - "webpack": "5.77.0", + "webpack": "5.78.0", "webpack-cli": "5.0.0" } } \ No newline at end of file diff --git a/cpg-language-typescript/src/main/nodejs/yarn.lock b/cpg-language-typescript/src/main/nodejs/yarn.lock index 4a2c679206..cd9f77a54c 100644 --- a/cpg-language-typescript/src/main/nodejs/yarn.lock +++ b/cpg-language-typescript/src/main/nodejs/yarn.lock @@ -758,10 +758,10 @@ webpack-sources@^3.2.3: resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== -webpack@5.77.0: - version "5.77.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.77.0.tgz#dea3ad16d7ea6b84aa55fa42f4eac9f30e7eb9b4" - integrity sha512-sbGNjBr5Ya5ss91yzjeJTLKyfiwo5C628AFjEa6WSXcZa4E+F57om3Cc8xLb1Jh0b243AWuSYRf3dn7HVeFQ9Q== +webpack@5.78.0: + version "5.78.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.78.0.tgz#836452a12416af2a7beae906b31644cb2562f9e6" + integrity sha512-gT5DP72KInmE/3azEaQrISjTvLYlSM0j1Ezhht/KLVkrqtv10JoP/RXhwmX/frrutOPuSq3o5Vq0ehR/4Vmd1g== dependencies: "@types/eslint-scope" "^3.7.3" "@types/estree" "^0.0.51" From 9da5ba8d87a8d878d0911b611c352e5e759e0d54 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 6 Apr 2023 07:21:54 +0000 Subject: [PATCH 023/143] Update tekao.net/jnigi digest to 69b87aa (#1148) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- cpg-language-go/src/main/golang/go.mod | 2 +- cpg-language-go/src/main/golang/go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/cpg-language-go/src/main/golang/go.mod b/cpg-language-go/src/main/golang/go.mod index e197d08b9d..4c4982a9dc 100644 --- a/cpg-language-go/src/main/golang/go.mod +++ b/cpg-language-go/src/main/golang/go.mod @@ -4,5 +4,5 @@ go 1.19 require ( golang.org/x/mod v0.9.0 - tekao.net/jnigi v0.0.0-20221227053512-56e0101fa996 + tekao.net/jnigi v0.0.0-20230402215112-69b87aaf8714 ) diff --git a/cpg-language-go/src/main/golang/go.sum b/cpg-language-go/src/main/golang/go.sum index ad7da18d55..57c62ef31a 100644 --- a/cpg-language-go/src/main/golang/go.sum +++ b/cpg-language-go/src/main/golang/go.sum @@ -8,3 +8,5 @@ tekao.net/jnigi v0.0.0-20220921102452-ce6d0be0c331 h1:p5apvrQZPCacG+Ux6GMzLWX4mU tekao.net/jnigi v0.0.0-20220921102452-ce6d0be0c331/go.mod h1:SmVvXetJ8N0ov5c2eOC+IxmkdYGEyuXghTuBq5HWZ/Y= tekao.net/jnigi v0.0.0-20221227053512-56e0101fa996 h1:Vl0GEBxRKyS1+/fjd9H6ptV7t/CAmfgxtsanvqsCob8= tekao.net/jnigi v0.0.0-20221227053512-56e0101fa996/go.mod h1:SmVvXetJ8N0ov5c2eOC+IxmkdYGEyuXghTuBq5HWZ/Y= +tekao.net/jnigi v0.0.0-20230402215112-69b87aaf8714 h1:bfygqxA+ZWLWg06ti7t7lYs79xogFFLu1Xraqb30Nrc= +tekao.net/jnigi v0.0.0-20230402215112-69b87aaf8714/go.mod h1:SmVvXetJ8N0ov5c2eOC+IxmkdYGEyuXghTuBq5HWZ/Y= From 89e9c58b608026acc5f41e396ba604121fc670d0 Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Tue, 11 Apr 2023 11:38:26 +0200 Subject: [PATCH 024/143] Multiple improvements in the Go language (#1108) --- .../aisec/cpg/graph/TypeManager.java | 6 +- .../de/fraunhofer/aisec/cpg/ScopeManager.kt | 18 +- .../cpg/frontends/cpp/ExpressionHandler.kt | 6 +- .../aisec/cpg/graph/DeclarationBuilder.kt | 5 +- .../aisec/cpg/graph/ExpressionBuilder.kt | 47 +- .../aisec/cpg/graph/StatementHolder.kt | 12 + .../declarations/NamespaceDeclaration.kt | 6 + .../graph/declarations/VariableDeclaration.kt | 5 + .../expressions/ArrayRangeExpression.kt | 50 -- .../ArraySubscriptionExpression.kt | 16 +- .../expressions/InitializerListExpression.kt | 16 +- .../statements/expressions/RangeExpression.kt | 83 +++ .../aisec/cpg/graph/types/FunctionType.kt | 6 +- .../aisec/cpg/passes/CallResolver.kt | 2 +- .../cpg/passes/EvaluationOrderGraphPass.kt | 8 +- .../aisec/cpg/passes/inference/Inference.kt | 28 + .../fraunhofer/aisec/cpg/graph/FluentTest.kt | 3 +- .../de/fraunhofer/aisec/cpg/TestUtils.kt | 17 +- .../src/main/golang/basic_types.go | 13 +- .../src/main/golang/declarations.go | 12 + .../src/main/golang/expressions.go | 54 +- .../golang/frontend/declaration_builder.go | 8 +- .../golang/frontend/expression_builder.go | 22 + .../src/main/golang/frontend/frontend.go | 19 +- .../src/main/golang/frontend/handler.go | 582 ++++++++++++------ .../main/golang/frontend/statement_builder.go | 4 + .../src/main/golang/lib/cpg/main.go | 3 + cpg-language-go/src/main/golang/statements.go | 17 + cpg-language-go/src/main/golang/types.go | 5 +- .../frontends/golang/GoLanguageFrontend.kt | 5 +- .../aisec/cpg/passes/GoExtraPass.kt | 286 +++++++++ .../cpg/frontends/golang/DeclarationTest.kt | 77 +++ .../cpg/frontends/golang/ExpressionTest.kt | 83 ++- .../golang/GoLanguageFrontendTest.kt | 185 +++--- .../src/test/resources/golang/call.go | 3 + .../src/test/resources/golang/declare.go | 46 ++ .../src/test/resources/golang/dfg.go | 2 + .../src/test/resources/golang/for.go | 17 +- .../src/test/resources/golang/function.go | 17 +- .../src/test/resources/golang/go.mod | 2 +- .../src/test/resources/golang/literal.go | 4 + .../src/test/resources/golang/slices.go | 21 + .../src/test/resources/golang/type_assert.go | 4 + .../test/resources/golang/type_constraints.go | 11 + 44 files changed, 1434 insertions(+), 402 deletions(-) delete mode 100644 cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ArrayRangeExpression.kt create mode 100644 cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/RangeExpression.kt create mode 100644 cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/GoExtraPass.kt create mode 100644 cpg-language-go/src/test/resources/golang/declare.go create mode 100644 cpg-language-go/src/test/resources/golang/slices.go create mode 100644 cpg-language-go/src/test/resources/golang/type_constraints.go diff --git a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/TypeManager.java b/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/TypeManager.java index fbc2e0e135..54bea206d7 100644 --- a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/TypeManager.java +++ b/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/TypeManager.java @@ -31,10 +31,7 @@ import de.fraunhofer.aisec.cpg.frontends.Language; import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend; import de.fraunhofer.aisec.cpg.frontends.cpp.CLanguage; -import de.fraunhofer.aisec.cpg.graph.declarations.Declaration; -import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration; -import de.fraunhofer.aisec.cpg.graph.declarations.TemplateDeclaration; -import de.fraunhofer.aisec.cpg.graph.declarations.TypedefDeclaration; +import de.fraunhofer.aisec.cpg.graph.declarations.*; import de.fraunhofer.aisec.cpg.graph.scopes.NameScope; import de.fraunhofer.aisec.cpg.graph.scopes.RecordScope; import de.fraunhofer.aisec.cpg.graph.scopes.Scope; @@ -113,6 +110,7 @@ public ParameterizedType getTypeParameter(RecordDeclaration recordDeclaration, S } } } + return null; } 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 86663a010e..29da6fb328 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 @@ -637,19 +637,19 @@ class ScopeManager : ScopeProvider { call: CallExpression, scope: Scope? = currentScope ): List { - val s = extractScope(call.name, scope) + val s = extractScope(call, scope) return resolve(s) { it.name.lastPartsMatch(call.name) && it.hasSignature(call.signature) } } - fun extractScope(name: Name, scope: Scope? = currentScope): Scope? { + fun extractScope(node: Node, scope: Scope? = currentScope): Scope? { var s = scope // First, we need to check, whether we have some kind of scoping. - if (name.parent != null) { + if (node.name.parent != null) { // extract the scope name, it is usually a name space, but could probably be something // else as well in other languages - val scopeName = name.parent + val scopeName = node.name.parent // TODO: proper scope selection @@ -657,12 +657,12 @@ class ScopeManager : ScopeProvider { val scopes = filterScopes { (it is NameScope && it.name == scopeName) } s = if (scopes.isEmpty()) { - LOGGER.error( - "Could not find the scope {} needed to resolve the call {}. Falling back to the default (current) scope", - scopeName, - name + Util.errorWithFileLocation( + node, + LOGGER, + "Could not find the scope $scopeName needed to resolve the call ${node.name}. Falling back to the default (current) scope" ) - scope + s } else { scopes[0] } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/ExpressionHandler.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/ExpressionHandler.kt index b9716032be..85adbc9cbe 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/ExpressionHandler.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/ExpressionHandler.kt @@ -590,11 +590,12 @@ class ExpressionHandler(lang: CXXLanguageFrontend) : } is CPPASTArrayRangeDesignator -> { oneLhs = - newArrayRangeExpression( + newRangeExpression( handle(des.rangeFloor), handle(des.rangeCeiling), des.getRawSignature() ) + oneLhs.operatorCode = "..." } else -> { Util.errorWithFileLocation( @@ -643,11 +644,12 @@ class ExpressionHandler(lang: CXXLanguageFrontend) : } is CPPASTArrayRangeDesignator -> { oneLhs = - newArrayRangeExpression( + newRangeExpression( handle(des.rangeFloor), handle(des.rangeCeiling), des.getRawSignature() ) + oneLhs.operatorCode = "..." } else -> { Util.errorWithFileLocation( diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/DeclarationBuilder.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/DeclarationBuilder.kt index d4b001907e..70b77bf692 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/DeclarationBuilder.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/DeclarationBuilder.kt @@ -65,10 +65,11 @@ fun MetadataProvider.newTranslationUnitDeclaration( fun MetadataProvider.newFunctionDeclaration( name: CharSequence?, code: String? = null, - rawNode: Any? = null + rawNode: Any? = null, + localNameOnly: Boolean = false ): FunctionDeclaration { val node = FunctionDeclaration() - node.applyMetadata(this, name, rawNode, code) + node.applyMetadata(this, name, rawNode, code, localNameOnly) log(node) return node diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/ExpressionBuilder.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/ExpressionBuilder.kt index 1057feff2d..ca734943b3 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/ExpressionBuilder.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/ExpressionBuilder.kt @@ -30,6 +30,7 @@ import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend import de.fraunhofer.aisec.cpg.graph.Node.Companion.EMPTY_NAME import de.fraunhofer.aisec.cpg.graph.NodeBuilder.log import de.fraunhofer.aisec.cpg.graph.statements.expressions.* +import de.fraunhofer.aisec.cpg.graph.statements.expressions.AssignExpression import de.fraunhofer.aisec.cpg.graph.types.Type /** @@ -411,6 +412,29 @@ fun MetadataProvider.newArraySubscriptionExpression( return node } +/** + * Creates a new [RangeExpression]. The [MetadataProvider] receiver will be used to fill different + * meta-data using [Node.applyMetadata]. Calling this extension function outside of Kotlin requires + * an appropriate [MetadataProvider], such as a [LanguageFrontend] as an additional prepended + * argument. + */ +@JvmOverloads +fun MetadataProvider.newRangeExpression( + floor: Expression? = null, + ceiling: Expression? = null, + code: String? = null, + rawNode: Any? = null +): RangeExpression { + val node = RangeExpression() + node.applyMetadata(this, EMPTY_NAME, rawNode, code, true) + + node.floor = floor + node.ceiling = ceiling + + log(node) + return node +} + /** * Creates a new [ArrayCreationExpression]. The [MetadataProvider] receiver will be used to fill * different meta-data using [Node.applyMetadata]. Calling this extension function outside of Kotlin @@ -451,29 +475,6 @@ fun MetadataProvider.newDeclaredReferenceExpression( return node } -/** - * Creates a new [ArrayRangeExpression]. The [MetadataProvider] receiver will be used to fill - * different meta-data using [Node.applyMetadata]. Calling this extension function outside of Kotlin - * requires an appropriate [MetadataProvider], such as a [LanguageFrontend] as an additional - * prepended argument. - */ -@JvmOverloads -fun MetadataProvider.newArrayRangeExpression( - floor: Expression?, - ceil: Expression?, - code: String? = null, - rawNode: Any? = null -): ArrayRangeExpression { - val node = ArrayRangeExpression() - node.applyMetadata(this, EMPTY_NAME, rawNode, code, true) - - node.floor = floor - node.ceiling = ceil - - log(node) - return node -} - /** * Creates a new [DeleteExpression]. The [MetadataProvider] receiver will be used to fill different * meta-data using [Node.applyMetadata]. Calling this extension function outside of Kotlin requires diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/StatementHolder.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/StatementHolder.kt index d2a8a2b0ff..2ad18b1ea7 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/StatementHolder.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/StatementHolder.kt @@ -66,6 +66,18 @@ interface StatementHolder : Holder { statementEdges.add(propertyEdge) } + /** Inserts the statement [s] before the statement specified in [before]. */ + fun insertStatementBefore(s: Statement, before: Statement) { + val statements = this.statements + val idx = statements.indexOf(before) + if (idx != -1) { + val before = statements.subList(0, idx) + val after = statements.subList(idx, statements.size) + + this.statements = listOf(*before.toTypedArray(), s, *after.toTypedArray()) + } + } + override operator fun plusAssign(node: Statement) { addStatement(node) } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/NamespaceDeclaration.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/NamespaceDeclaration.kt index bb0326760f..9fed1238d1 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/NamespaceDeclaration.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/NamespaceDeclaration.kt @@ -57,6 +57,12 @@ class NamespaceDeclaration : Declaration(), DeclarationHolder, StatementHolder { @AST override var statementEdges: MutableList> = ArrayList() + /** + * In some languages, there is a relationship between paths / directories and the package + * structure. Therefore, we need to be aware of the path this namespace / package is in. + */ + var path: String? = null + /** * Returns a non-null, possibly empty `Set` of the declaration of a specified type and clazz. * diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/VariableDeclaration.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/VariableDeclaration.kt index fe207e9956..3b61ae3786 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/VariableDeclaration.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/VariableDeclaration.kt @@ -128,6 +128,11 @@ class VariableDeclaration : ValueDeclaration(), HasType.TypeListener, HasInitial .toString() } + override val assignments: List + get() { + return initializer?.let { listOf(Assignment(it, this, this)) } ?: listOf() + } + override fun equals(other: Any?): Boolean { if (this === other) { return true diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ArrayRangeExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ArrayRangeExpression.kt deleted file mode 100644 index f7a9aeab64..0000000000 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ArrayRangeExpression.kt +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (c) 2020, 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.graph.statements.expressions - -import de.fraunhofer.aisec.cpg.graph.AST -import de.fraunhofer.aisec.cpg.graph.newLiteral -import java.util.Objects - -/** Expressions of the form `floor ... ceiling` */ -class ArrayRangeExpression : Expression() { - @AST var floor: Expression? = null - - @AST var step: Expression? = newLiteral(1) - - @AST var ceiling: Expression? = null - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other !is ArrayRangeExpression) return false - return super.equals(other) && - floor == other.floor && - ceiling == other.ceiling && - step == other.step - } - - override fun hashCode() = Objects.hash(super.hashCode(), floor, ceiling) -} diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ArraySubscriptionExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ArraySubscriptionExpression.kt index 5fd45de90b..0485da095c 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ArraySubscriptionExpression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ArraySubscriptionExpression.kt @@ -50,8 +50,8 @@ class ArraySubscriptionExpression : Expression(), HasType.TypeListener, HasBase /** * The expression which represents the "subscription" or index on which the array is accessed. - * This can for example be a reference to another variable ([DeclaredReferenceExpression]) or a - * [Literal]. + * This can for example be a reference to another variable ([DeclaredReferenceExpression]), a + * [Literal] or a [RangeExpression]. */ @AST var subscriptExpression: Expression = ProblemExpression("could not parse index expression") @@ -61,8 +61,18 @@ class ArraySubscriptionExpression : Expression(), HasType.TypeListener, HasBase override val operatorCode: String get() = "[]" + /** + * This helper function returns the subscript type of the [arrayType]. We have to differentiate + * here between to types of subscripts: + * * Slices (in the form of a [RangeExpression] return the same type as the array + * * Everything else (for example a [Literal] or any other [Expression] that is being evaluated) + * returns the de-referenced type + */ private fun getSubscriptType(arrayType: Type): Type { - return arrayType.dereference() + return when (subscriptExpression) { + is RangeExpression -> arrayType + else -> arrayType.dereference() + } } override fun typeChanged(src: HasType, root: MutableList, oldType: Type) { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/InitializerListExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/InitializerListExpression.kt index e41967162b..224cdb92c1 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/InitializerListExpression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/InitializerListExpression.kt @@ -28,7 +28,6 @@ package de.fraunhofer.aisec.cpg.graph.statements.expressions import de.fraunhofer.aisec.cpg.graph.AST import de.fraunhofer.aisec.cpg.graph.HasType import de.fraunhofer.aisec.cpg.graph.TypeManager -import de.fraunhofer.aisec.cpg.graph.edge.Properties import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.propertyEqualsList import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdgeDelegate @@ -60,14 +59,6 @@ class InitializerListExpression : Expression(), HasType.TypeListener { /** Virtual property to access [initializerEdges] without property edges. */ var initializers by PropertyEdgeDelegate(InitializerListExpression::initializerEdges) - fun addInitializer(initializer: Expression) { - val edge = PropertyEdge(this, initializer) - edge.addProperty(Properties.INDEX, initializerEdges.size) - initializer.registerTypeListener(this) - addPrevDFG(initializer) - initializerEdges.add(edge) - } - override fun typeChanged(src: HasType, root: MutableList, oldType: Type) { if (!TypeManager.isTypeSystemActive()) { return @@ -128,5 +119,10 @@ class InitializerListExpression : Expression(), HasType.TypeListener { propertyEqualsList(initializerEdges, other.initializerEdges) } - override fun hashCode() = Objects.hash(super.hashCode(), initializers) + override fun hashCode(): Int { + // Including initializerEdges directly is a HUGE performance loss in the calculation of each + // hash code. Therefore, we only include the array's size, which should hopefully be sort of + // unique to avoid too many hash collisions. + return Objects.hash(super.hashCode(), initializerEdges.size) + } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/RangeExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/RangeExpression.kt new file mode 100644 index 0000000000..d25e242d2f --- /dev/null +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/RangeExpression.kt @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2023, 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.graph.statements.expressions + +import java.util.* + +/** + * Represents the specification of a range (e.g., of an array). Usually used in combination with an + * [ArraySubscriptionExpression] as the [ArraySubscriptionExpression.subscriptExpression]. + * + * Examples can be found in Go: + * ```go + * a := []int{1,2,3} + * b := a[:1] + * ``` + * + * or Python: + * ```python + * a = (1,2,3) + * b = a[:1] + * ``` + * + * In C/C++ this can be also part of a [DesignatedInitializerExpression], as part of a GCC + * extension: + * ```c + * int a[] = { [0...4] = 1 }; + * ``` + * + * Individual meaning of the range indices might differ per language. + */ +class RangeExpression : Expression() { + + /** The lower bound ("floor") of the range. This index is usually *inclusive*. */ + var floor: Expression? = null + + /** The upper bound ("ceiling") of the range. This index is usually *exclusive*. */ + var ceiling: Expression? = null + + /** + * Some languages offer a third value. The meaning depends completely on the language. For + * example, Python allows specifying a step, while Go allows to control the underlying array's + * capacity (not length). + */ + var third: Expression? = null + + /** The operator code that separates the range elements. Common cases are `:` or `...` */ + var operatorCode = ":" + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is RangeExpression) return false + return super.equals(other) && + floor == other.floor && + ceiling == other.ceiling && + third == other.third && + operatorCode == other.operatorCode + } + + override fun hashCode() = Objects.hash(super.hashCode(), floor, ceiling, third, operatorCode) +} diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/FunctionType.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/FunctionType.kt index 9ec18da196..db94a55534 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/FunctionType.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/FunctionType.kt @@ -49,7 +49,11 @@ constructor( override fun reference(pointer: PointerType.PointerOrigin?): Type { // TODO(oxisto): In the future, we actually could just remove the FunctionPointerType // and just have a regular PointerType here - return FunctionPointerType(parameters.toList(), language, returnTypes.first()) + return FunctionPointerType( + parameters.toList(), + language, + returnTypes.firstOrNull() ?: newUnknownType(), + ) } override fun dereference(): Type { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolver.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolver.kt index 7fe3e0dadf..50a06b3354 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolver.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolver.kt @@ -207,7 +207,7 @@ open class CallResolver : SymbolResolverPass() { // the TU. It is also a little bit redundant, since ScopeManager.resolveFunction // (which gets called before) already extracts the scope, but this information // gets lost. - val scope = scopeManager.extractScope(call.name, scopeManager.globalScope) + val scope = scopeManager.extractScope(call, scopeManager.globalScope) // We have two possible start points, a namespace declaration or a translation // unit. Nothing else is allowed (fow now) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt index 86fd9e8cd3..392120d7e9 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt @@ -111,9 +111,7 @@ open class EvaluationOrderGraphPass : Pass() { map[ArrayCreationExpression::class.java] = { handleArrayCreationExpression(it as ArrayCreationExpression) } - map[ArrayRangeExpression::class.java] = { - handleArrayRangeExpression(it as ArrayRangeExpression) - } + map[RangeExpression::class.java] = { handleRangeExpression(it as RangeExpression) } map[DeclarationStatement::class.java] = { handleDeclarationStatement(it as DeclarationStatement) } @@ -427,10 +425,10 @@ open class EvaluationOrderGraphPass : Pass() { pushToEOG(node) } - protected fun handleArrayRangeExpression(node: ArrayRangeExpression) { + protected fun handleRangeExpression(node: RangeExpression) { createEOG(node.floor) createEOG(node.ceiling) - createEOG(node.step) + createEOG(node.third) pushToEOG(node) } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/Inference.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/Inference.kt index b148a8f6ed..19f9b33514 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/Inference.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/Inference.kt @@ -114,6 +114,7 @@ class Inference(val start: Node, val scopeManager: ScopeManager) : scopeManager.addDeclaration(inferred) // Some magic that adds it to static imports. Not sure if this really needed + if (record != null) { if (isStatic) { record.staticImports.add(inferred) @@ -331,6 +332,33 @@ class Inference(val start: Node, val scopeManager: ScopeManager) : currentTU.addDeclaration(declaration) return declaration } + + fun createInferredNamespaceDeclaration(name: Name, path: String?): NamespaceDeclaration { + // Here be dragons. Jump to the scope that the node defines directly, so that we can + // delegate further operations to the scope manager. We also save the old scope so we can + // restore it. + return inferInScopeOf(start) { + log.debug( + "Inferring a new namespace declaration $name ${ + if (path != null) { + "with path '$path'" + } else { + "" + } + }" + ) + + val inferred = newNamespaceDeclaration(name) + inferred.path = path + + scopeManager.addDeclaration(inferred) + + // We need to "enter" the scope to make it known to the scope map of the ScopeManager + scopeManager.enterScope(inferred) + scopeManager.leaveScope(inferred) + inferred + } + } } /** Provides information about the inference status of a node. */ diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/FluentTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/FluentTest.kt index d7852eb94d..f73cb9fcac 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/FluentTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/FluentTest.kt @@ -139,7 +139,8 @@ class FluentTest { assertNotNull(lit1) assertEquals(1, lit1.value) - // Third line is the CallExpression (containing another MemberCallExpression as argument) + // Third line is th + // e CallExpression (containing another MemberCallExpression as argument) val call = main[2] as? CallExpression assertNotNull(call) assertLocalName("do", call) diff --git a/cpg-core/src/testFixtures/kotlin/de/fraunhofer/aisec/cpg/TestUtils.kt b/cpg-core/src/testFixtures/kotlin/de/fraunhofer/aisec/cpg/TestUtils.kt index 16f28fa977..0e2907a606 100644 --- a/cpg-core/src/testFixtures/kotlin/de/fraunhofer/aisec/cpg/TestUtils.kt +++ b/cpg-core/src/testFixtures/kotlin/de/fraunhofer/aisec/cpg/TestUtils.kt @@ -31,10 +31,7 @@ import de.fraunhofer.aisec.cpg.graph.TypeManager import de.fraunhofer.aisec.cpg.graph.declarations.Declaration import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration -import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression -import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression -import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression -import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberExpression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.* import java.io.File import java.nio.file.Files import java.nio.file.Path @@ -297,10 +294,18 @@ object TestUtils { fun assertFullName(fqn: String, node: Node?, message: String? = null) { assertNotNull(node) - asserter.assertEquals(message, fqn, node.name.toString()) + assertEquals(fqn, node.name.toString(), message) } fun assertLocalName(localName: String, node: Node?, message: String? = null) { assertNotNull(node) - asserter.assertEquals(message, localName, node.name.localName) + assertEquals(localName, node.name.localName, message) +} + +/** + * Asserts that a) the expression in [expr] is a [Literal] and b) that it's value is equal to + * [expected]. + */ +fun assertLiteralValue(expected: T, expr: Expression?, message: String? = null) { + assertEquals(expected, assertIs>(expr).value, message) } diff --git a/cpg-language-go/src/main/golang/basic_types.go b/cpg-language-go/src/main/golang/basic_types.go index c789e338ba..3325260572 100644 --- a/cpg-language-go/src/main/golang/basic_types.go +++ b/cpg-language-go/src/main/golang/basic_types.go @@ -32,13 +32,16 @@ import ( ) func NewString(s string) *jnigi.ObjectRef { - o, err := env.NewObject("java/lang/String", []byte(s)) - if err != nil { - log.Fatal(err) + if s != "" { + o, err := env.NewObject("java/lang/String", []byte(s)) + if err != nil { + log.Fatal(err) + } + return o + } else { + return jnigi.NewObjectRef("java/lang/String") } - - return o } func NewCharSequence(s string) *jnigi.ObjectRef { diff --git a/cpg-language-go/src/main/golang/declarations.go b/cpg-language-go/src/main/golang/declarations.go index 74d376e09e..caadcced29 100644 --- a/cpg-language-go/src/main/golang/declarations.go +++ b/cpg-language-go/src/main/golang/declarations.go @@ -84,6 +84,12 @@ func (f *FunctionDeclaration) SetBody(s *Statement) (err error) { return } +func (n *NamespaceDeclaration) SetPath(path string) (err error) { + err = (*jnigi.ObjectRef)(n).CallMethod(env, "setPath", nil, NewString(path)) + + return +} + func (m *MethodDeclaration) SetType(t *Type) { (*HasType)(m).SetType(t) } @@ -108,6 +114,12 @@ func (p *ParamVariableDeclaration) SetType(t *Type) { (*HasType)(p).SetType(t) } +func (p *ParamVariableDeclaration) SetVariadic(b bool) (err error) { + err = (*jnigi.ObjectRef)(p).CallMethod(env, "setVariadic", nil, b) + + return +} + func (f *FieldDeclaration) SetType(t *Type) { (*HasType)(f).SetType(t) } diff --git a/cpg-language-go/src/main/golang/expressions.go b/cpg-language-go/src/main/golang/expressions.go index 51851bff0d..d1716ea336 100644 --- a/cpg-language-go/src/main/golang/expressions.go +++ b/cpg-language-go/src/main/golang/expressions.go @@ -59,15 +59,19 @@ type CastExpression Expression type NewExpression Expression type ArrayCreationExpression Expression type ArraySubscriptionExpression Expression +type RangeExpression Expression type ConstructExpression Expression type InitializerListExpression Expression type MemberCallExpression CallExpression type MemberExpression Expression type BinaryOperator Expression +type AssignExpression Expression type UnaryOperator Expression type Literal Expression type DeclaredReferenceExpression Expression type KeyValueExpression Expression +type LambdaExpression Expression +type ProblemExpression Expression func (e *Expression) SetType(t *Type) { (*HasType)(e).SetType(t) @@ -143,6 +147,28 @@ func (b *BinaryOperator) SetOperatorCode(s string) (err error) { return (*jnigi.ObjectRef)(b).SetField(env, "operatorCode", NewString(s)) } +func (a *AssignExpression) SetLHS(e []*Expression) { + list, err := ListOf(e) + if err != nil { + panic(err) + } + + (*jnigi.ObjectRef)(a).CallMethod(env, "setLhs", nil, list.Cast("java/util/List")) +} + +func (a *AssignExpression) SetRHS(e []*Expression) { + list, err := ListOf(e) + if err != nil { + panic(err) + } + + (*jnigi.ObjectRef)(a).CallMethod(env, "setRhs", nil, list.Cast("java/util/List")) +} + +func (a *AssignExpression) SetOperatorCode(op string) { + (*jnigi.ObjectRef)(a).CallMethod(env, "setOperatorCode", nil, NewString(op)) +} + func (u *UnaryOperator) SetInput(e *Expression) { (*jnigi.ObjectRef)(u).CallMethod(env, "setInput", nil, (*jnigi.ObjectRef)(e).Cast(ExpressionClass)) } @@ -184,6 +210,18 @@ func (r *ArraySubscriptionExpression) SetSubscriptExpression(e *Expression) { (*jnigi.ObjectRef)(r).CallMethod(env, "setSubscriptExpression", nil, (*jnigi.ObjectRef)(e).Cast(ExpressionClass)) } +func (s *RangeExpression) SetFloor(e *Expression) { + (*jnigi.ObjectRef)(s).CallMethod(env, "setFloor", nil, (*jnigi.ObjectRef)(e).Cast(ExpressionClass)) +} + +func (s *RangeExpression) SetCeiling(e *Expression) { + (*jnigi.ObjectRef)(s).CallMethod(env, "setCeiling", nil, (*jnigi.ObjectRef)(e).Cast(ExpressionClass)) +} + +func (s *RangeExpression) SetThird(e *Expression) { + (*jnigi.ObjectRef)(s).CallMethod(env, "setThird", nil, (*jnigi.ObjectRef)(e).Cast(ExpressionClass)) +} + func (c *ConstructExpression) AddArgument(e *Expression) { (*jnigi.ObjectRef)(c).CallMethod(env, "addArgument", nil, (*jnigi.ObjectRef)(e).Cast(ExpressionClass)) } @@ -198,8 +236,13 @@ func (n *NewExpression) SetInitializer(e *Expression) (err error) { return } -func (c *InitializerListExpression) AddInitializer(e *Expression) { - (*jnigi.ObjectRef)(c).CallMethod(env, "addInitializer", nil, (*jnigi.ObjectRef)(e).Cast(ExpressionClass)) +func (c *InitializerListExpression) SetInitializers(e []*Expression) { + l, err := ListOf(e) + if err != nil { + panic(err) + } + + (*jnigi.ObjectRef)(c).CallMethod(env, "setInitializers", nil, l.Cast("java/util/List")) } func (k *KeyValueExpression) SetKey(e *Expression) { @@ -209,3 +252,10 @@ func (k *KeyValueExpression) SetKey(e *Expression) { func (k *KeyValueExpression) SetValue(e *Expression) { (*jnigi.ObjectRef)(k).CallMethod(env, "setValue", nil, (*jnigi.ObjectRef)(e).Cast(ExpressionClass)) } + +func (l *LambdaExpression) SetFunction(f *FunctionDeclaration) { + err := (*jnigi.ObjectRef)(l).CallMethod(env, "setFunction", nil, (*jnigi.ObjectRef)(f).Cast(FunctionDeclarationClass)) + if err != nil { + panic(err) + } +} diff --git a/cpg-language-go/src/main/golang/frontend/declaration_builder.go b/cpg-language-go/src/main/golang/frontend/declaration_builder.go index 4965cacb36..817ce7faf9 100644 --- a/cpg-language-go/src/main/golang/frontend/declaration_builder.go +++ b/cpg-language-go/src/main/golang/frontend/declaration_builder.go @@ -46,8 +46,12 @@ func (frontend *GoLanguageFrontend) NewIncludeDeclaration(fset *token.FileSet, a return (*cpg.IncludeDeclaration)(frontend.NewDeclaration("IncludeDeclaration", fset, astNode, name)) } -func (frontend *GoLanguageFrontend) NewFunctionDeclaration(fset *token.FileSet, astNode ast.Node, name string) *cpg.FunctionDeclaration { - return (*cpg.FunctionDeclaration)(frontend.NewDeclaration("FunctionDeclaration", fset, astNode, name)) +func (frontend *GoLanguageFrontend) NewFunctionDeclaration(fset *token.FileSet, astNode ast.Node, name string, code string, localNameOnly bool) *cpg.FunctionDeclaration { + return (*cpg.FunctionDeclaration)(frontend.NewDeclaration("FunctionDeclaration", fset, astNode, name, + cpg.NewString(code), + jnigi.NewObjectRef("java/lang/Object"), + localNameOnly, + )) } func (frontend *GoLanguageFrontend) NewMethodDeclaration(fset *token.FileSet, astNode ast.Node, name string) *cpg.MethodDeclaration { diff --git a/cpg-language-go/src/main/golang/frontend/expression_builder.go b/cpg-language-go/src/main/golang/frontend/expression_builder.go index 06df6661b7..9b248e90e1 100644 --- a/cpg-language-go/src/main/golang/frontend/expression_builder.go +++ b/cpg-language-go/src/main/golang/frontend/expression_builder.go @@ -70,6 +70,10 @@ func (frontend *GoLanguageFrontend) NewArraySubscriptionExpression(fset *token.F return (*cpg.ArraySubscriptionExpression)(frontend.NewExpression("ArraySubscriptionExpression", fset, astNode)) } +func (frontend *GoLanguageFrontend) NewRangeExpression(fset *token.FileSet, astNode ast.Node) *cpg.RangeExpression { + return (*cpg.RangeExpression)(frontend.NewExpression("RangeExpression", fset, astNode)) +} + func (frontend *GoLanguageFrontend) NewConstructExpression(fset *token.FileSet, astNode ast.Node) *cpg.ConstructExpression { return (*cpg.ConstructExpression)(frontend.NewExpression("ConstructExpression", fset, astNode)) } @@ -84,6 +88,12 @@ func (frontend *GoLanguageFrontend) NewBinaryOperator(fset *token.FileSet, astNo )) } +func (frontend *GoLanguageFrontend) NewAssignExpression(fset *token.FileSet, astNode ast.Node, opCode string) *cpg.AssignExpression { + return (*cpg.AssignExpression)(frontend.NewExpression("AssignExpression", fset, astNode, + cpg.NewString(opCode), + )) +} + func (frontend *GoLanguageFrontend) NewUnaryOperator(fset *token.FileSet, astNode ast.Node, opCode string, postfix bool, prefix bool) *cpg.UnaryOperator { return (*cpg.UnaryOperator)(frontend.NewExpression("UnaryOperator", fset, astNode, cpg.NewString(opCode), @@ -98,6 +108,10 @@ func (frontend *GoLanguageFrontend) NewLiteral(fset *token.FileSet, astNode ast. value = value.Cast("java/lang/Object") } + if typ == nil { + panic("typ is nil") + } + return (*cpg.Literal)(frontend.NewExpression("Literal", fset, astNode, value, typ.Cast(cpg.TypeClass))) } @@ -109,6 +123,14 @@ func (frontend *GoLanguageFrontend) NewKeyValueExpression(fset *token.FileSet, a return (*cpg.KeyValueExpression)(frontend.NewExpression("KeyValueExpression", fset, astNode)) } +func (frontend *GoLanguageFrontend) NewLambdaExpression(fset *token.FileSet, astNode ast.Node) *cpg.LambdaExpression { + return (*cpg.LambdaExpression)(frontend.NewExpression("LambdaExpression", fset, astNode)) +} + +func (frontend *GoLanguageFrontend) NewProblemExpression(fset *token.FileSet, astNode ast.Node, problem string) *cpg.ProblemExpression { + return (*cpg.ProblemExpression)(frontend.NewExpression("ProblemExpression", fset, astNode, cpg.NewString(problem))) +} + func (frontend *GoLanguageFrontend) NewExpression(typ string, fset *token.FileSet, astNode ast.Node, args ...any) *jnigi.ObjectRef { var node = jnigi.NewObjectRef(fmt.Sprintf("%s/%s", cpg.ExpressionsPackage, typ)) diff --git a/cpg-language-go/src/main/golang/frontend/frontend.go b/cpg-language-go/src/main/golang/frontend/frontend.go index b70c070a5b..4dbe97468f 100644 --- a/cpg-language-go/src/main/golang/frontend/frontend.go +++ b/cpg-language-go/src/main/golang/frontend/frontend.go @@ -45,6 +45,7 @@ type GoLanguageFrontend struct { File *ast.File Module *modfile.File CommentMap ast.CommentMap + TopLevel string CurrentTU *cpg.TranslationUnitDeclaration } @@ -101,6 +102,18 @@ func (g *GoLanguageFrontend) LogDebug(format string, args ...interface{}) (err e return } +func (g *GoLanguageFrontend) LogTrace(format string, args ...interface{}) (err error) { + var logger *jnigi.ObjectRef + + if logger, err = g.getLog(); err != nil { + return + } + + err = logger.CallMethod(env, "trace", nil, cpg.NewString(fmt.Sprintf(format, args...))) + + return +} + func (g *GoLanguageFrontend) LogError(format string, args ...interface{}) (err error) { var logger *jnigi.ObjectRef @@ -121,10 +134,14 @@ func (g *GoLanguageFrontend) GetLanguage() (l *cpg.Language, err error) { } func updateCode(fset *token.FileSet, node *cpg.Node, astNode ast.Node) { + node.SetCode(code(fset, astNode)) +} + +func code(fset *token.FileSet, astNode ast.Node) string { var codeBuf bytes.Buffer _ = printer.Fprint(&codeBuf, fset, astNode) - node.SetCode(codeBuf.String()) + return codeBuf.String() } func updateLocation(fset *token.FileSet, node *cpg.Node, astNode ast.Node) { diff --git a/cpg-language-go/src/main/golang/frontend/handler.go b/cpg-language-go/src/main/golang/frontend/handler.go index f7cd9dedb0..923ced3e45 100644 --- a/cpg-language-go/src/main/golang/frontend/handler.go +++ b/cpg-language-go/src/main/golang/frontend/handler.go @@ -34,6 +34,7 @@ import ( "log" "os" "path" + "path/filepath" "strconv" "strings" @@ -104,22 +105,43 @@ func (this *GoLanguageFrontend) HandleFile(fset *token.FileSet, file *ast.File, } } - // create a new namespace declaration, representing the package + // Create a new namespace declaration, representing the package p := this.NewNamespaceDeclaration(fset, nil, file.Name.Name) + // we need to construct the package "path" (e.g. "encoding/json") out of the + // module path as well as the current directory in relation to the topLevel + packagePath := filepath.Dir(path) + + // Construct a relative path starting from the top level + packagePath, err = filepath.Rel(this.TopLevel, packagePath) + if err == nil { + // If we are in a module, we need to prepend the module path to it + if this.Module != nil { + packagePath = filepath.Join(this.Module.Module.Mod.Path, packagePath) + } + + p.SetPath(packagePath) + } else { + this.LogError("Could not relativize package path to top level. Cannot set package path: %v", err) + } + // enter scope scope.EnterScope((*cpg.Node)(p)) for _, decl := range file.Decls { - var d *cpg.Declaration - - d = this.handleDecl(fset, decl) - - if d != nil { - err = scope.AddDeclaration((*cpg.Declaration)(d)) - if err != nil { - log.Fatal(err) + // Retrieve all top level declarations. One "Decl" could potentially + // contain multiple CPG declarations. + decls := this.handleDecl(fset, decl) + + for _, d := range decls { + if d != nil { + // Add declaration to current scope. This will also add it to the + // respective AST scope holder + err = scope.AddDeclaration((*cpg.Declaration)(d)) + if err != nil { + log.Fatal(err) + } } } } @@ -135,7 +157,7 @@ func (this *GoLanguageFrontend) HandleFile(fset *token.FileSet, file *ast.File, // handleComments maps comments from ast.Node to a cpg.Node by using ast.CommentMap. func (this *GoLanguageFrontend) handleComments(node *cpg.Node, astNode ast.Node) { - this.LogDebug("Handling comments for %+v", astNode) + this.LogTrace("Handling comments for %+v", astNode) var comment = "" @@ -155,33 +177,43 @@ func (this *GoLanguageFrontend) handleComments(node *cpg.Node, astNode ast.Node) if comment != "" { node.SetComment(comment) - this.LogDebug("Comments: %+v", comment) + this.LogTrace("Comments: %+v", comment) } } -func (this *GoLanguageFrontend) handleDecl(fset *token.FileSet, decl ast.Decl) (d *cpg.Declaration) { - this.LogDebug("Handling declaration (%T): %+v", decl, decl) +// handleDecl parses an [ast.Decl]. Note, that in a "Decl", one or more actual +// declarations can be found. Therefore, this function returns a slice of +// [cpg.Declaration]. +func (this *GoLanguageFrontend) handleDecl(fset *token.FileSet, decl ast.Decl) (decls []*cpg.Declaration) { + this.LogTrace("Handling declaration (%T): %+v", decl, decl) + + decls = []*cpg.Declaration{} switch v := decl.(type) { case *ast.FuncDecl: - d = (*cpg.Declaration)(this.handleFuncDecl(fset, v)) + // There can be only a single function declaration + decls = append(decls, (*cpg.Declaration)(this.handleFuncDecl(fset, v))) case *ast.GenDecl: - d = (*cpg.Declaration)(this.handleGenDecl(fset, v)) + // GenDecl can hold multiple declarations + decls = this.handleGenDecl(fset, v) default: this.LogError("Not parsing declaration of type %T yet: %+v", v, v) - // no match - d = nil + // TODO: Return a ProblemDeclaration } - if d != nil { - this.handleComments((*cpg.Node)(d), decl) + // Handle comments for all declarations + for _, d := range decls { + // TODO: This is problematic because we are assigning it the wrong node + if d != nil { + this.handleComments((*cpg.Node)(d), decl) + } } return } -func (this *GoLanguageFrontend) handleFuncDecl(fset *token.FileSet, funcDecl *ast.FuncDecl) *jnigi.ObjectRef { - this.LogDebug("Handling func Decl: %+v", *funcDecl) +func (this *GoLanguageFrontend) handleFuncDecl(fset *token.FileSet, funcDecl *ast.FuncDecl) *cpg.FunctionDeclaration { + this.LogTrace("Handling func Decl: %+v", *funcDecl) var scope = this.GetScopeManager() var receiver *cpg.VariableDeclaration @@ -193,7 +225,7 @@ func (this *GoLanguageFrontend) handleFuncDecl(fset *token.FileSet, funcDecl *as // TODO: why is this a list? var recv = funcDecl.Recv.List[0] - var recordType = this.handleType(recv.Type) + var recordType = this.handleType(fset, recv.Type) // The name of the Go receiver is optional. In fact, if the name is not // specified we probably do not need any receiver variable at all, @@ -230,7 +262,7 @@ func (this *GoLanguageFrontend) handleFuncDecl(fset *token.FileSet, funcDecl *as // marked as AST and in Go a method is not part of the struct's AST but is declared // outside. In the future, we need to differentiate between just the associated members // of the class and the pure AST nodes declared in the struct itself - this.LogDebug("Record: %+v", record) + this.LogTrace("Record: %+v", record) err = record.AddMethod(m) if err != nil { @@ -242,31 +274,37 @@ func (this *GoLanguageFrontend) handleFuncDecl(fset *token.FileSet, funcDecl *as f = (*cpg.FunctionDeclaration)(m) } else { - f = this.NewFunctionDeclaration(fset, funcDecl, funcDecl.Name.Name) + // We do not want to prefix the package for an empty (lambda) function name + var localNameOnly bool = false + if funcDecl.Name.Name == "" { + localNameOnly = true + } + + f = this.NewFunctionDeclaration(fset, funcDecl, funcDecl.Name.Name, "", localNameOnly) } // enter scope for function scope.EnterScope((*cpg.Node)(f)) if receiver != nil { - this.LogDebug("Adding receiver %s", (*cpg.Node)(receiver).GetName()) + this.LogTrace("Adding receiver %s", (*cpg.Node)(receiver).GetName()) // add the receiver do the scope manager, so we can resolve the receiver value this.GetScopeManager().AddDeclaration((*cpg.Declaration)(receiver)) } - var t *cpg.Type = this.handleType(funcDecl.Type) + var t *cpg.Type = this.handleType(fset, funcDecl.Type) var returnTypes []*cpg.Type = []*cpg.Type{} if funcDecl.Type.Results != nil { for _, returnVariable := range funcDecl.Type.Results.List { - returnTypes = append(returnTypes, this.handleType(returnVariable.Type)) + returnTypes = append(returnTypes, this.handleType(fset, returnVariable.Type)) // if the function has named return variables, be sure to declare them as well if returnVariable.Names != nil { p := this.NewVariableDeclaration(fset, returnVariable, returnVariable.Names[0].Name) - p.SetType(this.handleType(returnVariable.Type)) + p.SetType(this.handleType(fset, returnVariable.Type)) // add parameter to scope this.GetScopeManager().AddDeclaration((*cpg.Declaration)(p)) @@ -274,7 +312,7 @@ func (this *GoLanguageFrontend) handleFuncDecl(fset *token.FileSet, funcDecl *as } } - this.LogDebug("Function has type %s", t.GetName()) + this.LogTrace("Function has type %s", t.GetName()) f.SetType(t) f.SetReturnTypes(returnTypes) @@ -284,7 +322,7 @@ func (this *GoLanguageFrontend) handleFuncDecl(fset *token.FileSet, funcDecl *as // go, since we do not have a 'this', but rather a named receiver for _, param := range funcDecl.Type.Params.List { - this.LogDebug("Parsing param: %+v", param) + this.LogTrace("Parsing param: %+v", param) var name string // Somehow parameters end up having no name sometimes, have not fully understood why. @@ -305,7 +343,22 @@ func (this *GoLanguageFrontend) handleFuncDecl(fset *token.FileSet, funcDecl *as p := this.NewParamVariableDeclaration(fset, param, name) - p.SetType(this.handleType(param.Type)) + // Check for varargs. In this case we want to parse the element type + // (and make it an array afterwards) + if ell, ok := param.Type.(*ast.Ellipsis); ok { + p.SetVariadic(true) + var t = this.handleType(fset, ell.Elt) + + var i = jnigi.NewObjectRef(cpg.PointerOriginClass) + err := env.GetStaticField(cpg.PointerOriginClass, "ARRAY", i) + if err != nil { + log.Fatal(err) + } + + p.SetType(t.Reference(i)) + } else { + p.SetType(this.handleType(fset, param.Type)) + } // add parameter to scope this.GetScopeManager().AddDeclaration((*cpg.Declaration)(p)) @@ -313,7 +366,7 @@ func (this *GoLanguageFrontend) handleFuncDecl(fset *token.FileSet, funcDecl *as this.handleComments((*cpg.Node)(p), param) } - this.LogDebug("Parsing function body of %s", (*cpg.Node)(f).GetName()) + this.LogTrace("Parsing function body of %s", (*cpg.Node)(f).GetName()) // parse body s := this.handleBlockStmt(fset, funcDecl.Body) @@ -331,55 +384,70 @@ func (this *GoLanguageFrontend) handleFuncDecl(fset *token.FileSet, funcDecl *as } - return (*jnigi.ObjectRef)(f) + return f } -func (this *GoLanguageFrontend) handleGenDecl(fset *token.FileSet, genDecl *ast.GenDecl) *jnigi.ObjectRef { - // TODO: Handle multiple declarations +func (this *GoLanguageFrontend) handleGenDecl(fset *token.FileSet, genDecl *ast.GenDecl) (decls []*cpg.Declaration) { + decls = []*cpg.Declaration{} + for _, spec := range genDecl.Specs { switch v := spec.(type) { case *ast.ValueSpec: - return (*jnigi.ObjectRef)(this.handleValueSpec(fset, v)) + decls = append(decls, this.handleValueSpec(fset, v)...) case *ast.TypeSpec: - return (*jnigi.ObjectRef)(this.handleTypeSpec(fset, v)) + decls = append(decls, this.handleTypeSpec(fset, v)) case *ast.ImportSpec: - // somehow these end up duplicate in the AST, so do not handle them here - return nil - /*return (*jnigi.ObjectRef)(this.handleImportSpec(fset, v))*/ + // Somehow these end up duplicate in the AST, so do not handle them here default: - this.LogError("Not parsing specication of type %T yet: %+v", v, v) + this.LogError("Not parsing specification of type %T yet: %+v", v, v) } } - return nil + return } -func (this *GoLanguageFrontend) handleValueSpec(fset *token.FileSet, valueDecl *ast.ValueSpec) *cpg.Declaration { - // TODO: more names - var ident = valueDecl.Names[0] +// handleValueSpec handles parsing of an [ast.ValueSpec], which is a variable +// declaration. Since this can potentially declare multiple variables with one +// "spec", this returns a slice of [cpg.Declaration]. +func (this *GoLanguageFrontend) handleValueSpec(fset *token.FileSet, valueDecl *ast.ValueSpec) (decls []*cpg.Declaration) { + decls = []*cpg.Declaration{} - d := this.NewVariableDeclaration(fset, valueDecl, ident.Name) + // We need to declare one variable for each name + for idx, ident := range valueDecl.Names { + d := this.NewVariableDeclaration(fset, valueDecl, ident.Name) - if valueDecl.Type != nil { - t := this.handleType(valueDecl.Type) + // Handle the type (if its there) + if valueDecl.Type != nil { + t := this.handleType(fset, valueDecl.Type) - d.SetType(t) - } + d.SetType(t) + } - // add an initializer - if len(valueDecl.Values) > 0 { - // TODO: How to deal with multiple values - var expr = this.handleExpr(fset, valueDecl.Values[0]) + // There could either be no initializers, otherwise the amount of values + // must match the names + lenValues := len(valueDecl.Values) + if lenValues != 0 && lenValues != len(valueDecl.Names) { + this.LogError("Number of initializers does not match number of names. Initializers might be incomplete") + } - err := d.SetInitializer(expr) - if err != nil { - log.Fatal(err) + // The initializer is in the "Values" slice with the respective index + if len(valueDecl.Values) > idx { + var expr = this.handleExpr(fset, valueDecl.Values[idx]) + + err := d.SetInitializer(expr) + if err != nil { + log.Fatal(err) + } } + + decls = append(decls, d.Declaration()) } - return (*cpg.Declaration)(d) + return decls } +// handleTypeSpec handles an [ast.TypeSec], which defines either a struct or an +// interface. It returns a single [cpg.Declaration]. func (this *GoLanguageFrontend) handleTypeSpec(fset *token.FileSet, typeDecl *ast.TypeSpec) *cpg.Declaration { err := this.LogInfo("Type specifier with name %s and type (%T, %+v)", typeDecl.Name.Name, typeDecl.Type, typeDecl.Type) if err != nil { @@ -397,7 +465,7 @@ func (this *GoLanguageFrontend) handleTypeSpec(fset *token.FileSet, typeDecl *as } func (this *GoLanguageFrontend) handleImportSpec(fset *token.FileSet, importSpec *ast.ImportSpec) *cpg.Declaration { - this.LogInfo("Import specifier with: %+v)", *importSpec) + this.LogTrace("Import specifier with: %+v)", *importSpec) i := this.NewIncludeDeclaration(fset, importSpec, getImportName(importSpec)) @@ -432,17 +500,17 @@ func (this *GoLanguageFrontend) handleStructTypeSpec(fset *token.FileSet, typeDe // by its type, it could make sense to name the field according to the type var name string - t := this.handleType(field.Type) + t := this.handleType(fset, field.Type) if field.Names == nil { // retrieve the root type name var typeName = t.GetRoot().GetName().ToString() - this.LogDebug("Handling embedded field of type %s", typeName) + this.LogTrace("Handling embedded field of type %s", typeName) name = typeName } else { - this.LogDebug("Handling field %s", field.Names[0].Name) + this.LogTrace("Handling field %s", field.Names[0].Name) // TODO: Multiple names? name = field.Names[0].Name @@ -470,7 +538,7 @@ func (this *GoLanguageFrontend) handleInterfaceTypeSpec(fset *token.FileSet, typ if !interfaceType.Incomplete { for _, method := range interfaceType.Methods.List { - t := this.handleType(method.Type) + t := this.handleType(fset, method.Type) // Even though this list is called "Methods", it contains all kinds // of things, so we need to proceed with caution. Only if the @@ -482,7 +550,7 @@ func (this *GoLanguageFrontend) handleInterfaceTypeSpec(fset *token.FileSet, typ scope.AddDeclaration((*cpg.Declaration)(m)) } else { - this.LogDebug("Adding %s as super class of interface %s", t.GetName(), (*cpg.Node)(r).GetName()) + this.LogTrace("Adding %s as super class of interface %s", t.GetName(), (*cpg.Node)(r).GetName()) // Otherwise, it contains either types or interfaces. For now we // hope that it only has interfaces. We consider embedded // interfaces as sort of super types for this interface. @@ -497,7 +565,7 @@ func (this *GoLanguageFrontend) handleInterfaceTypeSpec(fset *token.FileSet, typ } func (this *GoLanguageFrontend) handleBlockStmt(fset *token.FileSet, blockStmt *ast.BlockStmt) *cpg.CompoundStatement { - this.LogDebug("Handling block statement: %+v", *blockStmt) + this.LogTrace("Handling block statement: %+v", *blockStmt) c := this.NewCompoundStatement(fset, blockStmt) @@ -507,7 +575,7 @@ func (this *GoLanguageFrontend) handleBlockStmt(fset *token.FileSet, blockStmt * for _, stmt := range blockStmt.List { var s *cpg.Statement - s = this.handleStmt(fset, stmt) + s = this.handleStmt(fset, stmt, blockStmt) if s != nil { // add statement @@ -522,7 +590,7 @@ func (this *GoLanguageFrontend) handleBlockStmt(fset *token.FileSet, blockStmt * } func (this *GoLanguageFrontend) handleForStmt(fset *token.FileSet, forStmt *ast.ForStmt) *cpg.ForStatement { - this.LogDebug("Handling for statement: %+v", *forStmt) + this.LogTrace("Handling for statement: %+v", *forStmt) f := this.NewForStatement(fset, forStmt) @@ -530,19 +598,22 @@ func (this *GoLanguageFrontend) handleForStmt(fset *token.FileSet, forStmt *ast. scope.EnterScope((*cpg.Node)(f)) - if initStatement := this.handleStmt(fset, forStmt.Init); initStatement != nil { + if forStmt.Init != nil { + initStatement := this.handleStmt(fset, forStmt.Init, forStmt) f.SetInitializerStatement(initStatement) } - if condition := this.handleExpr(fset, forStmt.Cond); condition != nil { + if forStmt.Cond != nil { + condition := this.handleExpr(fset, forStmt.Cond) f.SetCondition(condition) } - if iter := this.handleStmt(fset, forStmt.Post); iter != nil { + if forStmt.Post != nil { + iter := this.handleStmt(fset, forStmt.Post, forStmt) f.SetIterationStatement(iter) } - if body := this.handleStmt(fset, forStmt.Body); body != nil { + if body := this.handleStmt(fset, forStmt.Body, forStmt); body != nil { f.SetStatement(body) } @@ -551,8 +622,53 @@ func (this *GoLanguageFrontend) handleForStmt(fset *token.FileSet, forStmt *ast. return f } +func (this *GoLanguageFrontend) handleRangeStmt(fset *token.FileSet, rangeStmt *ast.RangeStmt) *cpg.ForEachStatement { + this.LogTrace("Handling range statement: %+v", *rangeStmt) + + f := this.NewForEachStatement(fset, rangeStmt) + + var scope = this.GetScopeManager() + + scope.EnterScope((*cpg.Node)(f)) + + // TODO: Support other use cases that do not use DEFINE + if rangeStmt.Tok == token.DEFINE { + stmt := this.NewDeclarationStatement(fset, rangeStmt) + + // TODO: not really the best way to deal with this + // TODO: key type is always int. we could set this + var keyName = rangeStmt.Key.(*ast.Ident).Name + + key := this.NewVariableDeclaration(fset, rangeStmt.Key, keyName) + this.GetScopeManager().AddDeclaration((*cpg.Declaration)(key)) + stmt.AddToPropertyEdgeDeclaration((*cpg.Declaration)(key)) + + if rangeStmt.Value != nil { + // TODO: not really the best way to deal with this + // TODO: key type is always int. we could set this + var valueName = rangeStmt.Value.(*ast.Ident).Name + + value := this.NewVariableDeclaration(fset, rangeStmt.Key, valueName) + this.GetScopeManager().AddDeclaration((*cpg.Declaration)(value)) + stmt.AddToPropertyEdgeDeclaration((*cpg.Declaration)(value)) + } + + f.SetVariable((*cpg.Statement)(stmt)) + } + + iterable := (*cpg.Statement)(this.handleExpr(fset, rangeStmt.X)) + f.SetIterable(iterable) + + body := this.handleStmt(fset, rangeStmt.Body, rangeStmt) + f.SetStatement(body) + + scope.LeaveScope((*cpg.Node)(f)) + + return f +} + func (this *GoLanguageFrontend) handleReturnStmt(fset *token.FileSet, returnStmt *ast.ReturnStmt) *cpg.ReturnStatement { - this.LogDebug("Handling return statement: %+v", *returnStmt) + this.LogTrace("Handling return statement: %+v", *returnStmt) r := this.NewReturnStatement(fset, returnStmt) @@ -572,7 +688,7 @@ func (this *GoLanguageFrontend) handleReturnStmt(fset *token.FileSet, returnStmt } func (this *GoLanguageFrontend) handleIncDecStmt(fset *token.FileSet, incDecStmt *ast.IncDecStmt) *cpg.UnaryOperator { - this.LogDebug("Handling decimal increment statement: %+v", *incDecStmt) + this.LogTrace("Handling decimal increment statement: %+v", *incDecStmt) var opCode string if incDecStmt.Tok == token.INC { @@ -592,8 +708,8 @@ func (this *GoLanguageFrontend) handleIncDecStmt(fset *token.FileSet, incDecStmt return u } -func (this *GoLanguageFrontend) handleStmt(fset *token.FileSet, stmt ast.Stmt) (s *cpg.Statement) { - this.LogDebug("Handling statement (%T): %+v", stmt, stmt) +func (this *GoLanguageFrontend) handleStmt(fset *token.FileSet, stmt ast.Stmt, parent ast.Stmt) (s *cpg.Statement) { + this.LogTrace("Handling statement (%T): %+v", stmt, stmt) switch v := stmt.(type) { case *ast.ExprStmt: @@ -601,9 +717,11 @@ func (this *GoLanguageFrontend) handleStmt(fset *token.FileSet, stmt ast.Stmt) ( // so we do not need an expression statement wrapper s = (*cpg.Statement)(this.handleExpr(fset, v.X)) case *ast.AssignStmt: - s = (*cpg.Statement)(this.handleAssignStmt(fset, v)) + s = (*cpg.Statement)(this.handleAssignStmt(fset, v, parent)) case *ast.DeclStmt: s = (*cpg.Statement)(this.handleDeclStmt(fset, v)) + case *ast.GoStmt: + s = (*cpg.Statement)(this.handleGoStmt(fset, v)) case *ast.IfStmt: s = (*cpg.Statement)(this.handleIfStmt(fset, v)) case *ast.SwitchStmt: @@ -614,13 +732,16 @@ func (this *GoLanguageFrontend) handleStmt(fset *token.FileSet, stmt ast.Stmt) ( s = (*cpg.Statement)(this.handleBlockStmt(fset, v)) case *ast.ForStmt: s = (*cpg.Statement)(this.handleForStmt(fset, v)) + case *ast.RangeStmt: + s = (*cpg.Statement)(this.handleRangeStmt(fset, v)) case *ast.ReturnStmt: s = (*cpg.Statement)(this.handleReturnStmt(fset, v)) case *ast.IncDecStmt: s = (*cpg.Statement)(this.handleIncDecStmt(fset, v)) default: - this.LogError("Not parsing statement of type %T yet: %+v", v, v) - s = nil + msg := fmt.Sprintf("Not parsing statement of type %T yet: %s", v, code(fset, v)) + this.LogError(msg) + s = (*cpg.Statement)(this.NewProblemExpression(fset, v, msg)) } if s != nil { @@ -631,7 +752,7 @@ func (this *GoLanguageFrontend) handleStmt(fset *token.FileSet, stmt ast.Stmt) ( } func (this *GoLanguageFrontend) handleExpr(fset *token.FileSet, expr ast.Expr) (e *cpg.Expression) { - this.LogDebug("Handling expression (%T): %+v", expr, expr) + this.LogTrace("Handling expression (%T): %+v", expr, expr) switch v := expr.(type) { case *ast.CallExpr: @@ -646,12 +767,16 @@ func (this *GoLanguageFrontend) handleExpr(fset *token.FileSet, expr ast.Expr) ( e = (*cpg.Expression)(this.handleStarExpr(fset, v)) case *ast.SelectorExpr: e = (*cpg.Expression)(this.handleSelectorExpr(fset, v)) + case *ast.SliceExpr: + e = (*cpg.Expression)(this.handleSliceExpr(fset, v)) case *ast.KeyValueExpr: e = (*cpg.Expression)(this.handleKeyValueExpr(fset, v)) case *ast.BasicLit: e = (*cpg.Expression)(this.handleBasicLit(fset, v)) case *ast.CompositeLit: e = (*cpg.Expression)(this.handleCompositeLit(fset, v)) + case *ast.FuncLit: + e = (*cpg.Expression)(this.handleFuncLit(fset, v)) case *ast.Ident: e = (*cpg.Expression)(this.handleIdent(fset, v)) case *ast.TypeAssertExpr: @@ -659,9 +784,9 @@ func (this *GoLanguageFrontend) handleExpr(fset *token.FileSet, expr ast.Expr) ( case *ast.ParenExpr: e = this.handleExpr(fset, v.X) default: - this.LogError("Could not parse expression of type %T: %+v", v, v) - // TODO: return an error instead? - e = nil + msg := fmt.Sprintf("Not parsing expression of type %T yet: %s", v, code(fset, v)) + this.LogError(msg) + e = (*cpg.Expression)(this.NewProblemExpression(fset, v, msg)) } if e != nil { @@ -671,67 +796,72 @@ func (this *GoLanguageFrontend) handleExpr(fset *token.FileSet, expr ast.Expr) ( return } -func (this *GoLanguageFrontend) handleAssignStmt(fset *token.FileSet, assignStmt *ast.AssignStmt) (expr *cpg.Expression) { - this.LogDebug("Handling assignment statement: %+v", assignStmt) +func (this *GoLanguageFrontend) handleAssignStmt(fset *token.FileSet, assignStmt *ast.AssignStmt, parent ast.Stmt) (expr *cpg.Expression) { + this.LogTrace("Handling assignment statement: %+v", assignStmt) - // TODO: more than one Rhs?! - rhs := this.handleExpr(fset, assignStmt.Rhs[0]) + this.LogDebug("Parent: %#v", parent) - if assignStmt.Tok == token.DEFINE { - // lets create a variable declaration (wrapped with a declaration stmt) with this, because we define the variable here - stmt := this.NewDeclarationStatement(fset, assignStmt) + var rhs = []*cpg.Expression{} + var lhs = []*cpg.Expression{} + for _, expr := range assignStmt.Lhs { + lhs = append(lhs, this.handleExpr(fset, expr)) + } - var name = assignStmt.Lhs[0].(*ast.Ident).Name + for _, expr := range assignStmt.Rhs { + rhs = append(rhs, this.handleExpr(fset, expr)) + } - // TODO: assignment of multiple values - d := this.NewVariableDeclaration(fset, assignStmt, name) + a := this.NewAssignExpression(fset, assignStmt, "=") - if rhs != nil { - d.SetInitializer(rhs) - } + a.SetLHS(lhs) + a.SetRHS(rhs) - this.GetScopeManager().AddDeclaration((*cpg.Declaration)(d)) + // We need to explicitly set the operator code on this assignment as + // something which potentially declares a variable, so we can resolve this + // in our extra pass. + if assignStmt.Tok == token.DEFINE { + a.SetOperatorCode(":=") + } - stmt.SetSingleDeclaration((*cpg.Declaration)(d)) + expr = (*cpg.Expression)(a) - expr = (*cpg.Expression)(stmt) - } else { - lhs := this.handleExpr(fset, assignStmt.Lhs[0]) + return +} - b := this.NewBinaryOperator(fset, assignStmt, "=") +func (this *GoLanguageFrontend) handleDeclStmt(fset *token.FileSet, declStmt *ast.DeclStmt) (expr *cpg.Expression) { + this.LogTrace("Handling declaration statement: %+v", *declStmt) - if lhs != nil { - b.SetLHS(lhs) - } + // Lets create a variable declaration (wrapped with a declaration stmt) with + // this, because we define the variable here + stmt := this.NewDeclarationStatement(fset, declStmt) - if rhs != nil { - b.SetRHS(rhs) - } + decls := this.handleDecl(fset, declStmt.Decl) - expr = (*cpg.Expression)(b) + // Loop over the declarations and add them to the scope as well as the statement. + for _, d := range decls { + stmt.AddToPropertyEdgeDeclaration(d) + this.GetScopeManager().AddDeclaration(d) } - return + return (*cpg.Expression)(stmt) } -func (this *GoLanguageFrontend) handleDeclStmt(fset *token.FileSet, declStmt *ast.DeclStmt) (expr *cpg.Expression) { - this.LogDebug("Handling declaration statement: %+v", *declStmt) - - // lets create a variable declaration (wrapped with a declaration stmt) with this, - // because we define the variable here - stmt := this.NewDeclarationStatement(fset, declStmt) - - d := this.handleDecl(fset, declStmt.Decl) +// handleGoStmt handles the `go` statement, which is a special keyword in go +// that starts the supplied call expression in a separate Go routine. We cannot +// model this 1:1, so we basically we create a call expression to a built-in call. +func (this *GoLanguageFrontend) handleGoStmt(fset *token.FileSet, goStmt *ast.GoStmt) (expr *cpg.Expression) { + this.LogTrace("Handling go statement: %+v", *goStmt) - stmt.SetSingleDeclaration((*cpg.Declaration)(d)) + ref := (*cpg.Expression)(this.NewDeclaredReferenceExpression(fset, nil, "go")) - this.GetScopeManager().AddDeclaration(d) + call := this.NewCallExpression(fset, goStmt, ref, "go") + call.AddArgument(this.handleCallExpr(fset, goStmt.Call)) - return (*cpg.Expression)(stmt) + return (*cpg.Expression)(call) } func (this *GoLanguageFrontend) handleIfStmt(fset *token.FileSet, ifStmt *ast.IfStmt) (expr *cpg.Expression) { - this.LogDebug("Handling if statement: %+v", *ifStmt) + this.LogTrace("Handling if statement: %+v", *ifStmt) stmt := this.NewIfStatement(fset, ifStmt) @@ -739,23 +869,22 @@ func (this *GoLanguageFrontend) handleIfStmt(fset *token.FileSet, ifStmt *ast.If scope.EnterScope((*cpg.Node)(stmt)) - init := this.handleStmt(fset, ifStmt.Init) - if init != nil { + if ifStmt.Init != nil { + init := this.handleStmt(fset, ifStmt.Init, ifStmt) stmt.SetInitializerStatement(init) } cond := this.handleExpr(fset, ifStmt.Cond) - if cond != nil { - stmt.SetCondition(cond) - } else { - this.LogError("If statement should really have a condition. It is either missing or could not be parsed.") - } + stmt.SetCondition(cond) then := this.handleBlockStmt(fset, ifStmt.Body) - stmt.SetThenStatement((*cpg.Statement)(then)) + // Somehow this can be nil-ish? + if !then.IsNil() { + stmt.SetThenStatement((*cpg.Statement)(then)) + } - els := this.handleStmt(fset, ifStmt.Else) - if els != nil { + if ifStmt.Else != nil { + els := this.handleStmt(fset, ifStmt.Else, ifStmt) stmt.SetElseStatement((*cpg.Statement)(els)) } @@ -765,12 +894,12 @@ func (this *GoLanguageFrontend) handleIfStmt(fset *token.FileSet, ifStmt *ast.If } func (this *GoLanguageFrontend) handleSwitchStmt(fset *token.FileSet, switchStmt *ast.SwitchStmt) (expr *cpg.Expression) { - this.LogDebug("Handling switch statement: %+v", *switchStmt) + this.LogTrace("Handling switch statement: %+v", *switchStmt) s := this.NewSwitchStatement(fset, switchStmt) if switchStmt.Init != nil { - s.SetInitializerStatement(this.handleStmt(fset, switchStmt.Init)) + s.SetInitializerStatement(this.handleStmt(fset, switchStmt.Init, switchStmt)) } if switchStmt.Tag != nil { @@ -783,7 +912,7 @@ func (this *GoLanguageFrontend) handleSwitchStmt(fset *token.FileSet, switchStmt } func (this *GoLanguageFrontend) handleCaseClause(fset *token.FileSet, caseClause *ast.CaseClause) (expr *cpg.Expression) { - this.LogDebug("Handling case clause: %+v", *caseClause) + this.LogTrace("Handling case clause: %+v", *caseClause) var s *cpg.Statement @@ -805,7 +934,7 @@ func (this *GoLanguageFrontend) handleCaseClause(fset *token.FileSet, caseClause } for _, stmt := range caseClause.Body { - s = this.handleStmt(fset, stmt) + s = this.handleStmt(fset, stmt, caseClause) if s != nil && block != nil && !block.IsNil() { // add statement @@ -820,6 +949,28 @@ func (this *GoLanguageFrontend) handleCaseClause(fset *token.FileSet, caseClause func (this *GoLanguageFrontend) handleCallExpr(fset *token.FileSet, callExpr *ast.CallExpr) *cpg.Expression { var c *cpg.CallExpression + + // In Go, regular cast expressions (not type asserts are modelled as calls). + // In this case, the Fun contains a type expression. + switch v := callExpr.Fun.(type) { + case *ast.ArrayType, + *ast.StructType, + *ast.FuncType, + *ast.InterfaceType, + *ast.MapType, + *ast.ChanType: + this.LogDebug("Handling cast expression: %#v", callExpr) + + cast := this.NewCastExpression(fset, callExpr) + cast.SetCastType(this.handleType(fset, v)) + + if len(callExpr.Args) > 1 { + cast.SetExpression(this.handleExpr(fset, callExpr.Args[0])) + } + + return (*cpg.Expression)(cast) + } + // parse the Fun field, to see which kind of expression it is var reference = this.handleExpr(fset, callExpr.Fun) @@ -842,13 +993,13 @@ func (this *GoLanguageFrontend) handleCallExpr(fset *token.FileSet, callExpr *as } if isMemberExpression { - this.LogDebug("Fun is a member call to %s", name) + this.LogTrace("Fun is a member call to %s", name) m := this.NewMemberCallExpression(fset, callExpr, reference) c = (*cpg.CallExpression)(m) } else { - this.LogDebug("Handling regular call expression to %s", name) + this.LogTrace("Handling regular call expression to %s", name) c = this.NewCallExpression(fset, callExpr, reference, name) } @@ -861,25 +1012,50 @@ func (this *GoLanguageFrontend) handleCallExpr(fset *token.FileSet, callExpr *as } } - // reference.disconnectFromGraph() - return (*cpg.Expression)(c) } -func (this *GoLanguageFrontend) handleIndexExpr(fset *token.FileSet, indexExpr *ast.IndexExpr) *cpg.Expression { +func (this *GoLanguageFrontend) handleIndexExpr(fset *token.FileSet, indexExpr *ast.IndexExpr) *cpg.ArraySubscriptionExpression { a := this.NewArraySubscriptionExpression(fset, indexExpr) a.SetArrayExpression(this.handleExpr(fset, indexExpr.X)) a.SetSubscriptExpression(this.handleExpr(fset, indexExpr.Index)) - return (*cpg.Expression)(a) + return a +} + +// handleSliceExpr handles a [ast.SliceExpr], which is an extended version of +// [ast.IndexExpr]. We are modelling this as a combination of a +// [cpg.ArraySubscriptionExpression] that contains a [cpg.RangeExpression] as +// its subscriptExpression to share some code between this and an index +// expression. +func (this *GoLanguageFrontend) handleSliceExpr(fset *token.FileSet, sliceExpr *ast.SliceExpr) *cpg.ArraySubscriptionExpression { + a := this.NewArraySubscriptionExpression(fset, sliceExpr) + + a.SetArrayExpression(this.handleExpr(fset, sliceExpr.X)) + + // Build the slice expression + s := this.NewRangeExpression(fset, sliceExpr) + if sliceExpr.Low != nil { + s.SetFloor(this.handleExpr(fset, sliceExpr.Low)) + } + if sliceExpr.High != nil { + s.SetCeiling(this.handleExpr(fset, sliceExpr.High)) + } + if sliceExpr.Max != nil { + s.SetThird(this.handleExpr(fset, sliceExpr.Max)) + } + + a.SetSubscriptExpression((*cpg.Expression)(s)) + + return a } func (this *GoLanguageFrontend) handleNewExpr(fset *token.FileSet, callExpr *ast.CallExpr) *cpg.Expression { n := this.NewNewExpression(fset, callExpr) // first argument is type - t := this.handleType(callExpr.Args[0]) + t := this.handleType(fset, callExpr.Args[0]) // new is a pointer, so need to reference the type with a pointer var pointer = jnigi.NewObjectRef(cpg.PointerOriginClass) @@ -907,7 +1083,7 @@ func (this *GoLanguageFrontend) handleMakeExpr(fset *token.FileSet, callExpr *as } // first argument is always the type, handle it - t := this.handleType(callExpr.Args[0]) + t := this.handleType(fset, callExpr.Args[0]) // actually make() can make more than just arrays, i.e. channels and maps if _, isArray := callExpr.Args[0].(*ast.ArrayType); isArray { @@ -949,13 +1125,8 @@ func (this *GoLanguageFrontend) handleBinaryExpr(fset *token.FileSet, binaryExpr lhs := this.handleExpr(fset, binaryExpr.X) rhs := this.handleExpr(fset, binaryExpr.Y) - if lhs != nil { - b.SetLHS(lhs) - } - - if rhs != nil { - b.SetRHS(rhs) - } + b.SetLHS(lhs) + b.SetRHS(rhs) return b } @@ -1003,7 +1174,7 @@ func (this *GoLanguageFrontend) handleSelectorExpr(fset *token.FileSet, selector // we need to set the name to a FQN-style, including the package scope. the call resolver will then resolve this fqn := fmt.Sprintf("%s.%s", base.GetName(), selectorExpr.Sel.Name) - this.LogDebug("Trying to parse the fqn '%s'", fqn) + this.LogTrace("Trying to parse the fqn '%s'", fqn) name := this.ParseName(fqn) @@ -1032,7 +1203,7 @@ func (this *GoLanguageFrontend) handleSelectorExpr(fset *token.FileSet, selector } func (this *GoLanguageFrontend) handleKeyValueExpr(fset *token.FileSet, expr *ast.KeyValueExpr) *cpg.KeyValueExpression { - this.LogDebug("Handling key value expression %+v", *expr) + this.LogTrace("Handling key value expression %+v", *expr) k := this.NewKeyValueExpression(fset, expr) @@ -1050,7 +1221,7 @@ func (this *GoLanguageFrontend) handleKeyValueExpr(fset *token.FileSet, expr *as } func (this *GoLanguageFrontend) handleBasicLit(fset *token.FileSet, lit *ast.BasicLit) *cpg.Literal { - this.LogDebug("Handling literal %+v", *lit) + this.LogTrace("Handling literal %+v", *lit) var value cpg.Castable var t *cpg.Type @@ -1075,8 +1246,11 @@ func (this *GoLanguageFrontend) handleBasicLit(fset *token.FileSet, lit *ast.Bas value = cpg.NewDouble(f) t = cpg.TypeParser_createFrom("float64", lang) case token.IMAG: + // TODO + t = &cpg.UnknownType_getUnknown(lang).Type case token.CHAR: value = cpg.NewString(lit.Value) + t = cpg.TypeParser_createFrom("rune", lang) break } @@ -1089,12 +1263,12 @@ func (this *GoLanguageFrontend) handleBasicLit(fset *token.FileSet, lit *ast.Bas // ConstructExpression and a list of KeyValueExpressions. The problem is that we need to add the list // as a first argument of the construct expression. func (this *GoLanguageFrontend) handleCompositeLit(fset *token.FileSet, lit *ast.CompositeLit) *cpg.ConstructExpression { - this.LogDebug("Handling composite literal %+v", *lit) + this.LogTrace("Handling composite literal %+v", *lit) c := this.NewConstructExpression(fset, lit) // parse the type field, to see which kind of expression it is - var typ = this.handleType(lit.Type) + var typ = this.handleType(fset, lit.Type) if typ != nil { (*cpg.Node)(c).SetName(typ.GetName()) @@ -1110,17 +1284,37 @@ func (this *GoLanguageFrontend) handleCompositeLit(fset *token.FileSet, lit *ast // from its initialization. c.AddPrevDFG((*cpg.Node)(l)) + var exprs = []*cpg.Expression{} for _, elem := range lit.Elts { expr := this.handleExpr(fset, elem) if expr != nil { - l.AddInitializer(expr) + exprs = append(exprs, expr) } } + l.SetInitializers(exprs) + return c } +// handleFuncLit handles a function literal, which we need to translate into a combination of a +// LambdaExpression and a function declaration. +func (this *GoLanguageFrontend) handleFuncLit(fset *token.FileSet, lit *ast.FuncLit) *cpg.LambdaExpression { + this.LogTrace("Handling function literal %#v", *lit) + + l := this.NewLambdaExpression(fset, lit) + + // Parse the expression as a function declaration with a little trick + funcDecl := this.handleFuncDecl(fset, &ast.FuncDecl{Type: lit.Type, Body: lit.Body, Name: ast.NewIdent("")}) + + this.LogTrace("Function of literal is: %#v", funcDecl) + + l.SetFunction(funcDecl) + + return l +} + func (this *GoLanguageFrontend) handleIdent(fset *token.FileSet, ident *ast.Ident) *cpg.Expression { lang, err := this.GetLanguage() if err != nil { @@ -1158,18 +1352,21 @@ func (this *GoLanguageFrontend) handleTypeAssertExpr(fset *token.FileSet, assert expr := this.handleExpr(fset, assert.X) // Parse the type - typ := this.handleType(assert.Type) + typ := this.handleType(fset, assert.Type) cast.SetExpression(expr) - cast.SetCastType(typ) + + if typ != nil { + cast.SetCastType(typ) + } return cast } -func (this *GoLanguageFrontend) handleType(typeExpr ast.Expr) *cpg.Type { +func (this *GoLanguageFrontend) handleType(fset *token.FileSet, typeExpr ast.Expr) *cpg.Type { var err error - this.LogDebug("Parsing type %T: %+v", typeExpr, typeExpr) + this.LogTrace("Parsing type %T: %s", typeExpr, code(fset, typeExpr)) lang, err := this.GetLanguage() if err != nil { @@ -1181,20 +1378,20 @@ func (this *GoLanguageFrontend) handleType(typeExpr ast.Expr) *cpg.Type { var name string if this.isBuiltinType(v.Name) { name = v.Name - this.LogDebug("non-fqn type: %s", name) + this.LogTrace("non-fqn type: %s", name) } else { name = fmt.Sprintf("%s.%s", this.File.Name.Name, v.Name) - this.LogDebug("fqn type: %s", name) + this.LogTrace("fqn type: %s", name) } return cpg.TypeParser_createFrom(name, lang) case *ast.SelectorExpr: // small shortcut fqn := fmt.Sprintf("%s.%s", v.X.(*ast.Ident).Name, v.Sel.Name) - this.LogDebug("FQN type: %s", fqn) + this.LogTrace("FQN type: %s", fqn) return cpg.TypeParser_createFrom(fqn, lang) case *ast.StarExpr: - t := this.handleType(v.X) + t := this.handleType(fset, v.X) var i = jnigi.NewObjectRef(cpg.PointerOriginClass) err = env.GetStaticField(cpg.PointerOriginClass, "POINTER", i) @@ -1202,11 +1399,11 @@ func (this *GoLanguageFrontend) handleType(typeExpr ast.Expr) *cpg.Type { log.Fatal(err) } - this.LogDebug("Pointer to %s", t.GetName()) + this.LogTrace("Pointer to %s", t.GetName()) return t.Reference(i) case *ast.ArrayType: - t := this.handleType(v.Elt) + t := this.handleType(fset, v.Elt) var i = jnigi.NewObjectRef(cpg.PointerOriginClass) err = env.GetStaticField(cpg.PointerOriginClass, "ARRAY", i) @@ -1214,27 +1411,27 @@ func (this *GoLanguageFrontend) handleType(typeExpr ast.Expr) *cpg.Type { log.Fatal(err) } - this.LogDebug("Array of %s", t.GetName()) + this.LogTrace("Array of %s", t.GetName()) return t.Reference(i) case *ast.MapType: // we cannot properly represent Golangs built-in map types, yet so we have // to make a shortcut here and represent it as a Java-like map type. t := cpg.TypeParser_createFrom("map", lang) - keyType := this.handleType(v.Key) - valueType := this.handleType(v.Value) + keyType := this.handleType(fset, v.Key) + valueType := this.handleType(fset, v.Value) // TODO(oxisto): Find a better way to represent casts - (&(cpg.ObjectType{Type: *t})).AddGeneric(keyType) - (&(cpg.ObjectType{Type: *t})).AddGeneric(valueType) + (*cpg.ObjectType)(t).AddGeneric(keyType) + (*cpg.ObjectType)(t).AddGeneric(valueType) return t case *ast.ChanType: // handle them similar to maps t := cpg.TypeParser_createFrom("chan", lang) - chanType := this.handleType(v.Value) + chanType := this.handleType(fset, v.Value) - (&(cpg.ObjectType{Type: *t})).AddGeneric(chanType) + (*cpg.ObjectType)(t).AddGeneric(chanType) return t case *ast.FuncType: @@ -1243,7 +1440,7 @@ func (this *GoLanguageFrontend) handleType(typeExpr ast.Expr) *cpg.Type { var returnTypes = []*cpg.Type{} for _, param := range v.Params.List { - parameterTypes = append(parameterTypes, this.handleType(param.Type)) + parameterTypes = append(parameterTypes, this.handleType(fset, param.Type)) } parametersTypesList, err = cpg.ListOf(parameterTypes) @@ -1253,7 +1450,7 @@ func (this *GoLanguageFrontend) handleType(typeExpr ast.Expr) *cpg.Type { if v.Results != nil { for _, ret := range v.Results.List { - returnTypes = append(returnTypes, this.handleType(ret.Type)) + returnTypes = append(returnTypes, this.handleType(fset, ret.Type)) } } @@ -1277,6 +1474,41 @@ func (this *GoLanguageFrontend) handleType(typeExpr ast.Expr) *cpg.Type { } return &cpg.Type{ObjectRef: t} + case *ast.InterfaceType: + var name = "interface{" + // We do not really support dedicated interfaces types, so all we can for now + // is parse it as an object type with a pseudo-name + for _, method := range v.Methods.List { + name += this.handleType(fset, method.Type).GetName().ToString() + } + + name += "}" + + return cpg.TypeParser_createFrom(name, lang) + case *ast.IndexExpr: + // This is a type with one type parameter. First we need to parse the "X" expression as a type + var t = this.handleType(fset, v.X) + + // Then we parse the "Index" as a type parameter + var genericType = this.handleType(fset, v.Index) + + (*cpg.ObjectType)(t).AddGeneric(genericType) + + return t + case *ast.IndexListExpr: + // This is a type with two type parameters. First we need to parse the "X" expression as a type + var t = this.handleType(fset, v.X) + + // Then we parse the "Indices" as a type parameter + for _, index := range v.Indices { + var genericType = this.handleType(fset, index) + + (*cpg.ObjectType)(t).AddGeneric(genericType) + } + + return t + default: + this.LogError("Not parsing type of type %T yet. Defaulting to unknown type", v) } return &cpg.UnknownType_getUnknown(lang).Type diff --git a/cpg-language-go/src/main/golang/frontend/statement_builder.go b/cpg-language-go/src/main/golang/frontend/statement_builder.go index f483536e5e..f951d41392 100644 --- a/cpg-language-go/src/main/golang/frontend/statement_builder.go +++ b/cpg-language-go/src/main/golang/frontend/statement_builder.go @@ -54,6 +54,10 @@ func (frontend *GoLanguageFrontend) NewForStatement(fset *token.FileSet, astNode return (*cpg.ForStatement)(frontend.NewStatement("ForStatement", fset, astNode)) } +func (frontend *GoLanguageFrontend) NewForEachStatement(fset *token.FileSet, astNode ast.Node) *cpg.ForEachStatement { + return (*cpg.ForEachStatement)(frontend.NewStatement("ForEachStatement", fset, astNode)) +} + func (frontend *GoLanguageFrontend) NewSwitchStatement(fset *token.FileSet, astNode ast.Node) *cpg.SwitchStatement { return (*cpg.SwitchStatement)(frontend.NewStatement("SwitchStatement", fset, astNode)) } diff --git a/cpg-language-go/src/main/golang/lib/cpg/main.go b/cpg-language-go/src/main/golang/lib/cpg/main.go index 9ca8326023..eb28bdee51 100644 --- a/cpg-language-go/src/main/golang/lib/cpg/main.go +++ b/cpg-language-go/src/main/golang/lib/cpg/main.go @@ -58,6 +58,7 @@ func Java_de_fraunhofer_aisec_cpg_frontends_golang_GoLanguageFrontend_parseInter nil, nil, ast.CommentMap{}, + "", nil, } @@ -86,6 +87,8 @@ func Java_de_fraunhofer_aisec_cpg_frontends_golang_GoLanguageFrontend_parseInter log.Fatal(err) } + goFrontend.TopLevel = string(topLevel) + fset := token.NewFileSet() file, err := parser.ParseFile(fset, string(path), string(src), parser.ParseComments) if err != nil { diff --git a/cpg-language-go/src/main/golang/statements.go b/cpg-language-go/src/main/golang/statements.go index 481a2fe588..35bfcb001a 100644 --- a/cpg-language-go/src/main/golang/statements.go +++ b/cpg-language-go/src/main/golang/statements.go @@ -38,6 +38,7 @@ type SwitchStatement Statement type CaseStatement Statement type DefaultStatement Statement type ForStatement Statement +type ForEachStatement Statement const StatementsPackage = GraphPackage + "/statements" const StatementClass = StatementsPackage + "/Statement" @@ -51,6 +52,10 @@ func (f *DeclarationStatement) SetSingleDeclaration(d *Declaration) { (*jnigi.ObjectRef)(f).CallMethod(env, "setSingleDeclaration", nil, (*jnigi.ObjectRef)(d).Cast(DeclarationClass)) } +func (f *DeclarationStatement) AddToPropertyEdgeDeclaration(d *Declaration) { + (*jnigi.ObjectRef)(f).CallMethod(env, "addToPropertyEdgeDeclaration", nil, (*jnigi.ObjectRef)(d).Cast(DeclarationClass)) +} + func (m *IfStatement) SetThenStatement(s *Statement) { (*jnigi.ObjectRef)(m).SetField(env, "thenStatement", (*jnigi.ObjectRef)(s).Cast(StatementClass)) } @@ -91,6 +96,18 @@ func (fw *ForStatement) SetStatement(s *Statement) { (*jnigi.ObjectRef)(fw).SetField(env, "statement", (*jnigi.ObjectRef)(s).Cast(StatementClass)) } +func (fw *ForEachStatement) SetStatement(s *Statement) { + (*jnigi.ObjectRef)(fw).SetField(env, "statement", (*jnigi.ObjectRef)(s).Cast(StatementClass)) +} + +func (fw *ForEachStatement) SetIterable(s *Statement) { + (*jnigi.ObjectRef)(fw).CallMethod(env, "setIterable", nil, (*jnigi.ObjectRef)(s).Cast(StatementClass)) +} + +func (fw *ForEachStatement) SetVariable(s *Statement) { + (*jnigi.ObjectRef)(fw).SetField(env, "variable", (*jnigi.ObjectRef)(s).Cast(StatementClass)) +} + func (fw *ForStatement) SetIterationStatement(s *Statement) { (*jnigi.ObjectRef)(fw).SetField(env, "iterationStatement", (*jnigi.ObjectRef)(s).Cast(StatementClass)) } diff --git a/cpg-language-go/src/main/golang/types.go b/cpg-language-go/src/main/golang/types.go index 73e6bd48cf..0c03ef5abd 100644 --- a/cpg-language-go/src/main/golang/types.go +++ b/cpg-language-go/src/main/golang/types.go @@ -37,6 +37,7 @@ import ( var env *jnigi.Env type Type struct{ *jnigi.ObjectRef } +type ObjectType Type const TypesPackage = GraphPackage + "/types" const TypeClass = TypesPackage + "/Type" @@ -64,10 +65,6 @@ func (*Type) IsArray() bool { return false } -type ObjectType struct { - Type -} - func (*ObjectType) GetClassName() string { return ObjectTypeClass } 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 1c10b07a19..82e8ebafb3 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 @@ -32,11 +32,14 @@ import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend import de.fraunhofer.aisec.cpg.frontends.SupportsParallelParsing import de.fraunhofer.aisec.cpg.frontends.TranslationException import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration +import de.fraunhofer.aisec.cpg.passes.GoExtraPass +import de.fraunhofer.aisec.cpg.passes.order.RegisterExtraPass import de.fraunhofer.aisec.cpg.sarif.PhysicalLocation import java.io.File import java.io.FileOutputStream @SupportsParallelParsing(false) +@RegisterExtraPass(GoExtraPass::class) class GoLanguageFrontend( language: Language, config: TranslationConfiguration, @@ -84,7 +87,7 @@ class GoLanguageFrontend( override fun parse(file: File): TranslationUnitDeclaration { return parseInternal( file.readText(Charsets.UTF_8), - file.path, + file.absolutePath, config.topLevel?.absolutePath ?: file.parent ) } diff --git a/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/GoExtraPass.kt b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/GoExtraPass.kt new file mode 100644 index 0000000000..62ae002773 --- /dev/null +++ b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/GoExtraPass.kt @@ -0,0 +1,286 @@ +/* + * Copyright (c) 2023, 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.TranslationResult +import de.fraunhofer.aisec.cpg.frontends.Language +import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend +import de.fraunhofer.aisec.cpg.frontends.golang.GoLanguage +import de.fraunhofer.aisec.cpg.graph.* +import de.fraunhofer.aisec.cpg.graph.declarations.IncludeDeclaration +import de.fraunhofer.aisec.cpg.graph.declarations.NamespaceDeclaration +import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration +import de.fraunhofer.aisec.cpg.graph.scopes.Scope +import de.fraunhofer.aisec.cpg.graph.statements.DeclarationStatement +import de.fraunhofer.aisec.cpg.graph.statements.ForEachStatement +import de.fraunhofer.aisec.cpg.graph.statements.expressions.AssignExpression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.CastExpression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression +import de.fraunhofer.aisec.cpg.graph.types.PointerType +import de.fraunhofer.aisec.cpg.graph.types.Type +import de.fraunhofer.aisec.cpg.graph.types.TypeParser +import de.fraunhofer.aisec.cpg.graph.types.UnknownType +import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker +import de.fraunhofer.aisec.cpg.passes.inference.startInference +import de.fraunhofer.aisec.cpg.passes.order.ExecuteBefore + +/** + * This pass takes care of several things that we need to clean up, once all translation units are + * successfully parsed, but before any of the remaining CPG passes, such as call resolving occurs. + * + * ## Add Type Listeners for Key/Value Variables in For-Each Statements + * + * In Go, a common idiom is to use a short assignment in a for-each statement to declare a key and + * value object without explicitly specifying the type. + * + * ```go + * var bytes = []byte{1,2,3,4} + * for key, value := range bytes { + * // key is of type int; value of type byte + * fmt.Printf("bytes[%d]=%d\n", key, value) + * } + * ``` + * + * The key variable is always of type `int`, whereas the value variable depends on the iterated + * expression. Therefore, we set up type listeners based on the iterated object. + * + * ## Infer NamespaceDeclarations for Import Packages + * + * We want to infer namespace declarations for import packages, that are unknown to us. This allows + * us to then infer functions in those packages as well. + * + * ## Declare Variables in Short Assignments + * + * We want to implicitly declare variables in a short assignment. We cannot do this in the frontend + * itself, because some of the variables in the assignment might already exist, and those are not + * declared, but just assigned. Only the non-defined variables are declared by the short assignment. + * + * The following short assignment (in the second line) only declares the variable `b` but assigns + * `1` to the already existing variable `a` and `2` to the new variable `b`. + * + * ```go + * var a int + * a, b := 1, 2 + * ``` + * + * In the frontend we only do the assignment, therefore we need to create a new + * [VariableDeclaration] for `b` and inject a [DeclarationStatement]. + * + * ## Converting Call Expressions into Cast Expressions + * + * In Go, it is possible to convert compatible types by "calling" the type name as a function, such + * as + * + * ```go + * var i = int(2.0) + * ``` + * + * This is also possible with more complex types, such as interfaces or aliased types, as long as + * they are compatible. Because types in the same package can be defined in multiple files, we + * cannot decide during the frontend run. Therefore, we need to execute this pass before the + * [CallResolver] and convert certain [CallExpression] nodes into a [CastExpression]. + */ +@ExecuteBefore(VariableUsageResolver::class) +@ExecuteBefore(CallResolver::class) +@ExecuteBefore(DFGPass::class) +class GoExtraPass : Pass(), ScopeProvider { + + override val scope: Scope? + get() = scopeManager.currentScope + + override fun accept(t: TranslationResult) { + scopeManager = t.scopeManager + + val walker = SubgraphWalker.ScopedWalker(scopeManager) + walker.registerHandler { _, parent, node -> + when (node) { + is CallExpression -> handleCall(node, parent) + is IncludeDeclaration -> handleInclude(node) + is AssignExpression -> handleAssign(node) + is ForEachStatement -> handleForEachStatement(node) + } + } + + for (tu in t.translationUnits) { + walker.iterate(tu) + } + } + + /** + * handleForEachStatement adds a [HasType.TypeListener] to the [ForEachStatement.iterable] of an + * [ForEachStatement] in order to determine the types used in [ForEachStatement.variable] (index + * and iterated value). + */ + private fun handleForEachStatement(forEach: ForEachStatement) { + (forEach.iterable as HasType).registerTypeListener( + object : HasType.TypeListener { + override fun typeChanged(src: HasType, root: MutableList, oldType: Type) { + if (src.type is UnknownType) { + return + } + + val variable = forEach.variable + if (variable is DeclarationStatement) { + // The key is the first variable. It is always an int + val keyVariable = + variable.declarations.firstOrNull() as? VariableDeclaration + keyVariable?.type = TypeParser.createFrom("int", forEach.language) + + // The value is the second one. Its type depends on the array type + val valueVariable = + variable.declarations.getOrNull(1) as? VariableDeclaration + ((forEach.iterable as? HasType)?.type as? PointerType)?.let { + valueVariable?.type = it.elementType + } + } + } + + override fun possibleSubTypesChanged(src: HasType, root: MutableList) { + // Nothing to do + } + } + ) + } + + /** + * This function gets called for every [AssignExpression], to check, whether we need to + * implicitly define any variables assigned in the statement. + */ + private fun handleAssign(assign: AssignExpression) { + // Only filter nodes that could potentially declare + if (assign.operatorCode != ":=") { + return + } + + // Loop through the target variables (left-hand side) + for (expr in assign.lhs) { + if (expr is DeclaredReferenceExpression) { + // And try to resolve it + val ref = scopeManager.resolveReference(expr) + if (ref == null) { + // We need to implicitly declare it, if its not declared before. + val decl = newVariableDeclaration(expr.name, expr.type) + decl.location = expr.location + decl.isImplicit = true + decl.initializer = assign.findValue(expr) + + assign.declarations += decl + + // Add it to the scope, so other assignments / references can "see" it. + scopeManager.addDeclaration(decl) + } + } + } + } + + /** + * This function gets called for every [IncludeDeclaration] (which in Go imports a whole + * package) and checks, if we need to infer a [NamespaceDeclaration] for this particular + * include. + */ + // TODO: Somehow, this gets called twice?! + private fun handleInclude(include: IncludeDeclaration) { + // Try to see if we already know about this namespace somehow + val namespace = + scopeManager.resolve(scopeManager.globalScope, true) { + it.name == include.name && it.path == include.filename + } + + // If not, we can infer a namespace declaration, so we can bundle all inferred function + // declarations in there + if (namespace.isEmpty()) { + scopeManager.globalScope + ?.astNode + ?.startInference(scopeManager) + ?.createInferredNamespaceDeclaration(include.name, include.filename) + } + } + + /** + * This function gets called for every [CallExpression] and checks, whether this is actually a + * "calling" a type and is thus a [CastExpression] rather than a [CallExpression]. + */ + private fun handleCall(call: CallExpression, parent: Node?) { + // We need to check, whether the "callee" refers to a type and if yes, convert it into a + // cast expression. And this is only really necessary, if the function call has a single + // argument. + val callee = call.callee + if (parent != null && callee is DeclaredReferenceExpression && call.arguments.size == 1) { + val language = parent.language ?: GoLanguage() + + // First, check if this is a built-in type + if (language.builtInTypes.contains(callee.name.toString())) { + replaceCallWithCast(callee.name.toString(), language, parent, call) + } else { + // If not, then this could still refer to an existing type. We need to make sure + // that we take the current namespace into account + val fqn = + if (callee.name.parent == null) { + scopeManager.currentNamespace.fqn(callee.name.localName) + } else { + callee.name + } + + if (TypeManager.getInstance().typeExists(fqn.toString())) { + replaceCallWithCast(fqn, language, parent, call) + } + } + } + } + + private fun replaceCallWithCast( + typeName: CharSequence, + language: Language, + parent: Node, + call: CallExpression, + ) { + val cast = parent.newCastExpression(call.code) + cast.location = call.location + cast.castType = TypeParser.createFrom(typeName, false, language) + cast.expression = call.arguments.single() + + if (parent !is ArgumentHolder) { + log.error( + "Parent AST node of call expression is not an argument holder. Cannot convert to cast expression. Further analysis might not be entirely accurate." + ) + return + } + + val success = parent.replaceArgument(call, cast) + if (!success) { + log.error( + "Replacing call expression with cast expression was not successful. Further analysis might not be entirely accurate." + ) + } else { + call.disconnectFromGraph() + } + } + + override fun cleanup() { + // Nothing to do + } +} diff --git a/cpg-language-go/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/DeclarationTest.kt b/cpg-language-go/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/DeclarationTest.kt index 569c429b1b..5e379141c1 100644 --- a/cpg-language-go/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/DeclarationTest.kt +++ b/cpg-language-go/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/DeclarationTest.kt @@ -27,11 +27,15 @@ package de.fraunhofer.aisec.cpg.frontends.golang import de.fraunhofer.aisec.cpg.TestUtils import de.fraunhofer.aisec.cpg.assertFullName +import de.fraunhofer.aisec.cpg.assertLiteralValue +import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.byNameOrNull import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.NamespaceDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration +import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression +import de.fraunhofer.aisec.cpg.graph.variables import java.nio.file.Path import kotlin.test.* @@ -113,4 +117,77 @@ class DeclarationTest { assertContains(myInterface.superTypeDeclarations, myOtherInterface) assertTrue(myInterface.superClasses.any { it.name == myOtherInterface.name }) } + + @Test + fun testMultipleDeclarations() { + val topLevel = Path.of("src", "test", "resources", "golang") + val tu = + TestUtils.analyzeAndGetFirstTU( + listOf(topLevel.resolve("declare.go").toFile()), + topLevel, + true + ) { + it.registerLanguage() + } + assertNotNull(tu) + + val main = tu.functions["main.main"] + assertNotNull(main) + + // We should have 7 variables (a, b, c, d, e, f, g) + assertEquals(7, tu.variables.size) + + // Four should have (literal) initializers + val a = main.variables["a"] + assertLiteralValue(1, a?.initializer) + + val b = main.variables["b"] + assertLiteralValue(2, b?.initializer) + + val c = main.variables["c"] + assertLiteralValue(3, c?.initializer) + + val d = main.variables["d"] + assertLiteralValue(4, d?.initializer) + + // The next two variables are using a short assignment, therefore they do not have an + // initializer, but we can use the firstAssignment function + val e = main.variables["e"] + assertLiteralValue(5, e?.firstAssignment) + + val f = main.variables["f"] + assertLiteralValue(6, f?.firstAssignment) + + // And they should all be connected to the arguments of the Printf call + val printf = main.calls["Printf"] + assertNotNull(printf) + + printf.arguments.drop(1).forEach { + val ref = assertIs(it) + assertNotNull(ref.refersTo) + } + + // We have eight assignments in total (6 initializers + 2 assign expressions) + assertEquals(8, tu.assignments.size) + } + + @Test + fun testTypeConstraints() { + val topLevel = Path.of("src", "test", "resources", "golang") + val tu = + TestUtils.analyzeAndGetFirstTU( + listOf(topLevel.resolve("type_constraints.go").toFile()), + topLevel, + true + ) { + it.registerLanguage() + } + assertNotNull(tu) + + val myStruct = tu.records["MyStruct"] + assertNotNull(myStruct) + + val myInterface = tu.records["MyInterface"] + assertNotNull(myInterface) + } } diff --git a/cpg-language-go/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/ExpressionTest.kt b/cpg-language-go/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/ExpressionTest.kt index 033439f274..7921cec047 100644 --- a/cpg-language-go/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/ExpressionTest.kt +++ b/cpg-language-go/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/ExpressionTest.kt @@ -27,22 +27,19 @@ package de.fraunhofer.aisec.cpg.frontends.golang import de.fraunhofer.aisec.cpg.TestUtils import de.fraunhofer.aisec.cpg.assertFullName +import de.fraunhofer.aisec.cpg.assertLiteralValue +import de.fraunhofer.aisec.cpg.assertLocalName +import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.bodyOrNull -import de.fraunhofer.aisec.cpg.graph.byNameOrNull -import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration -import de.fraunhofer.aisec.cpg.graph.declarations.NamespaceDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration import de.fraunhofer.aisec.cpg.graph.statements.DeclarationStatement -import de.fraunhofer.aisec.cpg.graph.statements.expressions.CastExpression -import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.* import java.nio.file.Path -import kotlin.test.assertNotNull -import kotlin.test.assertSame -import org.junit.jupiter.api.Test +import kotlin.test.* class ExpressionTest { @Test - fun testTypeAssert() { + fun testCastExpression() { val topLevel = Path.of("src", "test", "resources", "golang") val tu = TestUtils.analyzeAndGetFirstTU( @@ -54,10 +51,10 @@ class ExpressionTest { } assertNotNull(tu) - val main = tu.byNameOrNull("main") + val main = tu.namespaces["main"] assertNotNull(main) - val mainFunc = main.byNameOrNull("main") + val mainFunc = main.functions["main"] assertNotNull(mainFunc) val f = @@ -74,5 +71,69 @@ class ExpressionTest { assertNotNull(cast) assertFullName("main.MyStruct", cast.castType) assertSame(f, (cast.expression as? DeclaredReferenceExpression)?.refersTo) + + val ignored = main.variables("_") + ignored.forEach { assertIs(it.initializer) } + } + + @Test + fun testSliceExpression() { + val topLevel = Path.of("src", "test", "resources", "golang") + val tu = + TestUtils.analyzeAndGetFirstTU( + listOf(topLevel.resolve("slices.go").toFile()), + topLevel, + true + ) { + it.registerLanguage() + } + assertNotNull(tu) + + val a = tu.variables["a"] + assertNotNull(a) + assertLocalName("int[]", a.type) + + val b = tu.variables["b"] + assertNotNull(b) + assertLocalName("int[]", b.type) + + // [:1] + var slice = + assertIs( + assertIs(b.initializer).subscriptExpression + ) + assertNull(slice.floor) + assertLiteralValue(1, slice.ceiling) + assertNull(slice.third) + + val c = tu.variables["c"] + assertNotNull(c) + assertLocalName("int[]", c.type) + + // [1:] + slice = assertIs(assertIs(c.initializer).subscriptExpression) + assertLiteralValue(1, slice.floor) + assertNull(slice.ceiling) + assertNull(slice.third) + + val d = tu.variables["d"] + assertNotNull(d) + assertLocalName("int[]", d.type) + + // [0:1] + slice = assertIs(assertIs(d.initializer).subscriptExpression) + assertLiteralValue(0, slice.floor) + assertLiteralValue(1, slice.ceiling) + assertNull(slice.third) + + val e = tu.variables["e"] + assertNotNull(e) + assertLocalName("int[]", e.type) + + // [0:1:1] + slice = assertIs(assertIs(e.initializer).subscriptExpression) + assertLiteralValue(0, slice.floor) + assertLiteralValue(1, slice.ceiling) + assertLiteralValue(1, slice.third) } } diff --git a/cpg-language-go/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoLanguageFrontendTest.kt b/cpg-language-go/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoLanguageFrontendTest.kt index ff69b57ded..d9dd581779 100644 --- a/cpg-language-go/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoLanguageFrontendTest.kt +++ b/cpg-language-go/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoLanguageFrontendTest.kt @@ -31,7 +31,10 @@ import de.fraunhofer.aisec.cpg.TestUtils.analyzeAndGetFirstTU import de.fraunhofer.aisec.cpg.assertFullName import de.fraunhofer.aisec.cpg.assertLocalName import de.fraunhofer.aisec.cpg.graph.* -import de.fraunhofer.aisec.cpg.graph.declarations.* +import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration +import de.fraunhofer.aisec.cpg.graph.declarations.NamespaceDeclaration +import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration +import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration import de.fraunhofer.aisec.cpg.graph.statements.* import de.fraunhofer.aisec.cpg.graph.statements.expressions.* import de.fraunhofer.aisec.cpg.graph.types.FunctionType @@ -50,19 +53,19 @@ class GoLanguageFrontendTest : BaseTest() { } assertNotNull(tu) - val p = tu.byNameOrNull("p") + val p = tu.namespaces["p"] assertNotNull(p) - val main = p.byNameOrNull("main") + val main = p.functions["main"] assertNotNull(main) - val message = - main.bodyOrNull(2)?.singleDeclaration as? VariableDeclaration + val message = main.variables["message"] assertNotNull(message) val map = - ((message.initializer as? ConstructExpression)?.arguments?.firstOrNull() - as? InitializerListExpression) + assertIs( + assertIs(message.firstAssignment).arguments.firstOrNull() + ) assertNotNull(map) val nameEntry = map.initializers.firstOrNull() as? KeyValueExpression @@ -80,21 +83,21 @@ class GoLanguageFrontendTest : BaseTest() { } assertNotNull(tu) - val p = tu.byNameOrNull("p") + val p = tu.namespaces["p"] assertNotNull(p) - val main = p.byNameOrNull("main") + val main = p.functions["main"] assertNotNull(main) - val data = main.bodyOrNull(0)?.singleDeclaration + val data = main.variables["data"] assertNotNull(data) // We should be able to follow the DFG backwards from the declaration to the individual // key/value expressions - val path = data.followPrevDFG { it is KeyValueExpression } + val path = data.firstAssignment?.followPrevDFG { it is KeyValueExpression } assertNotNull(path) - assertEquals(4, path.size) + assertEquals(3, path.size) } @Test @@ -110,26 +113,22 @@ class GoLanguageFrontendTest : BaseTest() { } assertNotNull(tu) - val p = tu.byNameOrNull("p") + val p = tu.namespaces["p"] assertNotNull(p) - val myStruct = p.byNameOrNull("p.MyStruct") + val myStruct = p.records["p.MyStruct"] assertNotNull(myStruct) - val main = p.byNameOrNull("main") + val main = p.functions["main"] assertNotNull(main) val body = main.body as? CompoundStatement assertNotNull(body) - var stmt = main.body(0) - assertNotNull(stmt) - - var decl = stmt.singleDeclaration as? VariableDeclaration + var decl = main.variables["o"] assertNotNull(decl) - val new = decl.initializer as? NewExpression - assertNotNull(new) + val new = assertIs(decl.firstAssignment) assertEquals(TypeParser.createFrom("p.MyStruct*", GoLanguage()), new.type) val construct = new.initializer as? ConstructExpression @@ -138,13 +137,10 @@ class GoLanguageFrontendTest : BaseTest() { // make array - stmt = main.body(1) - assertNotNull(stmt) - - decl = stmt.singleDeclaration as? VariableDeclaration + decl = main.variables["a"] assertNotNull(decl) - var make = decl.initializer + var make = assertIs(decl.firstAssignment) assertNotNull(make) assertEquals(TypeParser.createFrom("int[]", GoLanguage()), make.type) @@ -155,29 +151,23 @@ class GoLanguageFrontendTest : BaseTest() { assertEquals(5, dimension.value) // make map - stmt = main.body(2) - assertNotNull(stmt) - decl = stmt.singleDeclaration as? VariableDeclaration + decl = main.variables["m"] assertNotNull(decl) - make = decl.initializer + make = assertIs(decl.firstAssignment) assertNotNull(make) assertTrue(make is ConstructExpression) // TODO: Maps can have dedicated types and parsing them as a generic here is only a - // temporary solution. - // This should be fixed in the future. + // temporary solution. This should be fixed in the future. assertEquals(TypeParser.createFrom("map[string,string]", GoLanguage()), make.type) // make channel - stmt = main.body(3) - assertNotNull(stmt) - - decl = stmt.singleDeclaration as? VariableDeclaration + decl = main.variables["ch"] assertNotNull(decl) - make = decl.initializer + make = assertIs(decl.firstAssignment) assertNotNull(make) assertTrue(make is ConstructExpression) assertEquals(TypeParser.createFrom("chan[int]", GoLanguage()), make.type) @@ -193,32 +183,32 @@ class GoLanguageFrontendTest : BaseTest() { assertNotNull(tu) - val p = tu.byNameOrNull("p") + val p = tu.namespaces["p"] assertNotNull(p) - val a = p.byNameOrNull("a") + val a = p.variables["a"] assertNotNull(a) assertNotNull(a.location) assertLocalName("a", a) assertEquals(TypeParser.createFrom("int", GoLanguage()), a.type) - val s = p.byNameOrNull("s") + val s = p.variables["s"] assertNotNull(s) assertLocalName("s", s) assertEquals(TypeParser.createFrom("string", GoLanguage()), s.type) - val f = p.byNameOrNull("f") + val f = p.variables["f"] assertNotNull(f) assertLocalName("f", f) assertEquals(TypeParser.createFrom("float64", GoLanguage()), f.type) - val f32 = p.byNameOrNull("f32") + val f32 = p.variables["f32"] assertNotNull(f32) assertLocalName("f32", f32) assertEquals(TypeParser.createFrom("float32", GoLanguage()), f32.type) - val n = p.byNameOrNull("n") + val n = p.variables["n"] assertNotNull(n) assertEquals(TypeParser.createFrom("int*", GoLanguage()), n.type) @@ -226,6 +216,18 @@ class GoLanguageFrontendTest : BaseTest() { assertNotNull(nil) assertLocalName("nil", nil) assertEquals(null, nil.value) + + val fn = p.variables["fn"] + assertNotNull(fn) + + val lambda = assertIs(fn.initializer) + assertNotNull(lambda) + + val func = lambda.function + assertNotNull(func) + assertFullName("", func) + assertEquals(1, func.parameters.size) + assertEquals(1, func.returnTypes.size) } @Test @@ -265,7 +267,7 @@ class GoLanguageFrontendTest : BaseTest() { var body = main.body as? CompoundStatement assertNotNull(body) - var callExpression = body.statements.first() as? CallExpression + var callExpression = body.calls.firstOrNull() assertNotNull(callExpression) assertLocalName("myTest", callExpression) @@ -299,17 +301,15 @@ class GoLanguageFrontendTest : BaseTest() { assertLocalName("s", ref) assertEquals(s, ref.refersTo) - val stmt = body.statements[1] as? BinaryOperator + val stmt = body.statements[1] as? AssignExpression assertNotNull(stmt) - val a = stmt.lhs as? DeclaredReferenceExpression + val a = stmt.lhs.firstOrNull() as? DeclaredReferenceExpression assertNotNull(a) assertLocalName("a", a) - val op = stmt.rhs as? BinaryOperator - assertNotNull(op) - + val op = assertIs(stmt.rhs.firstOrNull()) assertEquals("+", op.operatorCode) val lhs = op.lhs as? Literal<*> @@ -322,14 +322,11 @@ class GoLanguageFrontendTest : BaseTest() { assertEquals(2, rhs.value) - val binOp = body.statements[2] as? BinaryOperator - - assertNotNull(binOp) - - val err = binOp.lhs + val binOp = assertIs(body.statements[2]) + val err = binOp.lhs.firstOrNull() assertNotNull(err) - assertEquals(TypeParser.createFrom("error", GoLanguage()), err.type) + assertLocalName("error", err.type) } @Test @@ -457,16 +454,16 @@ class GoLanguageFrontendTest : BaseTest() { val body = myFunc.body as? CompoundStatement assertNotNull(body) - val binOp = body.statements.first() as? BinaryOperator - assertNotNull(binOp) + val assign = body.statements.first() as? AssignExpression + assertNotNull(assign) - val lhs = binOp.lhs as? MemberExpression + val lhs = assign.lhs.firstOrNull() as? MemberExpression assertNotNull(lhs) assertEquals(myFunc.receiver, (lhs.base as? DeclaredReferenceExpression)?.refersTo) assertLocalName("Field", lhs) assertEquals(TypeParser.createFrom("int", GoLanguage()), lhs.type) - val rhs = binOp.rhs as? DeclaredReferenceExpression + val rhs = assign.rhs.firstOrNull() as? DeclaredReferenceExpression assertNotNull(rhs) assertFullName("otherPackage.OtherField", rhs) } @@ -587,15 +584,13 @@ class GoLanguageFrontendTest : BaseTest() { val body = main.body as? CompoundStatement assertNotNull(body) - val c = - (body.statements[0] as? DeclarationStatement)?.singleDeclaration as? VariableDeclaration + val c = body.variables["c"] assertNotNull(c) // type will be inferred from the function declaration assertEquals(TypeParser.createFrom("p.MyStruct*", GoLanguage()), c.type) - val newMyStruct = c.initializer as? CallExpression - assertNotNull(newMyStruct) + val newMyStruct = assertIs(c.firstAssignment) // fetch the function declaration from the other TU val tu2 = tus[1] @@ -612,6 +607,9 @@ class GoLanguageFrontendTest : BaseTest() { val base = call.base as? DeclaredReferenceExpression assertNotNull(base) assertEquals(c, base.refersTo) + + val go = main.calls["go"] + assertNotNull(go) } @Test @@ -628,15 +626,30 @@ class GoLanguageFrontendTest : BaseTest() { it.registerLanguage() } - val main = tu.functions["p.main"] + val main = tu.functions["main.main"] assertNotNull(main) val f = main.bodyOrNull() assertNotNull(f) assertTrue(f.condition is BinaryOperator) assertTrue(f.statement is CompoundStatement) - assertTrue(f.initializerStatement is DeclarationStatement) + assertTrue(f.initializerStatement is AssignExpression) assertTrue(f.iterationStatement is UnaryOperator) + + val each = main.bodyOrNull() + assertNotNull(each) + + val bytes = assertIs(each.iterable) + assertLocalName("bytes", bytes) + assertNotNull(bytes.refersTo) + + val idx = assertIs(each.variable).variables["idx"] + assertNotNull(idx) + assertLocalName("int", idx.type) + + val b = assertIs(each.variable).variables["b"] + assertNotNull(b) + assertLocalName("uint8", b.type) } @Test @@ -670,10 +683,10 @@ class GoLanguageFrontendTest : BaseTest() { val main = tu1.functions["main.main"] assertNotNull(main) - val a = main.getBodyStatementAs(0, DeclarationStatement::class.java) + val a = main.variables["a"] assertNotNull(a) - val call = (a.singleDeclaration as? VariableDeclaration)?.initializer as? CallExpression + val call = a.firstAssignment as? CallExpression assertNotNull(call) assertTrue(call.invokes.contains(newAwesome)) @@ -697,10 +710,10 @@ class GoLanguageFrontendTest : BaseTest() { assertNotNull(tu) - val mainNamespace = tu.byNameOrNull("main") + val mainNamespace = tu.namespaces["main"] assertNotNull(mainNamespace) - val main = mainNamespace.byNameOrNull("main") + val main = mainNamespace.functions["main"] assertNotNull(main) assertEquals("comment before function", main.comment) @@ -712,11 +725,11 @@ class GoLanguageFrontendTest : BaseTest() { assertNotNull(j) assertEquals("comment before parameter2", j.comment) - var declStmt = main.bodyOrNull() - assertNotNull(declStmt) - assertEquals("comment before assignment", declStmt.comment) + val assign = main.bodyOrNull() + assertNotNull(assign) + assertEquals("comment before assignment", assign.comment) - declStmt = main.bodyOrNull(1) + val declStmt = main.bodyOrNull() assertNotNull(declStmt) assertEquals("comment before declaration", declStmt.comment) @@ -743,9 +756,31 @@ class GoLanguageFrontendTest : BaseTest() { val main = mainPackage.byNameOrNull("main") assertNotNull(main) - val binOp = main.bodyOrNull() - assertNotNull(binOp) + val assign = main.bodyOrNull() + assertNotNull(assign) + assertEquals(1, assign.rhs.size) assertNotNull(tu) } + + @Test + fun testAssign() { + val topLevel = Path.of("src", "test", "resources", "golang") + val tu = + analyzeAndGetFirstTU(listOf(topLevel.resolve("function.go").toFile()), topLevel, true) { + it.registerLanguage() + } + assertNotNull(tu) + + val i = tu.variables["i"] + + val assign = + tu.functions["main"].assignments.firstOrNull { + (it.target as? DeclaredReferenceExpression)?.refersTo == i + } + assertNotNull(assign) + + val call = assertIs(assign.value) + assertLocalName("myTest", call) + } } diff --git a/cpg-language-go/src/test/resources/golang/call.go b/cpg-language-go/src/test/resources/golang/call.go index b4fc328494..9ecadbeb09 100644 --- a/cpg-language-go/src/test/resources/golang/call.go +++ b/cpg-language-go/src/test/resources/golang/call.go @@ -5,4 +5,7 @@ import ("http") func main() { c := NewMyStruct() c.myOtherFunc() + + go c.MyFunc() + go c.MyFunc() } diff --git a/cpg-language-go/src/test/resources/golang/declare.go b/cpg-language-go/src/test/resources/golang/declare.go new file mode 100644 index 0000000000..15da35ffa9 --- /dev/null +++ b/cpg-language-go/src/test/resources/golang/declare.go @@ -0,0 +1,46 @@ +package main + +import "fmt" + +func main() { + // Declaring multiple variables in a block with initializer. This is one + // GenDecl with two ValueSpec specs and one "Name" and (initializer) "Value" + // each. + // + // We translate this into one DeclarationStatement with two + // VariableDeclaration nodes + var ( + a int = 1 + b int = 2 + ) + + // Declaring multiple variables in a single line. This is one GenDecl with + // one ValueSpec spec which contains two "Names" and two "Values" which + // correspond to the respective initializer values. Note, that the number of + // values MUST match the number of names + // + // We translate this into one DeclarationStatement with two + // VariableDeclaration nodes + var c, d = 3, 4 + + // Short assignment using an assignment, where all variables were not + // defined before. This is an AssignStmt which has DEFINE as its token. + // + // We need to split this up into several nodes. First, we translate this + // into one (implicit) DeclarationStatement with two VariableDeclaration + // nodes. Afterwards we are parsing it as a regular assignment. + e, f := 5, 6 + + // Short assignment using an assignment, where one variable (f) was defined + // before in the local scope. This is an AssignStmt which has DEFINE as its + // token. From the AST we cannot differentiate this from the previous + // example and we need to do a (local) variable lookup here. + // + // Finally, We need to split this up into several nodes. First, we translate + // this into one (implicit) DeclarationStatement with one + // VariableDeclaration node. Afterwards we are parsing it as a regular + // assignment. + f, g := 7, 8 + + fmt.Printf("%d %d %d %d %d %d %d\n", a, b, c, d, e, f, g) +} diff --git a/cpg-language-go/src/test/resources/golang/dfg.go b/cpg-language-go/src/test/resources/golang/dfg.go index 67d8a787fd..2d282d6472 100644 --- a/cpg-language-go/src/test/resources/golang/dfg.go +++ b/cpg-language-go/src/test/resources/golang/dfg.go @@ -1,5 +1,7 @@ package p +import "db" + func main() int { data := &Data{Name: name} db.Create(data) diff --git a/cpg-language-go/src/test/resources/golang/for.go b/cpg-language-go/src/test/resources/golang/for.go index 67ec298a6c..e3f0829b40 100644 --- a/cpg-language-go/src/test/resources/golang/for.go +++ b/cpg-language-go/src/test/resources/golang/for.go @@ -1,7 +1,18 @@ -package p +package main + +import "fmt" func main() { - for i := 0; i < 5; i++ { - do() + var bytes = []byte{1,2,3,4} + + // Regular old-school for loop + for i := 0; i < 4; i++ { + fmt.Printf("bytes[%d]=%d\n", i, bytes[i]) + } + + // For-each style loop with range expression with key and value. idx and b are created using + // the short assignment syntax. Its scope is limited to the for-block. + for idx, b := range bytes { + fmt.Printf("bytes[%d]=%d; idx=%T b=%T\n", idx, b, idx, b) } } \ No newline at end of file diff --git a/cpg-language-go/src/test/resources/golang/function.go b/cpg-language-go/src/test/resources/golang/function.go index 0720767f82..e8984d0a40 100644 --- a/cpg-language-go/src/test/resources/golang/function.go +++ b/cpg-language-go/src/test/resources/golang/function.go @@ -3,15 +3,22 @@ package p import "fmt" func main() { - myTest("some string") + var i int + var err error + + i, err = myTest("some string") + + if err == nil { + fmt.Printf("%d", i) + } } func myTest(s string) (a int, err error) { - fmt.Printf("%s", s) + fmt.Printf("%s", s) - a = 1 + 2 + a = 1 + 2 - err = nil + err = nil - return + return } diff --git a/cpg-language-go/src/test/resources/golang/go.mod b/cpg-language-go/src/test/resources/golang/go.mod index 745649b879..12cb5307fb 100644 --- a/cpg-language-go/src/test/resources/golang/go.mod +++ b/cpg-language-go/src/test/resources/golang/go.mod @@ -1 +1 @@ -module p +module mymodule diff --git a/cpg-language-go/src/test/resources/golang/literal.go b/cpg-language-go/src/test/resources/golang/literal.go index 63cdc1c7e5..0450e78c4b 100644 --- a/cpg-language-go/src/test/resources/golang/literal.go +++ b/cpg-language-go/src/test/resources/golang/literal.go @@ -5,3 +5,7 @@ const s = "test" const f = 1.0 const f32 float32 = 1.00 var n *int = nil + +var fn = func(_ int) int { + return 1 +} \ No newline at end of file diff --git a/cpg-language-go/src/test/resources/golang/slices.go b/cpg-language-go/src/test/resources/golang/slices.go new file mode 100644 index 0000000000..4c6c44a496 --- /dev/null +++ b/cpg-language-go/src/test/resources/golang/slices.go @@ -0,0 +1,21 @@ +package main + +import "fmt" + +func main() { + a := []int{1,2,3} + + // [1] + b := a[:1] + + // [2, 3] + c := a[1:] + + // [1] + d := a[0:1] + + // [1] + e := a[0:1:1] + + fmt.Printf("%v %v %v %v %v", a, b, c, d, e) +} \ No newline at end of file diff --git a/cpg-language-go/src/test/resources/golang/type_assert.go b/cpg-language-go/src/test/resources/golang/type_assert.go index cbee1cb06b..4f42ac2798 100644 --- a/cpg-language-go/src/test/resources/golang/type_assert.go +++ b/cpg-language-go/src/test/resources/golang/type_assert.go @@ -13,4 +13,8 @@ func main () { var s = f.(MyStruct) fmt.Printf("%+v", s) + + var _ = MyInterface(s) + var _ = interface{}(s) + var _ = any(s) } \ No newline at end of file diff --git a/cpg-language-go/src/test/resources/golang/type_constraints.go b/cpg-language-go/src/test/resources/golang/type_constraints.go new file mode 100644 index 0000000000..485f29bc2e --- /dev/null +++ b/cpg-language-go/src/test/resources/golang/type_constraints.go @@ -0,0 +1,11 @@ +package main + +type MyStruct[T any] struct {} +type MyInterface interface {} + +func SomeFunc[T any, S MyInterface]() {} + +func main() { + _ := &MyStruct[MyInterface]{} + SomeFunc[any, MyInterface]() +} \ No newline at end of file From 1d6b14d3e95c8c5522d1f1461bd80bc04a9338bf Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 12 Apr 2023 07:27:12 +0200 Subject: [PATCH 025/143] Update module golang.org/x/mod to v0.10.0 (#1151) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- cpg-language-go/src/main/golang/go.mod | 2 +- cpg-language-go/src/main/golang/go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/cpg-language-go/src/main/golang/go.mod b/cpg-language-go/src/main/golang/go.mod index 4c4982a9dc..76bb4cd174 100644 --- a/cpg-language-go/src/main/golang/go.mod +++ b/cpg-language-go/src/main/golang/go.mod @@ -3,6 +3,6 @@ module cpg go 1.19 require ( - golang.org/x/mod v0.9.0 + golang.org/x/mod v0.10.0 tekao.net/jnigi v0.0.0-20230402215112-69b87aaf8714 ) diff --git a/cpg-language-go/src/main/golang/go.sum b/cpg-language-go/src/main/golang/go.sum index 57c62ef31a..793b174f76 100644 --- a/cpg-language-go/src/main/golang/go.sum +++ b/cpg-language-go/src/main/golang/go.sum @@ -4,6 +4,8 @@ golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs= golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= +golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= tekao.net/jnigi v0.0.0-20220921102452-ce6d0be0c331 h1:p5apvrQZPCacG+Ux6GMzLWX4mUZOPlguj0MrONXutrQ= tekao.net/jnigi v0.0.0-20220921102452-ce6d0be0c331/go.mod h1:SmVvXetJ8N0ov5c2eOC+IxmkdYGEyuXghTuBq5HWZ/Y= tekao.net/jnigi v0.0.0-20221227053512-56e0101fa996 h1:Vl0GEBxRKyS1+/fjd9H6ptV7t/CAmfgxtsanvqsCob8= From 5a9c9c097404636c8118a4410960b0064648c72d Mon Sep 17 00:00:00 2001 From: KuechA <31155350+KuechA@users.noreply.github.com> Date: Wed, 12 Apr 2023 10:32:05 +0200 Subject: [PATCH 026/143] Move compound assignment operators to languages (#1146) * Configure languages with compound assignment operators and remove them from passes and nodes * Add references --- .../aisec/cpg/frontends/Language.kt | 3 +++ .../aisec/cpg/frontends/cpp/CLanguage.kt | 7 ++++++ .../expressions/AssignExpression.kt | 4 ++-- .../statements/expressions/BinaryOperator.kt | 14 ++++------- .../cpg/passes/ControlFlowSensitiveDFGPass.kt | 3 ++- .../de/fraunhofer/aisec/cpg/passes/DFGPass.kt | 11 +-------- .../aisec/cpg/frontends/TestLanguage.kt | 3 +++ .../aisec/cpg/frontends/golang/GoLanguage.kt | 7 ++++++ .../aisec/cpg/frontends/java/JavaLanguage.kt | 7 ++++++ .../cpg/frontends/llvm/LLVMIRLanguage.kt | 1 + .../cpg/frontends/python/PythonLanguage.kt | 7 ++++++ .../typescript/JavaScriptLanguage.kt | 23 +++++++++++++++++++ 12 files changed, 68 insertions(+), 22 deletions(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/Language.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/Language.kt index 94cc0abd87..b663d1e307 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/Language.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/Language.kt @@ -75,6 +75,9 @@ abstract class Language : Node() { open val arithmeticOperations: Set get() = setOf("+", "-", "*", "/", "%", "<<", ">>") + /** All operators which perform and assignment and an operation using lhs and rhs. */ + abstract val compoundAssignmentOperators: Set + /** Creates a new [LanguageFrontend] object to parse the language. */ abstract fun newFrontend( config: TranslationConfiguration, diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CLanguage.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CLanguage.kt index 511dc5fa2c..83007ad5a7 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CLanguage.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CLanguage.kt @@ -60,6 +60,13 @@ open class CLanguage : override val conjunctiveOperators = listOf("&&") override val disjunctiveOperators = listOf("||") + /** + * All operators which perform and assignment and an operation using lhs and rhs. See + * https://en.cppreference.com/w/c/language/operator_assignment + */ + override val compoundAssignmentOperators = + setOf("+=", "-=", "*=", "/=", "%=", "<<=", ">>=", "&=", "|=", "^=") + @Transient @JsonIgnore override val builtInTypes: Map = diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/AssignExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/AssignExpression.kt index 3938e91ece..b64202cbe8 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/AssignExpression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/AssignExpression.kt @@ -96,8 +96,8 @@ class AssignExpression : Expression(), AssignmentHolder, HasType.TypeListener { */ val isCompoundAssignment: Boolean get() { - return arrayOf("*=", "/=", "%=", "+=", "-=", "<<=", ">>=", "&=", "^=", "|=") - .contains(operatorCode) && isSingleValue + return operatorCode in (language?.compoundAssignmentOperators ?: setOf()) && + isSingleValue } /** diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/BinaryOperator.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/BinaryOperator.kt index 8e9f9a078c..d2df7c63d6 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/BinaryOperator.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/BinaryOperator.kt @@ -32,7 +32,6 @@ import de.fraunhofer.aisec.cpg.graph.types.Type import de.fraunhofer.aisec.cpg.graph.types.UnknownType import java.util.* import org.apache.commons.lang3.builder.ToStringBuilder -import org.neo4j.ogm.annotation.Transient /** * A binary operation expression, such as "a + b". It consists of a left hand expression (lhs), a @@ -61,7 +60,10 @@ class BinaryOperator : override var operatorCode: String? = null set(value) { field = value - if (compoundOperators.contains(operatorCode) || operatorCode == "=") { + if ( + (operatorCode in (language?.compoundAssignmentOperators ?: setOf())) || + (operatorCode == "=") + ) { NodeBuilder.LOGGER.warn( "Creating a BinaryOperator with an assignment operator code is deprecated. The class AssignExpression should be used instead." ) @@ -84,7 +86,7 @@ class BinaryOperator : registerTypeListener(lhs as HasType.TypeListener) registerTypeListener(this.lhs as HasType.TypeListener) } - } else if (compoundOperators.contains(operatorCode)) { + } else if (operatorCode in (language?.compoundAssignmentOperators ?: setOf())) { if (lhs is DeclaredReferenceExpression) { // declared reference expr is the left-hand side of an assignment -> writing to the // var @@ -243,10 +245,4 @@ class BinaryOperator : null } } - - companion object { - /** Required for compound BinaryOperators. This should not be stored in the graph */ - @Transient - val compoundOperators = listOf("*=", "/=", "%=", "+=", "-=", "<<=", ">>=", "&=", "^=", "|=") - } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt index 72fd969122..20460c3453 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt @@ -378,7 +378,8 @@ open class ControlFlowSensitiveDFGPass : Pass() { */ private fun isCompoundAssignment(currentNode: Node) = currentNode is BinaryOperator && - currentNode.operatorCode in BinaryOperator.compoundOperators && + currentNode.operatorCode in + (currentNode.language?.compoundAssignmentOperators ?: setOf()) && (currentNode.lhs as? DeclaredReferenceExpression)?.refersTo != null /** Checks if the node is a simple assignment of the form `var = ...` */ diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/DFGPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/DFGPass.kt index 6b467d38e6..7a220594b7 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/DFGPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/DFGPass.kt @@ -362,16 +362,7 @@ class DFGPass : Pass() { node.rhs.addNextDFG(node) } } - "*=", - "/=", - "%=", - "+=", - "-=", - "<<=", - ">>=", - "&=", - "^=", - "|=" -> { + in node.language?.compoundAssignmentOperators ?: setOf() -> { node.lhs.let { node.addPrevDFG(it) node.addNextDFG(it) diff --git a/cpg-core/src/testFixtures/kotlin/de/fraunhofer/aisec/cpg/frontends/TestLanguage.kt b/cpg-core/src/testFixtures/kotlin/de/fraunhofer/aisec/cpg/frontends/TestLanguage.kt index 8c5d7b8348..d97d0bd89d 100644 --- a/cpg-core/src/testFixtures/kotlin/de/fraunhofer/aisec/cpg/frontends/TestLanguage.kt +++ b/cpg-core/src/testFixtures/kotlin/de/fraunhofer/aisec/cpg/frontends/TestLanguage.kt @@ -47,6 +47,9 @@ class TestLanguage(namespaceDelimiter: String = "::") : Language = listOf() override val namespaceDelimiter: String override val frontend: KClass = TestLanguageFrontend::class + override val compoundAssignmentOperators = + setOf("+=", "-=", "*=", "/=", "%=", "<<=", ">>=", "&=", "|=", "^=") + override val builtInTypes: Map = mapOf( "boolean" to IntegerType("boolean", 1, this, NumericType.Modifier.SIGNED), diff --git a/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoLanguage.kt b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoLanguage.kt index 62a1e748ca..15af1a3147 100644 --- a/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoLanguage.kt +++ b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoLanguage.kt @@ -45,6 +45,13 @@ class GoLanguage : override val startCharacter = '[' override val endCharacter = ']' + /** + * All operators which perform and assignment and an operation using lhs and rhs. See + * https://go.dev/ref/spec#Operators_and_punctuation + */ + override val compoundAssignmentOperators = + setOf("+=", "-=", "*=", "/=", "%=", "<<=", ">>=", "&^=", "&=", "|=", "^=") + /** See [Documentation](https://pkg.go.dev/builtin). */ @Transient override val builtInTypes = diff --git a/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/JavaLanguage.kt b/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/JavaLanguage.kt index d1a8a6a528..cabd99d5bc 100644 --- a/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/JavaLanguage.kt +++ b/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/JavaLanguage.kt @@ -58,6 +58,13 @@ open class JavaLanguage : override val conjunctiveOperators = listOf("&&") override val disjunctiveOperators = listOf("||") + /** + * All operators which perform and assignment and an operation using lhs and rhs. See + * https://docs.oracle.com/javase/tutorial/java/nutsandbolts/operators.html + */ + override val compoundAssignmentOperators = + setOf("+=", "-=", "*=", "/=", "%=", "<<=", ">>=", ">>>=", "&=", "|=", "^=") + /** * See * [Java Language Specification](https://docs.oracle.com/javase/specs/jls/se19/html/jls-4.html). diff --git a/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/LLVMIRLanguage.kt b/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/LLVMIRLanguage.kt index 417fbb7498..0106a8f49d 100644 --- a/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/LLVMIRLanguage.kt +++ b/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/LLVMIRLanguage.kt @@ -40,6 +40,7 @@ class LLVMIRLanguage : Language() { override val namespaceDelimiter = "::" @Transient override val frontend: KClass = LLVMIRLanguageFrontend::class + override val compoundAssignmentOperators = setOf() // TODO: In theory, the integers can have any bitwidth from 1 to 1^32 bits. It's not known if // they are interpreted as signed or unsigned. diff --git a/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonLanguage.kt b/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonLanguage.kt index a934d59ebd..78c119b68b 100644 --- a/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonLanguage.kt +++ b/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonLanguage.kt @@ -43,6 +43,13 @@ class PythonLanguage : Language(), HasShortCircuitOperat override val conjunctiveOperators = listOf("and") override val disjunctiveOperators = listOf("or") + /** + * All operators which perform and assignment and an operation using lhs and rhs. See + * https://docs.python.org/3/library/operator.html#in-place-operators + */ + override val compoundAssignmentOperators = + setOf("+=", "-=", "*=", "**=", "/=", "//=", "%=", "<<=", ">>=", "&=", "|=", "^=", "@=") + /** See [Documentation](https://docs.python.org/3/library/stdtypes.html#). */ @Transient override val builtInTypes = diff --git a/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/JavaScriptLanguage.kt b/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/JavaScriptLanguage.kt index 9095985124..e3a727a716 100644 --- a/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/JavaScriptLanguage.kt +++ b/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/JavaScriptLanguage.kt @@ -43,6 +43,29 @@ open class JavaScriptLanguage : Language(), HasShort override val conjunctiveOperators = listOf("&&", "&&=", "??", "??=") override val disjunctiveOperators = listOf("||", "||=") + /** + * All operators which perform and assignment and an operation using lhs and rhs. See + * https://tc39.es/ecma262/#sec-assignment-operators + */ + override val compoundAssignmentOperators = + setOf( + "+=", + "-=", + "*=", + "**=", + "/=", + "%=", + "<<=", + ">>=", + ">>>=", + "&=", + "&&=", + "|=", + "||=", + "^=", + "??=" + ) + /** * See * [Documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#primitive_values). From 751723ed7b0f8c6c9f9eee7771e9dade64a22e42 Mon Sep 17 00:00:00 2001 From: KuechA <31155350+KuechA@users.noreply.github.com> Date: Wed, 12 Apr 2023 12:00:19 +0200 Subject: [PATCH 027/143] Reference eog spec in readme (#1152) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 5653e237a1..75fe8b7820 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ This library uses [Eclipse CDT](https://www.eclipse.org/cdt/) for parsing C/C++ In order to improve some formal aspects of our library, we created several specifications of our core concepts. Currently, the following specifications exist: * [Dataflow Graph](./cpg-core/specifications/dfg.md) +* [Evaluation Order Graph](./cpg-core/specifications/eog.md) * [Language and Language Frontend](./cpg-core/specifications/language.md) We aim to provide more specifications over time and also include them in a new generated documentation site. From 3228c566e7f759533f466d149c7f013deaabd460 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 13 Apr 2023 09:47:06 +0200 Subject: [PATCH 028/143] Update dependency webpack to v5.79.0 (#1153) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .../src/main/nodejs/package.json | 2 +- .../src/main/nodejs/yarn.lock | 100 +++++++++--------- 2 files changed, 51 insertions(+), 51 deletions(-) diff --git a/cpg-language-typescript/src/main/nodejs/package.json b/cpg-language-typescript/src/main/nodejs/package.json index ef5834e83d..0625eef777 100644 --- a/cpg-language-typescript/src/main/nodejs/package.json +++ b/cpg-language-typescript/src/main/nodejs/package.json @@ -11,7 +11,7 @@ }, "license": "Apache-2.0", "devDependencies": { - "webpack": "5.78.0", + "webpack": "5.79.0", "webpack-cli": "5.0.0" } } \ No newline at end of file diff --git a/cpg-language-typescript/src/main/nodejs/yarn.lock b/cpg-language-typescript/src/main/nodejs/yarn.lock index cd9f77a54c..d846372947 100644 --- a/cpg-language-typescript/src/main/nodejs/yarn.lock +++ b/cpg-language-typescript/src/main/nodejs/yarn.lock @@ -16,7 +16,7 @@ "@jridgewell/sourcemap-codec" "^1.4.10" "@jridgewell/trace-mapping" "^0.3.9" -"@jridgewell/resolve-uri@^3.0.3": +"@jridgewell/resolve-uri@3.1.0", "@jridgewell/resolve-uri@^3.0.3": version "3.1.0" resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== @@ -34,11 +34,19 @@ "@jridgewell/gen-mapping" "^0.3.0" "@jridgewell/trace-mapping" "^0.3.9" -"@jridgewell/sourcemap-codec@^1.4.10": +"@jridgewell/sourcemap-codec@1.4.14", "@jridgewell/sourcemap-codec@^1.4.10": version "1.4.14" resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== +"@jridgewell/trace-mapping@^0.3.17": + version "0.3.18" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz#25783b2086daf6ff1dcb53c9249ae480e4dd4cd6" + integrity sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA== + dependencies: + "@jridgewell/resolve-uri" "3.1.0" + "@jridgewell/sourcemap-codec" "1.4.14" + "@jridgewell/trace-mapping@^0.3.9": version "0.3.14" resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz#b231a081d8f66796e475ad588a1ef473112701ed" @@ -63,11 +71,16 @@ "@types/estree" "*" "@types/json-schema" "*" -"@types/estree@*", "@types/estree@^0.0.51": +"@types/estree@*": version "0.0.51" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.51.tgz#cfd70924a25a3fd32b218e5e420e6897e1ac4f40" integrity sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ== +"@types/estree@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.0.tgz#5fb2e536c1ae9bf35366eed879e827fa59ca41c2" + integrity sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ== + "@types/json-schema@*", "@types/json-schema@^7.0.8": version "7.0.8" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.8.tgz#edf1bf1dbf4e04413ca8e5b17b3b7d7d54b59818" @@ -336,10 +349,10 @@ envinfo@^7.7.3: resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.8.1.tgz#06377e3e5f4d379fea7ac592d5ad8927e0c4d475" integrity sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw== -es-module-lexer@^0.9.0: - version "0.9.3" - resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-0.9.3.tgz#6f13db00cc38417137daf74366f535c8eb438f19" - integrity sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ== +es-module-lexer@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.2.1.tgz#ba303831f63e6a394983fde2f97ad77b22324527" + integrity sha512-9978wrXM50Y4rTMmW5kXIC09ZdXQZqkE4mxhwkd8VbzsGkXGPgV4zWuqQJgCEzYngdo2dYDa0l8xhX4fkSwJSg== escalade@^3.1.1: version "3.1.1" @@ -463,10 +476,10 @@ isobject@^3.0.1: resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= -jest-worker@^27.0.2: - version "27.0.6" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.0.6.tgz#a5fdb1e14ad34eb228cfe162d9f729cdbfa28aed" - integrity sha512-qupxcj/dRuA3xHPMUd40gr2EaAurFbkwzOh7wfPaeE9id7hyjURRQoqNfHifHK3XjJU6YJJUQKILGUnwGPEOCA== +jest-worker@^27.4.5: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.5.1.tgz#8d146f0900e8973b106b6f73cc1e9a8cb86f8db0" + integrity sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg== dependencies: "@types/node" "*" merge-stream "^2.0.0" @@ -533,13 +546,6 @@ p-limit@^2.2.0: dependencies: p-try "^2.0.0" -p-limit@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" - integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== - dependencies: - yocto-queue "^0.1.0" - p-locate@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" @@ -619,7 +625,7 @@ safe-buffer@^5.1.0: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== -schema-utils@^3.0.0, schema-utils@^3.1.0: +schema-utils@^3.1.0, schema-utils@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.1.1.tgz#bc74c4b6b6995c1d88f76a8b77bea7219e0c8281" integrity sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw== @@ -628,10 +634,10 @@ schema-utils@^3.0.0, schema-utils@^3.1.0: ajv "^6.12.5" ajv-keywords "^3.5.2" -serialize-javascript@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8" - integrity sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag== +serialize-javascript@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.1.tgz#b206efb27c3da0b0ab6b52f48d170b7996458e5c" + integrity sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w== dependencies: randombytes "^2.1.0" @@ -662,7 +668,7 @@ source-map-support@~0.5.20: buffer-from "^1.0.0" source-map "^0.6.0" -source-map@^0.6.0, source-map@^0.6.1: +source-map@^0.6.0: version "0.6.1" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== @@ -684,22 +690,21 @@ tapable@^2.1.1, tapable@^2.2.0: resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.0.tgz#5c373d281d9c672848213d0e037d1c4165ab426b" integrity sha512-FBk4IesMV1rBxX2tfiK8RAmogtWn53puLOQlvO8XuwlgxcYbP4mVPS9Ph4aeamSyyVjOl24aYWAuc8U5kCVwMw== -terser-webpack-plugin@^5.1.3: - version "5.1.4" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.1.4.tgz#c369cf8a47aa9922bd0d8a94fe3d3da11a7678a1" - integrity sha512-C2WkFwstHDhVEmsmlCxrXUtVklS+Ir1A7twrYzrDrQQOIMOaVAYykaoo/Aq1K0QRkMoY2hhvDQY1cm4jnIMFwA== +terser-webpack-plugin@^5.3.7: + version "5.3.7" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.7.tgz#ef760632d24991760f339fe9290deb936ad1ffc7" + integrity sha512-AfKwIktyP7Cu50xNjXF/6Qb5lBNzYaWpU6YfoX3uZicTx0zTy0stDDCsvjDapKsSDvOeWo5MEq4TmdBy2cNoHw== dependencies: - jest-worker "^27.0.2" - p-limit "^3.1.0" - schema-utils "^3.0.0" - serialize-javascript "^6.0.0" - source-map "^0.6.1" - terser "^5.7.0" + "@jridgewell/trace-mapping" "^0.3.17" + jest-worker "^27.4.5" + schema-utils "^3.1.1" + serialize-javascript "^6.0.1" + terser "^5.16.5" -terser@^5.7.0: - version "5.14.2" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.14.2.tgz#9ac9f22b06994d736174f4091aa368db896f1c10" - integrity sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA== +terser@^5.16.5: + version "5.16.9" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.16.9.tgz#7a28cb178e330c484369886f2afd623d9847495f" + integrity sha512-HPa/FdTB9XGI2H1/keLFZHxl6WNvAI4YalHGtDQTlMnJcoqSab1UwL4l1hGEhs6/GmLHBZIg/YgB++jcbzoOEg== dependencies: "@jridgewell/source-map" "^0.3.2" acorn "^8.5.0" @@ -758,13 +763,13 @@ webpack-sources@^3.2.3: resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== -webpack@5.78.0: - version "5.78.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.78.0.tgz#836452a12416af2a7beae906b31644cb2562f9e6" - integrity sha512-gT5DP72KInmE/3azEaQrISjTvLYlSM0j1Ezhht/KLVkrqtv10JoP/RXhwmX/frrutOPuSq3o5Vq0ehR/4Vmd1g== +webpack@5.79.0: + version "5.79.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.79.0.tgz#8552b5da5a26e4e25842c08a883e08fc7740547a" + integrity sha512-3mN4rR2Xq+INd6NnYuL9RC9GAmc1ROPKJoHhrZ4pAjdMFEkJJWrsPw8o2JjCIyQyTu7rTXYn4VG6OpyB3CobZg== dependencies: "@types/eslint-scope" "^3.7.3" - "@types/estree" "^0.0.51" + "@types/estree" "^1.0.0" "@webassemblyjs/ast" "1.11.1" "@webassemblyjs/wasm-edit" "1.11.1" "@webassemblyjs/wasm-parser" "1.11.1" @@ -773,7 +778,7 @@ webpack@5.78.0: browserslist "^4.14.5" chrome-trace-event "^1.0.2" enhanced-resolve "^5.10.0" - es-module-lexer "^0.9.0" + es-module-lexer "^1.2.1" eslint-scope "5.1.1" events "^3.2.0" glob-to-regexp "^0.4.1" @@ -784,7 +789,7 @@ webpack@5.78.0: neo-async "^2.6.2" schema-utils "^3.1.0" tapable "^2.1.1" - terser-webpack-plugin "^5.1.3" + terser-webpack-plugin "^5.3.7" watchpack "^2.4.0" webpack-sources "^3.2.3" @@ -799,8 +804,3 @@ wildcard@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.0.tgz#a77d20e5200c6faaac979e4b3aadc7b3dd7f8fec" integrity sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw== - -yocto-queue@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" - integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== From 46377c1830a8e216b041638a367c3f8d36dbf647 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 13 Apr 2023 08:02:00 +0000 Subject: [PATCH 029/143] Update dependency org.mockito:mockito-core to v5.3.0 (#1150) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 85e5c95096..ff4dcd8bb9 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -36,7 +36,7 @@ llvm = { module = "org.bytedeco:llvm-platform", version = "15.0.3-1.5.8"} # test junit-params = { module = "org.junit.jupiter:junit-jupiter-params", version = "5.9.1"} -mockito = { module = "org.mockito:mockito-core", version = "5.2.0"} +mockito = { module = "org.mockito:mockito-core", version = "5.3.0"} # plugins needed for build.gradle.kts in buildSrc kotlin-gradle = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } From 3f4181745dcc2d1b6e75c8a66a6e6aa2782bd7b6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 15 Apr 2023 15:17:23 +0200 Subject: [PATCH 030/143] Update spotless to v6.18.0 (#1156) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ff4dcd8bb9..9e8caad35c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,7 +3,7 @@ kotlin = "1.8.0" neo4j = "4.0.2" log4j = "2.20.0" sonarqube = "4.0.0.2929" -spotless = "6.17.0" +spotless = "6.18.0" [libraries] kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin"} From 39e55971d95ece3e3d04d2d0dc0149bb3e21abbf Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 15 Apr 2023 22:58:31 +0200 Subject: [PATCH 031/143] Update dependency com.ibm.icu:icu4j to v73 (#1157) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9e8caad35c..7cd263d3e5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -25,7 +25,7 @@ javaparser = { module = "com.github.javaparser:javaparser-symbol-solver-core", v jackson = { module = "com.fasterxml.jackson.module:jackson-module-kotlin", version = "2.14.0"} eclipse-runtime = { module = "org.eclipse.platform:org.eclipse.core.runtime", version = "3.26.0"} osgi-service = { module = "org.osgi:org.osgi.service.prefs", version = "1.1.2"} -icu4j = { module = "com.ibm.icu:icu4j", version = "72.1"} +icu4j = { module = "com.ibm.icu:icu4j", version = "73.1"} eclipse-cdt-core = { module = "org.eclipse.cdt:core", version = "8.0.0.202211292120"} commons-io = { module = "commons-io:commons-io", version = "2.11.0"} jetbrains-annotations = { module = "org.jetbrains:annotations", version = "24.0.0"} From 3ed5508466b10302e1684dfa4cac4fb916147935 Mon Sep 17 00:00:00 2001 From: KuechA <31155350+KuechA@users.noreply.github.com> Date: Fri, 21 Apr 2023 12:09:03 +0200 Subject: [PATCH 032/143] Use `ConcurrentHashMap` to keep language-specific `UnknownType`s (#1158) --- .../de/fraunhofer/aisec/cpg/graph/types/UnknownType.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/UnknownType.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/UnknownType.kt index 7b8a3ac96c..a31b79dd44 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/UnknownType.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/UnknownType.kt @@ -30,6 +30,7 @@ import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend import de.fraunhofer.aisec.cpg.graph.Name import de.fraunhofer.aisec.cpg.graph.types.PointerType.PointerOrigin import java.util.* +import java.util.concurrent.ConcurrentHashMap /** * UnknownType describe the case in which it is not possible for the CPG to determine which Type is @@ -82,11 +83,14 @@ class UnknownType : Type { companion object { /** A map of [UnknownType] and their respective [Language]. */ - private val unknownTypes = mutableMapOf?, UnknownType>() + private val unknownTypes = ConcurrentHashMap?, UnknownType>() + private val unknownTypeNull = UnknownType() /** Use this function to obtain an [UnknownType] for the particular [language]. */ @JvmStatic fun getUnknownType(language: Language?): UnknownType { + if (language == null) return unknownTypeNull + return unknownTypes.computeIfAbsent(language) { val unknownType = UnknownType() unknownType.language = language From 489397495ab42722bbea66e552134b4d85f45549 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 22 Apr 2023 10:15:29 +0200 Subject: [PATCH 033/143] Update dependency webpack to v5.80.0 (#1159) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .../src/main/nodejs/package.json | 2 +- .../src/main/nodejs/yarn.lock | 253 +++++++++--------- 2 files changed, 132 insertions(+), 123 deletions(-) diff --git a/cpg-language-typescript/src/main/nodejs/package.json b/cpg-language-typescript/src/main/nodejs/package.json index 0625eef777..d3278a596f 100644 --- a/cpg-language-typescript/src/main/nodejs/package.json +++ b/cpg-language-typescript/src/main/nodejs/package.json @@ -11,7 +11,7 @@ }, "license": "Apache-2.0", "devDependencies": { - "webpack": "5.79.0", + "webpack": "5.80.0", "webpack-cli": "5.0.0" } } \ No newline at end of file diff --git a/cpg-language-typescript/src/main/nodejs/yarn.lock b/cpg-language-typescript/src/main/nodejs/yarn.lock index d846372947..0790eaed0a 100644 --- a/cpg-language-typescript/src/main/nodejs/yarn.lock +++ b/cpg-language-typescript/src/main/nodejs/yarn.lock @@ -96,125 +96,125 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-18.15.3.tgz#f0b991c32cfc6a4e7f3399d6cb4b8cf9a0315014" integrity sha512-p6ua9zBxz5otCmbpb5D3U4B5Nanw6Pk3PPyX05xnxbB/fRv71N7CPmORg7uAD5P70T0xmx1pzAx/FUfa5X+3cw== -"@webassemblyjs/ast@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.1.tgz#2bfd767eae1a6996f432ff7e8d7fc75679c0b6a7" - integrity sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw== - dependencies: - "@webassemblyjs/helper-numbers" "1.11.1" - "@webassemblyjs/helper-wasm-bytecode" "1.11.1" - -"@webassemblyjs/floating-point-hex-parser@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz#f6c61a705f0fd7a6aecaa4e8198f23d9dc179e4f" - integrity sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ== - -"@webassemblyjs/helper-api-error@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz#1a63192d8788e5c012800ba6a7a46c705288fd16" - integrity sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg== - -"@webassemblyjs/helper-buffer@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz#832a900eb444884cde9a7cad467f81500f5e5ab5" - integrity sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA== - -"@webassemblyjs/helper-numbers@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz#64d81da219fbbba1e3bd1bfc74f6e8c4e10a62ae" - integrity sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ== - dependencies: - "@webassemblyjs/floating-point-hex-parser" "1.11.1" - "@webassemblyjs/helper-api-error" "1.11.1" +"@webassemblyjs/ast@1.11.5", "@webassemblyjs/ast@^1.11.5": + version "1.11.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.5.tgz#6e818036b94548c1fb53b754b5cae3c9b208281c" + integrity sha512-LHY/GSAZZRpsNQH+/oHqhRQ5FT7eoULcBqgfyTB5nQHogFnK3/7QoN7dLnwSE/JkUAF0SrRuclT7ODqMFtWxxQ== + dependencies: + "@webassemblyjs/helper-numbers" "1.11.5" + "@webassemblyjs/helper-wasm-bytecode" "1.11.5" + +"@webassemblyjs/floating-point-hex-parser@1.11.5": + version "1.11.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.5.tgz#e85dfdb01cad16b812ff166b96806c050555f1b4" + integrity sha512-1j1zTIC5EZOtCplMBG/IEwLtUojtwFVwdyVMbL/hwWqbzlQoJsWCOavrdnLkemwNoC/EOwtUFch3fuo+cbcXYQ== + +"@webassemblyjs/helper-api-error@1.11.5": + version "1.11.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.5.tgz#1e82fa7958c681ddcf4eabef756ce09d49d442d1" + integrity sha512-L65bDPmfpY0+yFrsgz8b6LhXmbbs38OnwDCf6NpnMUYqa+ENfE5Dq9E42ny0qz/PdR0LJyq/T5YijPnU8AXEpA== + +"@webassemblyjs/helper-buffer@1.11.5": + version "1.11.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.5.tgz#91381652ea95bb38bbfd270702351c0c89d69fba" + integrity sha512-fDKo1gstwFFSfacIeH5KfwzjykIE6ldh1iH9Y/8YkAZrhmu4TctqYjSh7t0K2VyDSXOZJ1MLhht/k9IvYGcIxg== + +"@webassemblyjs/helper-numbers@1.11.5": + version "1.11.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.5.tgz#23380c910d56764957292839006fecbe05e135a9" + integrity sha512-DhykHXM0ZABqfIGYNv93A5KKDw/+ywBFnuWybZZWcuzWHfbp21wUfRkbtz7dMGwGgT4iXjWuhRMA2Mzod6W4WA== + dependencies: + "@webassemblyjs/floating-point-hex-parser" "1.11.5" + "@webassemblyjs/helper-api-error" "1.11.5" "@xtuc/long" "4.2.2" -"@webassemblyjs/helper-wasm-bytecode@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz#f328241e41e7b199d0b20c18e88429c4433295e1" - integrity sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q== +"@webassemblyjs/helper-wasm-bytecode@1.11.5": + version "1.11.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.5.tgz#e258a25251bc69a52ef817da3001863cc1c24b9f" + integrity sha512-oC4Qa0bNcqnjAowFn7MPCETQgDYytpsfvz4ujZz63Zu/a/v71HeCAAmZsgZ3YVKec3zSPYytG3/PrRCqbtcAvA== -"@webassemblyjs/helper-wasm-section@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz#21ee065a7b635f319e738f0dd73bfbda281c097a" - integrity sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg== +"@webassemblyjs/helper-wasm-section@1.11.5": + version "1.11.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.5.tgz#966e855a6fae04d5570ad4ec87fbcf29b42ba78e" + integrity sha512-uEoThA1LN2NA+K3B9wDo3yKlBfVtC6rh0i4/6hvbz071E8gTNZD/pT0MsBf7MeD6KbApMSkaAK0XeKyOZC7CIA== dependencies: - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/helper-buffer" "1.11.1" - "@webassemblyjs/helper-wasm-bytecode" "1.11.1" - "@webassemblyjs/wasm-gen" "1.11.1" + "@webassemblyjs/ast" "1.11.5" + "@webassemblyjs/helper-buffer" "1.11.5" + "@webassemblyjs/helper-wasm-bytecode" "1.11.5" + "@webassemblyjs/wasm-gen" "1.11.5" -"@webassemblyjs/ieee754@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz#963929e9bbd05709e7e12243a099180812992614" - integrity sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ== +"@webassemblyjs/ieee754@1.11.5": + version "1.11.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.11.5.tgz#b2db1b33ce9c91e34236194c2b5cba9b25ca9d60" + integrity sha512-37aGq6qVL8A8oPbPrSGMBcp38YZFXcHfiROflJn9jxSdSMMM5dS5P/9e2/TpaJuhE+wFrbukN2WI6Hw9MH5acg== dependencies: "@xtuc/ieee754" "^1.2.0" -"@webassemblyjs/leb128@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.11.1.tgz#ce814b45574e93d76bae1fb2644ab9cdd9527aa5" - integrity sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw== +"@webassemblyjs/leb128@1.11.5": + version "1.11.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.11.5.tgz#482e44d26b6b949edf042a8525a66c649e38935a" + integrity sha512-ajqrRSXaTJoPW+xmkfYN6l8VIeNnR4vBOTQO9HzR7IygoCcKWkICbKFbVTNMjMgMREqXEr0+2M6zukzM47ZUfQ== dependencies: "@xtuc/long" "4.2.2" -"@webassemblyjs/utf8@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.11.1.tgz#d1f8b764369e7c6e6bae350e854dec9a59f0a3ff" - integrity sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ== - -"@webassemblyjs/wasm-edit@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz#ad206ebf4bf95a058ce9880a8c092c5dec8193d6" - integrity sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA== - dependencies: - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/helper-buffer" "1.11.1" - "@webassemblyjs/helper-wasm-bytecode" "1.11.1" - "@webassemblyjs/helper-wasm-section" "1.11.1" - "@webassemblyjs/wasm-gen" "1.11.1" - "@webassemblyjs/wasm-opt" "1.11.1" - "@webassemblyjs/wasm-parser" "1.11.1" - "@webassemblyjs/wast-printer" "1.11.1" - -"@webassemblyjs/wasm-gen@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz#86c5ea304849759b7d88c47a32f4f039ae3c8f76" - integrity sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA== - dependencies: - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/helper-wasm-bytecode" "1.11.1" - "@webassemblyjs/ieee754" "1.11.1" - "@webassemblyjs/leb128" "1.11.1" - "@webassemblyjs/utf8" "1.11.1" - -"@webassemblyjs/wasm-opt@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz#657b4c2202f4cf3b345f8a4c6461c8c2418985f2" - integrity sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw== - dependencies: - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/helper-buffer" "1.11.1" - "@webassemblyjs/wasm-gen" "1.11.1" - "@webassemblyjs/wasm-parser" "1.11.1" - -"@webassemblyjs/wasm-parser@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz#86ca734534f417e9bd3c67c7a1c75d8be41fb199" - integrity sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA== - dependencies: - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/helper-api-error" "1.11.1" - "@webassemblyjs/helper-wasm-bytecode" "1.11.1" - "@webassemblyjs/ieee754" "1.11.1" - "@webassemblyjs/leb128" "1.11.1" - "@webassemblyjs/utf8" "1.11.1" - -"@webassemblyjs/wast-printer@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz#d0c73beda8eec5426f10ae8ef55cee5e7084c2f0" - integrity sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg== - dependencies: - "@webassemblyjs/ast" "1.11.1" +"@webassemblyjs/utf8@1.11.5": + version "1.11.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.11.5.tgz#83bef94856e399f3740e8df9f63bc47a987eae1a" + integrity sha512-WiOhulHKTZU5UPlRl53gHR8OxdGsSOxqfpqWeA2FmcwBMaoEdz6b2x2si3IwC9/fSPLfe8pBMRTHVMk5nlwnFQ== + +"@webassemblyjs/wasm-edit@^1.11.5": + version "1.11.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.5.tgz#93ee10a08037657e21c70de31c47fdad6b522b2d" + integrity sha512-C0p9D2fAu3Twwqvygvf42iGCQ4av8MFBLiTb+08SZ4cEdwzWx9QeAHDo1E2k+9s/0w1DM40oflJOpkZ8jW4HCQ== + dependencies: + "@webassemblyjs/ast" "1.11.5" + "@webassemblyjs/helper-buffer" "1.11.5" + "@webassemblyjs/helper-wasm-bytecode" "1.11.5" + "@webassemblyjs/helper-wasm-section" "1.11.5" + "@webassemblyjs/wasm-gen" "1.11.5" + "@webassemblyjs/wasm-opt" "1.11.5" + "@webassemblyjs/wasm-parser" "1.11.5" + "@webassemblyjs/wast-printer" "1.11.5" + +"@webassemblyjs/wasm-gen@1.11.5": + version "1.11.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.5.tgz#ceb1c82b40bf0cf67a492c53381916756ef7f0b1" + integrity sha512-14vteRlRjxLK9eSyYFvw1K8Vv+iPdZU0Aebk3j6oB8TQiQYuO6hj9s4d7qf6f2HJr2khzvNldAFG13CgdkAIfA== + dependencies: + "@webassemblyjs/ast" "1.11.5" + "@webassemblyjs/helper-wasm-bytecode" "1.11.5" + "@webassemblyjs/ieee754" "1.11.5" + "@webassemblyjs/leb128" "1.11.5" + "@webassemblyjs/utf8" "1.11.5" + +"@webassemblyjs/wasm-opt@1.11.5": + version "1.11.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.5.tgz#b52bac29681fa62487e16d3bb7f0633d5e62ca0a" + integrity sha512-tcKwlIXstBQgbKy1MlbDMlXaxpucn42eb17H29rawYLxm5+MsEmgPzeCP8B1Cl69hCice8LeKgZpRUAPtqYPgw== + dependencies: + "@webassemblyjs/ast" "1.11.5" + "@webassemblyjs/helper-buffer" "1.11.5" + "@webassemblyjs/wasm-gen" "1.11.5" + "@webassemblyjs/wasm-parser" "1.11.5" + +"@webassemblyjs/wasm-parser@1.11.5", "@webassemblyjs/wasm-parser@^1.11.5": + version "1.11.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.5.tgz#7ba0697ca74c860ea13e3ba226b29617046982e2" + integrity sha512-SVXUIwsLQlc8srSD7jejsfTU83g7pIGr2YYNb9oHdtldSxaOhvA5xwvIiWIfcX8PlSakgqMXsLpLfbbJ4cBYew== + dependencies: + "@webassemblyjs/ast" "1.11.5" + "@webassemblyjs/helper-api-error" "1.11.5" + "@webassemblyjs/helper-wasm-bytecode" "1.11.5" + "@webassemblyjs/ieee754" "1.11.5" + "@webassemblyjs/leb128" "1.11.5" + "@webassemblyjs/utf8" "1.11.5" + +"@webassemblyjs/wast-printer@1.11.5": + version "1.11.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.11.5.tgz#7a5e9689043f3eca82d544d7be7a8e6373a6fa98" + integrity sha512-f7Pq3wvg3GSPUPzR0F6bmI89Hdb+u9WXrSKc4v+N0aV0q6r42WoF92Jp2jEorBEBRoRNXgjp53nBniDXcqZYPA== + dependencies: + "@webassemblyjs/ast" "1.11.5" "@xtuc/long" "4.2.2" "@webpack-cli/configtest@^2.0.0": @@ -336,10 +336,10 @@ electron-to-chromium@^1.3.723: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.772.tgz#fd1ed39f9f3149f62f581734e4f026e600369479" integrity sha512-X/6VRCXWALzdX+RjCtBU6cyg8WZgoxm9YA02COmDOiNJEZ59WkQggDbWZ4t/giHi/3GS+cvdrP6gbLISANAGYA== -enhanced-resolve@^5.10.0: - version "5.10.0" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.10.0.tgz#0dc579c3bb2a1032e357ac45b8f3a6f3ad4fb1e6" - integrity sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ== +enhanced-resolve@^5.13.0: + version "5.13.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.13.0.tgz#26d1ecc448c02de997133217b5c1053f34a0a275" + integrity sha512-eyV8f0y1+bzyfh8xAwW/WTSZpLbjhqc4ne9eGSH4Zo2ejdyiNG9pU6mf9DG8a7+Auk6MFTlNOT4Y2y/9k8GKVg== dependencies: graceful-fs "^4.2.4" tapable "^2.2.0" @@ -625,7 +625,7 @@ safe-buffer@^5.1.0: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== -schema-utils@^3.1.0, schema-utils@^3.1.1: +schema-utils@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.1.1.tgz#bc74c4b6b6995c1d88f76a8b77bea7219e0c8281" integrity sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw== @@ -634,6 +634,15 @@ schema-utils@^3.1.0, schema-utils@^3.1.1: ajv "^6.12.5" ajv-keywords "^3.5.2" +schema-utils@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.1.2.tgz#36c10abca6f7577aeae136c804b0c741edeadc99" + integrity sha512-pvjEHOgWc9OWA/f/DE3ohBWTD6EleVLf7iFUkoSwAxttdBhB9QUebQgxER2kWueOvRJXPHNnyrvvh9eZINB8Eg== + dependencies: + "@types/json-schema" "^7.0.8" + ajv "^6.12.5" + ajv-keywords "^3.5.2" + serialize-javascript@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.1.tgz#b206efb27c3da0b0ab6b52f48d170b7996458e5c" @@ -763,21 +772,21 @@ webpack-sources@^3.2.3: resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== -webpack@5.79.0: - version "5.79.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.79.0.tgz#8552b5da5a26e4e25842c08a883e08fc7740547a" - integrity sha512-3mN4rR2Xq+INd6NnYuL9RC9GAmc1ROPKJoHhrZ4pAjdMFEkJJWrsPw8o2JjCIyQyTu7rTXYn4VG6OpyB3CobZg== +webpack@5.80.0: + version "5.80.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.80.0.tgz#3e660b4ab572be38c5e954bdaae7e2bf76010fdc" + integrity sha512-OIMiq37XK1rWO8mH9ssfFKZsXg4n6klTEDL7S8/HqbAOBBaiy8ABvXvz0dDCXeEF9gqwxSvVk611zFPjS8hJxA== dependencies: "@types/eslint-scope" "^3.7.3" "@types/estree" "^1.0.0" - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/wasm-edit" "1.11.1" - "@webassemblyjs/wasm-parser" "1.11.1" + "@webassemblyjs/ast" "^1.11.5" + "@webassemblyjs/wasm-edit" "^1.11.5" + "@webassemblyjs/wasm-parser" "^1.11.5" acorn "^8.7.1" acorn-import-assertions "^1.7.6" browserslist "^4.14.5" chrome-trace-event "^1.0.2" - enhanced-resolve "^5.10.0" + enhanced-resolve "^5.13.0" es-module-lexer "^1.2.1" eslint-scope "5.1.1" events "^3.2.0" @@ -787,7 +796,7 @@ webpack@5.79.0: loader-runner "^4.2.0" mime-types "^2.1.27" neo-async "^2.6.2" - schema-utils "^3.1.0" + schema-utils "^3.1.2" tapable "^2.1.1" terser-webpack-plugin "^5.3.7" watchpack "^2.4.0" From 3067ff5c9b7ed662485889b5d7385bddf7e9deee Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 22 Apr 2023 08:33:24 +0000 Subject: [PATCH 034/143] Update plugin node to v3.6.0 (#1162) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7cd263d3e5..f5e8af30c4 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -53,4 +53,4 @@ kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin"} dokka = { id = "org.jetbrains.dokka", version.ref = "kotlin" } sonarqube = { id = "org.sonarqube", version.ref = "sonarqube" } spotless = { id = "com.diffplug.spotless", version.ref = "spotless" } -node = { id = "com.github.node-gradle.node", version = "3.5.0"} +node = { id = "com.github.node-gradle.node", version = "3.6.0"} From dc94adb7430a41cad8d35291d660cd8fffdda733 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 5 May 2023 18:34:28 +0200 Subject: [PATCH 035/143] Update plugin node to v4 (#1166) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f5e8af30c4..7c32962431 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -53,4 +53,4 @@ kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin"} dokka = { id = "org.jetbrains.dokka", version.ref = "kotlin" } sonarqube = { id = "org.sonarqube", version.ref = "sonarqube" } spotless = { id = "com.diffplug.spotless", version.ref = "spotless" } -node = { id = "com.github.node-gradle.node", version = "3.6.0"} +node = { id = "com.github.node-gradle.node", version = "4.0.0"} From f1889d76f1a64e3ee2732e8217115ae49f8f1106 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 5 May 2023 20:59:57 +0200 Subject: [PATCH 036/143] Update dependency com.fasterxml.jackson.module:jackson-module-kotlin to v2.15.0 (#1164) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7c32962431..ce768ba185 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -22,7 +22,7 @@ neo4j-ogm = { module = "org.neo4j:neo4j-ogm", version.ref = "neo4j"} neo4j-ogm-bolt = { module = "org.neo4j:neo4j-ogm-bolt-driver", version.ref = "neo4j"} javaparser = { module = "com.github.javaparser:javaparser-symbol-solver-core", version = "3.25.0"} -jackson = { module = "com.fasterxml.jackson.module:jackson-module-kotlin", version = "2.14.0"} +jackson = { module = "com.fasterxml.jackson.module:jackson-module-kotlin", version = "2.15.0"} eclipse-runtime = { module = "org.eclipse.platform:org.eclipse.core.runtime", version = "3.26.0"} osgi-service = { module = "org.osgi:org.osgi.service.prefs", version = "1.1.2"} icu4j = { module = "com.ibm.icu:icu4j", version = "73.1"} From be5a8241d316d0ca09f40cdcdae280b430a6149e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 6 May 2023 10:07:23 +0200 Subject: [PATCH 037/143] Update dependency @types/node to v18.16.5 (#1163) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- cpg-language-typescript/src/main/nodejs/package.json | 2 +- cpg-language-typescript/src/main/nodejs/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cpg-language-typescript/src/main/nodejs/package.json b/cpg-language-typescript/src/main/nodejs/package.json index d3278a596f..fa67abe7e7 100644 --- a/cpg-language-typescript/src/main/nodejs/package.json +++ b/cpg-language-typescript/src/main/nodejs/package.json @@ -6,7 +6,7 @@ "start": "node src/parser.js" }, "dependencies": { - "@types/node": "18.15.3", + "@types/node": "18.16.5", "typescript": "5.0.2" }, "license": "Apache-2.0", diff --git a/cpg-language-typescript/src/main/nodejs/yarn.lock b/cpg-language-typescript/src/main/nodejs/yarn.lock index 0790eaed0a..954916d5fd 100644 --- a/cpg-language-typescript/src/main/nodejs/yarn.lock +++ b/cpg-language-typescript/src/main/nodejs/yarn.lock @@ -91,10 +91,10 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.0.tgz#f38c7139247a1d619f6cc6f27b072606af7c289d" integrity sha512-IOXCvVRToe7e0ny7HpT/X9Rb2RYtElG1a+VshjwT00HxrM2dWBApHQoqsI6WiY7Q03vdf2bCrIGzVrkF/5t10w== -"@types/node@18.15.3": - version "18.15.3" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.15.3.tgz#f0b991c32cfc6a4e7f3399d6cb4b8cf9a0315014" - integrity sha512-p6ua9zBxz5otCmbpb5D3U4B5Nanw6Pk3PPyX05xnxbB/fRv71N7CPmORg7uAD5P70T0xmx1pzAx/FUfa5X+3cw== +"@types/node@18.16.5": + version "18.16.5" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.16.5.tgz#bf64e42719dc2e74da24709a2e1c0b50a966120a" + integrity sha512-seOA34WMo9KB+UA78qaJoCO20RJzZGVXQ5Sh6FWu0g/hfT44nKXnej3/tCQl7FL97idFpBhisLYCTB50S0EirA== "@webassemblyjs/ast@1.11.5", "@webassemblyjs/ast@^1.11.5": version "1.11.5" From 747d435d700df04ca38bbb28637fa3cf034c2140 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 6 May 2023 08:23:21 +0000 Subject: [PATCH 038/143] Update dependency org.jetbrains.kotlinx:kotlinx-coroutines-core to v1.7.0 (#1167) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ce768ba185..83653baaf7 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,7 +8,7 @@ spotless = "6.18.0" [libraries] kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin"} kotlin-script-runtime = { module = "org.jetbrains.kotlin:kotlin-script-runtime", version.ref = "kotlin"} -kotlin-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version = "1.6.4"} +kotlin-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version = "1.7.0"} kotlin-ki-shell = { module = "org.jetbrains.kotlinx:ki-shell", version = "0.5.2"} kotlin-test-junit5 = { module = "org.jetbrains.kotlin:kotlin-test-junit5", version.ref = "kotlin"} # this is only needed for the testFixtures in cpg-core, everywhere else kotlin("test") is used From 62d4cf7903526a26fa219bf9f2fbf37655474601 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 6 May 2023 08:40:02 +0000 Subject: [PATCH 039/143] Update dependency webpack to v5.82.0 (#1165) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- cpg-language-typescript/src/main/nodejs/package.json | 2 +- cpg-language-typescript/src/main/nodejs/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cpg-language-typescript/src/main/nodejs/package.json b/cpg-language-typescript/src/main/nodejs/package.json index fa67abe7e7..00d16a6856 100644 --- a/cpg-language-typescript/src/main/nodejs/package.json +++ b/cpg-language-typescript/src/main/nodejs/package.json @@ -11,7 +11,7 @@ }, "license": "Apache-2.0", "devDependencies": { - "webpack": "5.80.0", + "webpack": "5.82.0", "webpack-cli": "5.0.0" } } \ No newline at end of file diff --git a/cpg-language-typescript/src/main/nodejs/yarn.lock b/cpg-language-typescript/src/main/nodejs/yarn.lock index 954916d5fd..90bfc3f9cd 100644 --- a/cpg-language-typescript/src/main/nodejs/yarn.lock +++ b/cpg-language-typescript/src/main/nodejs/yarn.lock @@ -772,10 +772,10 @@ webpack-sources@^3.2.3: resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== -webpack@5.80.0: - version "5.80.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.80.0.tgz#3e660b4ab572be38c5e954bdaae7e2bf76010fdc" - integrity sha512-OIMiq37XK1rWO8mH9ssfFKZsXg4n6klTEDL7S8/HqbAOBBaiy8ABvXvz0dDCXeEF9gqwxSvVk611zFPjS8hJxA== +webpack@5.82.0: + version "5.82.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.82.0.tgz#3c0d074dec79401db026b4ba0fb23d6333f88e7d" + integrity sha512-iGNA2fHhnDcV1bONdUu554eZx+XeldsaeQ8T67H6KKHl2nUSwX8Zm7cmzOA46ox/X1ARxf7Bjv8wQ/HsB5fxBg== dependencies: "@types/eslint-scope" "^3.7.3" "@types/estree" "^1.0.0" From 02878ac0e91e1766cafe49e454761fd8c3e7f5e8 Mon Sep 17 00:00:00 2001 From: KuechA <31155350+KuechA@users.noreply.github.com> Date: Sat, 6 May 2023 11:02:10 +0200 Subject: [PATCH 040/143] Transfer more of the core-functionality tests to the DSL to get rid of language frontends (#1140) --- .../aisec/cpg/graph/builder/Fluent.kt | 354 ++++++++- .../cpg/graph/statements/WhileStatement.kt | 12 +- .../expressions/ConditionalExpression.kt | 16 +- .../de/fraunhofer/aisec/cpg/GraphExamples.kt | 718 ++++++++++++++++++ .../aisec/cpg/enhancements/DFGTest.kt | 207 ----- .../aisec/cpg/enhancements/EOGTest.kt | 48 +- .../aisec/cpg/enhancements/InferenceTest.kt | 26 +- .../fraunhofer/aisec/cpg/passes}/DFGTest.kt | 578 ++++++++------ .../aisec/cpg/passes/UnresolvedDFGPassTest.kt | 180 +++++ .../aisec/cpg/passes/VariableResolverTest.kt | 9 +- .../test/resources/dfg/compoundoperator.cpp | 6 - .../resources/dfg/conditional_expression.cpp | 7 - .../src/test/resources/dfg/unaryoperator.cpp | 6 - .../src/test/resources/inference/record.cpp | 5 - .../test/resources/inference/record_ptr.cpp | 7 - .../aisec/cpg/frontends/TestLanguage.kt | 12 +- .../aisec/cpg/enhancements/EOGTest.kt | 46 +- .../de/fraunhofer/aisec/cpg/passes/DFGTest.kt | 67 -- .../aisec/cpg/passes/UnresolvedDFGPassTest.kt | 118 --- .../src/test/resources/dfg/BasicSlice.java | 50 -- .../dfg/ControlFlowSensitiveDFGIfMerge.java | 20 - .../dfg/ControlFlowSensitiveDFGIfNoMerge.java | 11 - .../dfg/ControlFlowSensitiveDFGSwitch.java | 22 - .../dfg/DelayedAssignmentAfterRHS.java | 9 - .../resources/dfg/DfgUnresolvedCalls.java | 19 - .../src/test/resources/dfg/LoopDFGs.java | 35 - .../src/test/resources/dfg/ReturnTest.java | 10 - .../test/resources/variables/Variables.java | 22 - 28 files changed, 1644 insertions(+), 976 deletions(-) delete mode 100644 cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/DFGTest.kt rename {cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements => cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes}/DFGTest.kt (64%) create mode 100644 cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/UnresolvedDFGPassTest.kt rename {cpg-language-java => cpg-core}/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/VariableResolverTest.kt (90%) delete mode 100644 cpg-core/src/test/resources/dfg/compoundoperator.cpp delete mode 100644 cpg-core/src/test/resources/dfg/conditional_expression.cpp delete mode 100644 cpg-core/src/test/resources/dfg/unaryoperator.cpp delete mode 100644 cpg-core/src/test/resources/inference/record.cpp delete mode 100644 cpg-core/src/test/resources/inference/record_ptr.cpp delete mode 100644 cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/DFGTest.kt delete mode 100644 cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/UnresolvedDFGPassTest.kt delete mode 100644 cpg-language-java/src/test/resources/dfg/BasicSlice.java delete mode 100644 cpg-language-java/src/test/resources/dfg/ControlFlowSensitiveDFGIfMerge.java delete mode 100644 cpg-language-java/src/test/resources/dfg/ControlFlowSensitiveDFGIfNoMerge.java delete mode 100644 cpg-language-java/src/test/resources/dfg/ControlFlowSensitiveDFGSwitch.java delete mode 100644 cpg-language-java/src/test/resources/dfg/DelayedAssignmentAfterRHS.java delete mode 100644 cpg-language-java/src/test/resources/dfg/DfgUnresolvedCalls.java delete mode 100644 cpg-language-java/src/test/resources/dfg/LoopDFGs.java delete mode 100644 cpg-language-java/src/test/resources/dfg/ReturnTest.java delete mode 100644 cpg-language-java/src/test/resources/variables/Variables.java diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt index 4137d3a22e..94dc1c0ba3 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt @@ -460,6 +460,48 @@ fun LanguageFrontend.ifStmt(init: IfStatement.() -> Unit): IfStatement { return node } +/** + * Creates a new [SwitchStatement] in the Fluent Node DSL and adds it to the + * [StatementHolder.statements] of the nearest enclosing [StatementHolder]. The [init] block can be + * used to create further sub-nodes as well as configuring the created node itself. + */ +context(StatementHolder) + +fun LanguageFrontend.switchStmt( + selector: Expression, + needsScope: Boolean = true, + init: SwitchStatement.() -> Unit +): SwitchStatement { + val node = newSwitchStatement() + node.selector = selector + scopeIfNecessary(needsScope, node, init) + + (this@StatementHolder) += node + + return node +} + +/** + * Creates a new [WhileStatement] in the Fluent Node DSL and adds it to the + * [StatementHolder.statements] of the nearest enclosing [StatementHolder]. The [init] block can be + * used to create further sub-nodes as well as configuring the created node itself. + */ +context(StatementHolder) + +fun LanguageFrontend.whileStmt( + needsScope: Boolean = true, + init: WhileStatement.() -> Unit +): WhileStatement { + val node = newWhileStatement() + scopeIfNecessary(needsScope, node, init) + + (this@StatementHolder) += node + + return node +} + +// TODO: Combine the condition functions + /** * Configures the [IfStatement.condition] in the Fluent Node DSL of the nearest enclosing * [IfStatement]. The [init] block can be used to create further sub-nodes as well as configuring @@ -471,6 +513,17 @@ fun LanguageFrontend.condition(init: IfStatement.() -> BinaryOperator): BinaryOp return init(this@IfStatement) } +/** + * Configures the [WhileStatement.condition] in the Fluent Node DSL of the nearest enclosing + * [WhileStatement]. The [init] block can be used to create further sub-nodes as well as configuring + * the created node itself. + */ +context(WhileStatement) + +fun LanguageFrontend.whileCondition(init: WhileStatement.() -> BinaryOperator): BinaryOperator { + return init(this@WhileStatement) +} + /** * Creates a new [CompoundStatement] in the Fluent Node DSL and sets it to the * [IfStatement.thenStatement] of the nearest enclosing [IfStatement]. The [init] block can be used @@ -506,6 +559,38 @@ fun LanguageFrontend.elseIf(init: IfStatement.() -> Unit): IfStatement { return node } +// TODO: Merge the bodies together + +/** + * Creates a new [CompoundStatement] in the Fluent Node DSL and sets it to the + * [WhileStatement.statement] of the nearest enclosing [WhileStatement]. The [init] block can be + * used to create further sub-nodes as well as configuring the created node itself. + */ +context(WhileStatement) + +fun LanguageFrontend.loopBody(init: CompoundStatement.() -> Unit): CompoundStatement { + val node = newCompoundStatement() + init(node) + statement = node + + return node +} + +/** + * Creates a new [CompoundStatement] in the Fluent Node DSL and sets it to the + * [SwitchStatement.statement] of the nearest enclosing [SwitchStatement]. The [init] block can be + * used to create further sub-nodes as well as configuring the created node itself. + */ +context(SwitchStatement) + +fun LanguageFrontend.switchBody(init: CompoundStatement.() -> Unit): CompoundStatement { + val node = newCompoundStatement() + init(node) + statement = node + + return node +} + /** * Creates a new [CompoundStatement] in the Fluent Node DSL and sets it to the * [IfStatement.elseStatement] of the nearest enclosing [IfStatement]. The [init] block can be used @@ -525,6 +610,102 @@ fun LanguageFrontend.elseStmt( return node } +/** + * Creates a new [LabelStatement] in the Fluent Node DSL and invokes [StatementHolder.addStatement] + * of the nearest enclosing [Holder], but only if it is an [StatementHolder]. + */ +context(Holder) + +fun LanguageFrontend.label( + label: String, + init: (LabelStatement.() -> Statement)? = null +): LabelStatement { + val node = newLabelStatement() + node.label = label + if (init != null) { + node.subStatement = init(node) + } + + // Only add this to a statement holder if the nearest holder is a statement holder + val holder = this@Holder + if (holder is StatementHolder) { + holder += node + } + + return node +} + +/** + * Creates a new [ContinueStatement] in the Fluent Node DSL and invokes + * [StatementHolder.addStatement] of the nearest enclosing [StatementHolder]. + */ +context(StatementHolder) + +fun LanguageFrontend.continueStmt(label: String? = null): ContinueStatement { + val node = newContinueStatement() + node.label = label + + this@StatementHolder += node + + return node +} + +/** + * Creates a new [BreakStatement] in the Fluent Node DSL and invokes [StatementHolder.addStatement] + * of the nearest enclosing [Holder], but only if it is an [StatementHolder]. + */ +context(Holder) + +fun LanguageFrontend.breakStmt(label: String? = null): BreakStatement { + val node = newBreakStatement() + node.label = label + + // Only add this to a statement holder if the nearest holder is a statement holder + val holder = this@Holder + if (holder is StatementHolder) { + holder += node + } + + return node +} + +/** + * Creates a new [CaseStatement] in the Fluent Node DSL and invokes [StatementHolder.addStatement] + * of the nearest enclosing [Holder], but only if it is an [StatementHolder]. + */ +context(Holder) + +fun LanguageFrontend.case(caseExpr: Expression? = null): CaseStatement { + val node = newCaseStatement() + node.caseExpression = caseExpr + + // Only add this to a statement holder if the nearest holder is a statement holder + val holder = this@Holder + if (holder is StatementHolder) { + holder += node + } + + return node +} +/** + * Creates a new [DefaultStatement] in the Fluent Node DSL and invokes + * [StatementHolder.addStatement] of the nearest enclosing [Holder], but only if it is an + * [StatementHolder]. + */ +context(Holder) + +fun LanguageFrontend.default(): DefaultStatement { + val node = newDefaultStatement() + + // Only add this to a statement holder if the nearest holder is a statement holder + val holder = this@Holder + if (holder is StatementHolder) { + holder += node + } + + return node +} + /** * Creates a new [Literal] in the Fluent Node DSL and invokes [ArgumentHolder.addArgument] of the * nearest enclosing [Holder], but only if it is an [ArgumentHolder]. @@ -578,7 +759,11 @@ fun LanguageFrontend.ref( */ context(Holder) -fun LanguageFrontend.member(name: CharSequence, base: Expression? = null): MemberExpression { +fun LanguageFrontend.member( + name: CharSequence, + base: Expression? = null, + operatorCode: String = "." +): MemberExpression { val parsedName = parseName(name) val type = if (parsedName.parent != null) { @@ -593,7 +778,7 @@ fun LanguageFrontend.member(name: CharSequence, base: Expression? = null): Membe } val memberBase = base ?: memberOrRef(parsedName.parent ?: parseName("this"), type) - val node = newMemberExpression(name, memberBase) + val node = newMemberExpression(name, memberBase, operatorCode = operatorCode) // Only add this to an argument holder if the nearest holder is an argument holder val holder = this@Holder @@ -604,6 +789,28 @@ fun LanguageFrontend.member(name: CharSequence, base: Expression? = null): Membe return node } +/** + * Creates a new [BinaryOperator] with a `*` [BinaryOperator.operatorCode] in the Fluent Node DSL + * and invokes [ArgumentHolder.addArgument] of the nearest enclosing [ArgumentHolder]. + */ +context(LanguageFrontend, ArgumentHolder) + +operator fun Expression.times(rhs: Expression): BinaryOperator { + val node = (this@LanguageFrontend).newBinaryOperator("*") + node.lhs = this + node.rhs = rhs + + (this@ArgumentHolder) += node + + // We need to do a little trick here. Because of the evaluation order, lhs and rhs might also + // been added to the argument holders arguments (and we do not want that). However, we cannot + // prevent it, so we need to remove them again + (this@ArgumentHolder) -= node.lhs + (this@ArgumentHolder) -= node.rhs + + return node +} + /** * Creates a new [BinaryOperator] with a `+` [BinaryOperator.operatorCode] in the Fluent Node DSL * and invokes [ArgumentHolder.addArgument] of the nearest enclosing [ArgumentHolder]. @@ -626,6 +833,42 @@ operator fun Expression.plus(rhs: Expression): BinaryOperator { return node } +/** + * Creates a new [BinaryOperator] with a `+` [BinaryOperator.operatorCode] in the Fluent Node DSL + * and invokes [StatementHolder.addStatement] of the nearest enclosing [StatementHolder]. + */ +context(LanguageFrontend, StatementHolder) + +operator fun Expression.plusAssign(rhs: Expression): Unit { + val node = (this@LanguageFrontend).newBinaryOperator("+=") + node.lhs = this + node.rhs = rhs + + (this@StatementHolder) += node +} + +/** + * Creates a new [BinaryOperator] with a `+` [BinaryOperator.operatorCode] in the Fluent Node DSL + * and invokes [ArgumentHolder.addArgument] of the nearest enclosing [ArgumentHolder]. + */ +context(LanguageFrontend, ArgumentHolder) + +operator fun Expression.rem(rhs: Expression): BinaryOperator { + val node = (this@LanguageFrontend).newBinaryOperator("%") + node.lhs = this + node.rhs = rhs + + (this@ArgumentHolder) += node + + // We need to do a little trick here. Because of the evaluation order, lhs and rhs might also + // been added to the argument holders arguments (and we do not want that). However, we cannot + // prevent it, so we need to remove them again + (this@ArgumentHolder) -= node.lhs + (this@ArgumentHolder) -= node.rhs + + return node +} + /** * Creates a new [BinaryOperator] with a `-` [BinaryOperator.operatorCode] in the Fluent Node DSL * and invokes [ArgumentHolder.addArgument] of the nearest enclosing [ArgumentHolder]. @@ -642,6 +885,55 @@ operator fun Expression.minus(rhs: Expression): BinaryOperator { return node } +/** + * Creates a new [UnaryOperator] with a `&` [UnaryOperator.operatorCode] in the Fluent Node DSL and + * invokes [ArgumentHolder.addArgument] of the nearest enclosing [ArgumentHolder]. + */ +context(LanguageFrontend, ArgumentHolder) + +fun reference(input: Expression): UnaryOperator { + val node = (this@LanguageFrontend).newUnaryOperator("&", false, false) + node.input = input + + this@ArgumentHolder += node + + return node +} + +/** + * Creates a new [UnaryOperator] with a `--` [UnaryOperator.operatorCode] in the Fluent Node DSL and + * invokes [StatementHolder.addStatement] of the nearest enclosing [StatementHolder]. + */ +context(LanguageFrontend, Holder) + +operator fun Expression.dec(): UnaryOperator { + val node = (this@LanguageFrontend).newUnaryOperator("--", true, false) + node.input = this + + if (this@Holder is StatementHolder) { + this@Holder += node + } + + return node +} + +/** + * Creates a new [UnaryOperator] with a `++` [UnaryOperator.operatorCode] in the Fluent Node DSL and + * invokes [ArgumentHolder.addArgument] of the nearest enclosing [ArgumentHolder]. + */ +context(LanguageFrontend, Holder) + +operator fun Expression.inc(): UnaryOperator { + val node = (this@LanguageFrontend).newUnaryOperator("++", true, false) + node.input = this + + if (this@Holder is StatementHolder) { + this@Holder += node + } + + return node +} + /** * Creates a new [BinaryOperator] with a `==` [BinaryOperator.operatorCode] in the Fluent Node DSL * and invokes [ArgumentHolder.addArgument] of the nearest enclosing [ArgumentHolder]. @@ -674,9 +966,43 @@ infix fun Expression.gt(rhs: Expression): BinaryOperator { return node } +/** + * Creates a new [BinaryOperator] with a `<` [BinaryOperator.operatorCode] in the Fluent Node DSL + * and invokes [ArgumentHolder.addArgument] of the nearest enclosing [ArgumentHolder]. + */ +context(LanguageFrontend, ArgumentHolder) + +infix fun Expression.lt(rhs: Expression): BinaryOperator { + val node = (this@LanguageFrontend).newBinaryOperator("<") + node.lhs = this + node.rhs = rhs + + (this@ArgumentHolder) += node + + return node +} + +/** + * Creates a new [ConditionalExpression] with a `=` [BinaryOperator.operatorCode] in the Fluent Node + * DSL and invokes [StatementHolder.addStatement] of the nearest enclosing [StatementHolder]. + */ +context(LanguageFrontend, StatementHolder) + +fun Expression.conditional( + condition: Expression, + thenExpr: Expression, + elseExpr: Expression +): ConditionalExpression { + val node = (this@LanguageFrontend).newConditionalExpression(condition, thenExpr, elseExpr) + + (this@StatementHolder) += node + + return node +} + /** * Creates a new [BinaryOperator] with a `=` [BinaryOperator.operatorCode] in the Fluent Node DSL - * and invokes [ArgumentHolder.addArgument] of the nearest enclosing [StatementHolder]. + * and invokes [StatementHolder.addStatement] of the nearest enclosing [StatementHolder]. */ context(LanguageFrontend, StatementHolder) @@ -692,33 +1018,39 @@ infix fun Expression.assign(init: BinaryOperator.() -> Expression): BinaryOperat /** * Creates a new [BinaryOperator] with a `=` [BinaryOperator.operatorCode] in the Fluent Node DSL - * and invokes [ArgumentHolder.addArgument] of the nearest enclosing [StatementHolder]. + * and invokes [StatementHolder.addStatement] of the nearest enclosing [StatementHolder]. */ -context(LanguageFrontend, StatementHolder) +context(LanguageFrontend, Holder) infix fun Expression.assign(rhs: Expression): BinaryOperator { val node = (this@LanguageFrontend).newBinaryOperator("=") node.lhs = this node.rhs = rhs - (this@StatementHolder) += node + if (this@Holder is StatementHolder) { + this@Holder += node + } return node } /** Creates a new [Type] with the given [name] in the Fluent Node DSL. */ -fun LanguageFrontend.t(name: CharSequence): Type { - return parseType(name) +fun LanguageFrontend.t(name: CharSequence, init: (Type.() -> Unit)? = null): Type { + val type = parseType(name) + if (init != null) { + init(type) + } + return type } /** * Internally used to enter a new scope if [needsScope] is true before invoking [init] and leaving * it afterwards. */ -private fun LanguageFrontend.scopeIfNecessary( +private fun LanguageFrontend.scopeIfNecessary( needsScope: Boolean, - node: CompoundStatement, - init: CompoundStatement.() -> Unit + node: T, + init: T.() -> Unit ) { if (needsScope) { scopeManager.enterScope(node) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/WhileStatement.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/WhileStatement.kt index ceaea1b8aa..faf23cf27a 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/WhileStatement.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/WhileStatement.kt @@ -26,13 +26,14 @@ package de.fraunhofer.aisec.cpg.graph.statements import de.fraunhofer.aisec.cpg.graph.AST +import de.fraunhofer.aisec.cpg.graph.ArgumentHolder import de.fraunhofer.aisec.cpg.graph.declarations.Declaration import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression import java.util.* import org.apache.commons.lang3.builder.ToStringBuilder /** Represents a conditional loop statement of the form: `while(...){...}`. */ -class WhileStatement : Statement() { +class WhileStatement : Statement(), ArgumentHolder { /** C++ allows defining a declaration instead of a pure logical expression as condition */ @AST var conditionDeclaration: Declaration? = null @@ -53,6 +54,15 @@ class WhileStatement : Statement() { .toString() } + override fun addArgument(expression: Expression) { + this.condition = expression + } + + override fun replaceArgument(old: Expression, new: Expression): Boolean { + this.condition = new + return true + } + override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is WhileStatement) return false diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ConditionalExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ConditionalExpression.kt index bdac419afd..2ff4a459c7 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ConditionalExpression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ConditionalExpression.kt @@ -25,10 +25,7 @@ */ package de.fraunhofer.aisec.cpg.graph.statements.expressions -import de.fraunhofer.aisec.cpg.graph.AST -import de.fraunhofer.aisec.cpg.graph.HasType -import de.fraunhofer.aisec.cpg.graph.TypeManager -import de.fraunhofer.aisec.cpg.graph.newUnknownType +import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.types.Type import java.util.ArrayList import java.util.Objects @@ -38,7 +35,7 @@ import org.apache.commons.lang3.builder.ToStringBuilder * Represents an expression containing a ternary operator: `var x = condition ? valueIfTrue : * valueIfFalse`; */ -class ConditionalExpression : Expression(), HasType.TypeListener { +class ConditionalExpression : Expression(), HasType.TypeListener, ArgumentHolder { @AST var condition: Expression = ProblemExpression("could not parse condition expression") @AST @@ -96,6 +93,15 @@ class ConditionalExpression : Expression(), HasType.TypeListener { .build() } + override fun addArgument(expression: Expression) { + // Do nothing + } + + override fun replaceArgument(old: Expression, new: Expression): Boolean { + // Do nothing + return false + } + override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is ConditionalExpression) return false diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/GraphExamples.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/GraphExamples.kt index a91e4b77c4..508cace363 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/GraphExamples.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/GraphExamples.kt @@ -25,13 +25,729 @@ */ package de.fraunhofer.aisec.cpg +import de.fraunhofer.aisec.cpg.frontends.StructTestLanguage import de.fraunhofer.aisec.cpg.frontends.TestLanguage import de.fraunhofer.aisec.cpg.frontends.TestLanguageFrontend import de.fraunhofer.aisec.cpg.graph.builder.* import de.fraunhofer.aisec.cpg.graph.newVariableDeclaration +import de.fraunhofer.aisec.cpg.sarif.PhysicalLocation +import de.fraunhofer.aisec.cpg.sarif.Region +import java.net.URI class GraphExamples { companion object { + + fun getInferenceRecordPtr( + config: TranslationConfiguration = + TranslationConfiguration.builder() + .defaultPasses() + .registerLanguage(StructTestLanguage(".")) + .inferenceConfiguration( + InferenceConfiguration.builder().inferRecords(true).build() + ) + .build() + ) = + TestLanguageFrontend( + ScopeManager(), + config.languages.first().namespaceDelimiter, + config.languages.first() + ) + .build { + translationResult(config) { + translationUnit("record.cpp") { + // The main method + function("main", t("int")) { + body { + declare { variable("node", t("T*")) } + member("value", ref("node"), "->") assign literal(42, t("int")) + member("next", ref("node"), "->") assign ref("node") + memberCall( + "dump", + ref("node") + ) // TODO: Do we have to encode the "->" here? + returnStmt { isImplicit = true } + } + } + } + } + } + + fun getInferenceRecord( + config: TranslationConfiguration = + TranslationConfiguration.builder() + .defaultPasses() + .registerLanguage(StructTestLanguage(".")) + .inferenceConfiguration( + InferenceConfiguration.builder().inferRecords(true).build() + ) + .build() + ) = + TestLanguageFrontend( + ScopeManager(), + config.languages.first().namespaceDelimiter, + config.languages.first() + ) + .build { + translationResult(config) { + translationUnit("record.cpp") { + // The main method + function("main", t("int")) { + body { + declare { variable("node", t("T")) } + member("value", ref("node")) assign literal(42, t("int")) + member("next", ref("node")) assign { reference(ref("node")) } + returnStmt { isImplicit = true } + } + } + } + } + } + + fun getVariables( + config: TranslationConfiguration = + TranslationConfiguration.builder() + .defaultPasses() + .registerLanguage(TestLanguage(".")) + .build() + ) = + TestLanguageFrontend(ScopeManager(), ".").build { + translationResult(config) { + translationUnit("Variables.java") { + record("Variables") { + field("field", t("int")) { + literal(42, t("int")) + modifiers = listOf("private") + } + method("getField", t("int")) { + receiver = newVariableDeclaration("this", t("Variables")) + body { returnStmt { member("field") } } + } + method("getLocal", t("int")) { + receiver = newVariableDeclaration("this", t("Variables")) + body { + declare { + variable("local", t("int")) { literal(42, t("int")) } + } + returnStmt { ref("local") } + } + } + method("getShadow", t("int")) { + receiver = newVariableDeclaration("this", t("Variables")) + body { + declare { + variable("field", t("int")) { literal(43, t("int")) } + } + returnStmt { ref("field") } + } + } + method("getNoShadow", t("int")) { + receiver = newVariableDeclaration("this", t("Variables")) + body { + declare { + variable("field", t("int")) { literal(43, t("int")) } + } + returnStmt { member("field", ref("this")) } + } + } + } + } + } + } + + fun getUnaryOperator( + config: TranslationConfiguration = + TranslationConfiguration.builder() + .defaultPasses() + .registerLanguage(TestLanguage(".")) + .build() + ) = + TestLanguageFrontend(ScopeManager(), ".").build { + translationResult(config) { + translationUnit("unaryoperator.cpp") { + // The main method + function("somefunc") { + body { + declare { variable("i", t("int")) { literal(0, t("int")) } } + ref("i").inc() + returnStmt { isImplicit = true } + } + } + } + } + } + + fun getCompoundOperator( + config: TranslationConfiguration = + TranslationConfiguration.builder() + .defaultPasses() + .registerLanguage(TestLanguage(".")) + .build() + ) = + TestLanguageFrontend(ScopeManager(), ".").build { + translationResult(config) { + translationUnit("compoundoperator.cpp") { + // The main method + function("somefunc") { + body { + declare { variable("i", t("int")) { literal(0, t("int")) } } + ref("i") += literal(0, t("int")) + returnStmt { isImplicit = true } + } + } + } + } + } + + fun getConditionalExpression( + config: TranslationConfiguration = + TranslationConfiguration.builder() + .defaultPasses() + .registerLanguage(TestLanguage(".")) + .build() + ) = + TestLanguageFrontend(ScopeManager(), ".").build { + translationResult(config) { + translationUnit("conditional_expression.cpp") { + // The main method + function("main", t("int")) { + body { + declare { variable("a", t("int")) { literal(0, t("int")) } } + declare { variable("b", t("int")) { literal(1, t("int")) } } + + ref("a") { + location = + PhysicalLocation( + URI("conditional_expression.cpp"), + Region(5, 3, 5, 4) + ) + } assign + { + conditional( + ref("a") { + location = + PhysicalLocation( + URI("conditional_expression.cpp"), + Region(5, 7, 5, 8) + ) + } eq + ref("b") { + location = + PhysicalLocation( + URI("conditional_expression.cpp"), + Region(5, 12, 5, 13) + ) + }, + ref("b") { + location = + PhysicalLocation( + URI("conditional_expression.cpp"), + Region(5, 16, 5, 17) + ) + } assign literal(2, t("int")), + ref("b") { + location = + PhysicalLocation( + URI("conditional_expression.cpp"), + Region(5, 23, 5, 24) + ) + } assign literal(3, t("int")) + ) + } + ref("a") { + location = + PhysicalLocation( + URI("conditional_expression.cpp"), + Region(6, 3, 6, 4) + ) + } assign + ref("b") { + location = + PhysicalLocation( + URI("conditional_expression.cpp"), + Region(6, 7, 6, 8) + ) + } + returnStmt { isImplicit = true } + } + } + } + } + } + + fun getBasicSlice( + config: TranslationConfiguration = + TranslationConfiguration.builder() + .defaultPasses() + .registerLanguage(TestLanguage(".")) + .build() + ) = + TestLanguageFrontend(ScopeManager(), ".").build { + translationResult(config) { + translationUnit("BasicSlice.java") { + record("BasicSlice") { + // The main method + method("main") { + this.isStatic = true + param("args", t("String[]")) + body { + declare { variable("a", t("int")) { literal(0, t("int")) } } + declare { + variable("b", t("int")) { literal(1, t("int")) } + variable("c", t("int")) { literal(0, t("int")) } + variable("d", t("int")) { literal(0, t("int")) } + } + declare { + variable("sunShines", t("boolean")) { + literal(true, t("boolean")) + } + } + + ifStmt { + condition { ref("a") gt literal(0, t("int")) } + thenStmt { + ref("d") assign literal(5, t("int")) + ref("c") assign literal(2, t("int")) + ifStmt { + condition { ref("b") gt literal(0, t("int")) } + thenStmt { + ref("d") assign ref("a") * literal(2, t("int")) + ref("a") assign + ref("a") + ref("d") * literal(2, t("int")) + } + elseIf { + condition { ref("b") lt literal(-2, t("int")) } + thenStmt { + ref("a") assign + ref("a") - literal(10, t("int")) + } + } + } + } + elseStmt { + ref("b") assign literal(-2, t("int")) + ref("d") assign literal(-2, t("int")) + ref("a").dec() + } + } + + ref("a") assign { ref("a") + ref("b") } + + switchStmt(ref("sunShines")) { + switchBody { + case( + ref("True") + ) // No idea why it was "True" and not "true". Bug? On + // purpose? I just keep it + ref("a") assign { ref("a") * literal(2, t("int")) } + ref("c") assign literal(-2, t("int")) + breakStmt() + case( + ref("False") + ) // No idea why it was "False" and not "false". Bug? On + // purpose? I just keep it + ref("a") assign literal(290, t("int")) + ref("d") assign literal(-2, t("int")) + ref("b") assign literal(-2, t("int")) + breakStmt() + } + } + + returnStmt { isImplicit = true } + } + } + } + } + } + } + + fun getControlFlowSensitiveDFGIfMerge( + config: TranslationConfiguration = + TranslationConfiguration.builder() + .defaultPasses() + .registerLanguage(TestLanguage(".")) + .build() + ) = + TestLanguageFrontend(ScopeManager(), ".").build { + translationResult(config) { + translationUnit("ControlFlowSensitiveDFGIfMerge.java") { + record("ControlFlowSensitiveDFGIfMerge") { + field("bla", t("int")) {} + constructor() { + isImplicit = true + receiver = + newVariableDeclaration( + "this", + t("ControlFlowSensitiveDFGIfMerge") + ) + body { returnStmt { isImplicit = true } } + } + method("func") { + receiver = + newVariableDeclaration( + "this", + t("ControlFlowSensitiveDFGIfMerge") + ) + param("args", t("int[]")) + body { + declare { variable("a", t("int")) { literal(1, t("int")) } } + ifStmt { + condition { + member("length", ref("args")) gt literal(3, t("int")) + } + thenStmt { ref("a") assign literal(2, t("int")) } + elseStmt { + memberCall( + "println", + member( + "out", + ref("System") { isStaticAccess = true } + ) + ) { + ref("a") + } + } + } + + declare { variable("b", t("int")) { ref("a") } } + returnStmt { isImplicit = true } + } + } + + // The main method + method("main") { + this.isStatic = true + param("args", t("String[]")) + body { + declare { + variable("obj", t("ControlFlowSensitiveDFGIfMerge")) { + new { construct("ControlFlowSensitiveDFGIfMerge") } + } + } + member("bla", ref("obj")) assign literal(3, t("int")) + returnStmt { isImplicit = true } + } + } + } + } + } + } + + fun getControlFlowSesitiveDFGSwitch( + config: TranslationConfiguration = + TranslationConfiguration.builder() + .defaultPasses() + .registerLanguage(TestLanguage(".")) + .build() + ) = + TestLanguageFrontend(ScopeManager(), ".").build { + translationResult(config) { + translationUnit("ControlFlowSesitiveDFGSwitch.java") { + record("ControlFlowSesitiveDFGSwitch") { + // The main method + method("func3") { + receiver = + newVariableDeclaration( + "this", + t("ControlFlowSesitiveDFGSwitch") + ) + body { + declare { + variable("switchVal", t("int")) { literal(3, t("int")) } + } + declare { variable("a", t("int")) { literal(0, t("int")) } } + switchStmt(ref("switchVal")) { + switchBody { + case(literal(1, t("int"))) + ref("a") { + location = + PhysicalLocation( + URI("ControlFlowSesitiveDFGSwitch.java"), + Region(8, 9, 8, 10) + ) + } assign literal(10, t("int")) + breakStmt() + case(literal(2, t("int"))) + ref("a") { + location = + PhysicalLocation( + URI("ControlFlowSesitiveDFGSwitch.java"), + Region(11, 9, 11, 10) + ) + } assign literal(11, t("int")) + breakStmt() + case(literal(3, t("int"))) + ref("a") { + location = + PhysicalLocation( + URI("ControlFlowSesitiveDFGSwitch.java"), + Region(14, 9, 14, 10) + ) + } assign literal(12, t("int")) + default() + memberCall( + "println", + member( + "out", + ref("System") { isStaticAccess = true } + ) + ) { + ref("a") + } + breakStmt() + } + } + + declare { variable("b", t("int")) { ref("a") } } + returnStmt { isImplicit = true } + } + } + } + } + } + } + + fun getControlFlowSensitiveDFGIfNoMerge( + config: TranslationConfiguration = + TranslationConfiguration.builder() + .defaultPasses() + .registerLanguage(TestLanguage(".")) + .build() + ) = + TestLanguageFrontend(ScopeManager(), ".").build { + translationResult(config) { + translationUnit("ControlFlowSensitiveDFGIfNoMerge.java") { + record("ControlFlowSensitiveDFGIfNoMerge") { + // The main method + method("func2") { + receiver = + newVariableDeclaration( + "this", + t("ControlFlowSensitiveDFGIfNoMerge") + ) + body { + declare { variable("a", t("int")) { literal(1, t("int")) } } + ifStmt { + condition { + member("length", ref("args")) gt literal(3, t("int")) + } + thenStmt { ref("a") assign literal(2, t("int")) } + elseStmt { + ref("a") assign literal(4, t("int")) + declare { variable("b", t("int")) { ref("a") } } + } + } + returnStmt { isImplicit = true } + } + } + } + } + } + } + + fun getLabeledBreakContinueLoopDFG( + config: TranslationConfiguration = + TranslationConfiguration.builder() + .defaultPasses() + .registerLanguage(TestLanguage(".")) + .build() + ) = + TestLanguageFrontend(ScopeManager(), ".").build { + translationResult(config) { + translationUnit("LoopDFGs.java") { + record("LoopDFGs") { + // The main method + method("labeledBreakContinue") { + receiver = newVariableDeclaration("this", t("LoopDFGs")) + param("param", t("int")) + body { + declare { variable("a", t("int")) { literal(0, t("int")) } } + label("lab1") { + whileStmt { + whileCondition { ref("param") lt literal(5, t("int")) } + loopBody { + whileStmt { + whileCondition { + ref("param") gt literal(6, t("int")) + } + loopBody { + ifStmt { + condition { + ref("param") gt literal(7, t("int")) + } + thenStmt { + ref("a") assign literal(1, t("int")) + continueStmt("lab1") + } + elseStmt { + memberCall( + "println", + member( + "out", + ref("System") { + isStaticAccess = true + } + ) + ) { + ref("a") + } + ref("a") assign literal(2, t("int")) + breakStmt("lab1") + } + } + ref("a") assign literal(4, t("int")) + } + } + memberCall( + "println", + member( + "out", + ref("System") { isStaticAccess = true } + ) + ) { + ref("a") + } + ref("a") assign literal(3, t("int")) + } + } + } + + memberCall( + "println", + member("out", ref("System") { isStaticAccess = true }) + ) { + ref("a") + } + returnStmt { isImplicit = true } + } + } + } + } + } + } + + fun getLoopingDFG( + config: TranslationConfiguration = + TranslationConfiguration.builder() + .defaultPasses() + .registerLanguage(TestLanguage(".")) + .build() + ) = + TestLanguageFrontend(ScopeManager(), ".").build { + translationResult(config) { + translationUnit("LoopDFGs.java") { + record("LoopDFGs") { + // The main method + method("looping") { + receiver = newVariableDeclaration("this", t("LoopDFGs")) + param("param", t("int")) + body { + declare { variable("a", t("int")) { literal(0, t("int")) } } + whileStmt { + whileCondition { + (ref("param") % literal(6, t("int"))) eq + literal(5, t("int")) + } + loopBody { + ifStmt { + condition { ref("param") gt literal(7, t("int")) } + thenStmt { ref("a") assign literal(1, t("int")) } + elseStmt { + memberCall( + "println", + member( + "out", + ref("System") { isStaticAccess = true } + ) + ) { + ref("a") + } + ref("a") assign literal(2, t("int")) + } + } + } + } + + ref("a") assign { literal(3, t("int")) } + returnStmt { isImplicit = true } + } + } + } + } + } + } + + fun getDelayedAssignmentAfterRHS( + config: TranslationConfiguration = + TranslationConfiguration.builder() + .defaultPasses() + .registerLanguage(TestLanguage(".")) + .build() + ) = + TestLanguageFrontend(ScopeManager(), ".").build { + translationResult(config) { + translationUnit("DelayedAssignmentAfterRHS.java") { + record("DelayedAssignmentAfterRHS") { + // The main method + method("main") { + this.isStatic = true + param("args", t("String[]")) + body { + declare { variable("a", t("int")) { literal(0, t("int")) } } + declare { variable("b", t("int")) { literal(1, t("int")) } } + ref("a") assign { ref("a") + ref("b") } + } + } + } + } + } + } + + fun getReturnTest( + config: TranslationConfiguration = + TranslationConfiguration.builder() + .defaultPasses() + .registerLanguage(TestLanguage(".")) + .build() + ) = + TestLanguageFrontend(ScopeManager(), ".").build { + translationResult(config) { + translationUnit("ReturnTest.java") { + record("ReturnTest", "class") { + method("testReturn", t("int")) { + receiver = newVariableDeclaration("this", t("ReturnTest")) + body { + declare { variable("a", t("int")) { literal(1, t("int")) } } + ifStmt { + condition { ref("a") eq literal(5, t("int")) } + thenStmt { + returnStmt { + returnValue = literal(2, t("int")) + location = + PhysicalLocation( + URI("ReturnTest.java"), + Region(5, 13, 5, 21) + ) + } + } + elseStmt { + returnStmt { + returnValue = ref("a") + location = + PhysicalLocation( + URI("ReturnTest.java"), + Region(7, 13, 7, 21) + ) + } + } + } + returnStmt { isImplicit = true } + } + } + } + } + } + } + fun getVisitorTest( config: TranslationConfiguration = TranslationConfiguration.builder() @@ -123,6 +839,7 @@ class GraphExamples { // The main method method("main") { + this.isStatic = true param("args", t("String[]")) body { declare { @@ -218,6 +935,7 @@ class GraphExamples { // The main method method("main") { + this.isStatic = true param("args", t("int[]")) body { declare { diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/DFGTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/DFGTest.kt deleted file mode 100644 index 5b261cf7cb..0000000000 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/DFGTest.kt +++ /dev/null @@ -1,207 +0,0 @@ -/* - * Copyright (c) 2021, 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.enhancements - -import de.fraunhofer.aisec.cpg.TestUtils.analyze -import de.fraunhofer.aisec.cpg.TestUtils.findByUniqueName -import de.fraunhofer.aisec.cpg.graph.* -import de.fraunhofer.aisec.cpg.graph.statements.expressions.* -import java.nio.file.Path -import kotlin.test.* - -internal class DFGTest { - // Test DFG - // Test ControlFlowSensitiveDFGPass - /** - * To test assignments of different value in an expression that then has a joinPoint. a = a == b - * ? b = 2: b = 3; - * - * @throws Exception - */ - @Test - @Throws(Exception::class) - fun testConditionalExpression() { - val topLevel = Path.of("src", "test", "resources", "dfg") - val result = - analyze(listOf(topLevel.resolve("conditional_expression.cpp").toFile()), topLevel, true) - val bJoin = result.refs[{ it.name.localName == "b" && it.location?.region?.startLine == 6 }] - val a5 = result.refs[{ it.name.localName == "a" && it.location?.region?.startLine == 5 }] - val a6 = result.refs[{ it.name.localName == "a" && it.location?.region?.startLine == 6 }] - val bCond = - result.refs[ - { - it.name.localName == "b" && - it.location?.region?.startLine == 5 && - it.location?.region?.startColumn == 16 - }] - val b2 = - result.refs[ - { - it.name.localName == "b" && - it.location?.region?.startLine == 5 && - it.location?.region?.startColumn == 16 - }] - val b3 = - result.refs[ - { - it.name.localName == "b" && - it.location?.region?.startLine == 5 && - it.location?.region?.startColumn == 23 - }] - assertNotNull(bJoin) - assertNotNull(bCond) - assertNotNull(b2) - assertNotNull(b3) - assertNotNull(a5) - assertNotNull(a6) - - val val2 = result.literals[{ it.value == 2 }] - assertNotNull(val2) - - val val3 = result.literals[{ it.value == 3 }] - assertNotNull(val3) - - assertEquals(1, b2.prevDFG.size) - assertTrue(b2.prevDFG.contains(val2)) - assertEquals(1, b3.prevDFG.size) - assertTrue(b3.prevDFG.contains(val3)) - - // We want the ConditionalExpression - assertEquals(1, a5.prevDFG.size) - assertTrue(a5.prevDFG.first() is ConditionalExpression) - assertTrue(flattenDFGGraph(a5, false).contains(val2)) - assertTrue(flattenDFGGraph(a5, false).contains(val3)) - - assertEquals(1, a6.prevDFG.size) - assertTrue(a6.prevDFG.contains(bJoin)) - assertEquals(2, bJoin.prevDFG.size) - // The b which got assigned 2 flows to the b in line 6 - assertTrue(bJoin.prevDFG.contains(b2)) - // The b which got assigned 3 flows to the b in line 6 - assertTrue(bJoin.prevDFG.contains(b3)) - } - // Test DFG when ReadWrite access occurs, such as compound operators or unary operators - @Test - @Throws(Exception::class) - fun testCompoundOperatorDFG() { - val topLevel = Path.of("src", "test", "resources", "dfg") - val result = - analyze(listOf(topLevel.resolve("compoundoperator.cpp").toFile()), topLevel, true) - val rwCompoundOperator = findByUniqueName(result.allChildren(), "+=") - assertNotNull(rwCompoundOperator) - - val expression = findByUniqueName(result.refs, "i") - assertNotNull(expression) - - val prevDFGOperator = rwCompoundOperator.prevDFG - assertNotNull(prevDFGOperator) - assertTrue(prevDFGOperator.contains(expression)) - - val nextDFGOperator = rwCompoundOperator.nextDFG - assertNotNull(nextDFGOperator) - assertTrue(nextDFGOperator.contains(expression)) - } - - @Test - @Throws(Exception::class) - fun testUnaryOperatorDFG() { - val topLevel = Path.of("src", "test", "resources", "dfg") - val result = analyze(listOf(topLevel.resolve("unaryoperator.cpp").toFile()), topLevel, true) - val rwUnaryOperator = findByUniqueName(result.allChildren(), "++") - assertNotNull(rwUnaryOperator) - - val expression = findByUniqueName(result.refs, "i") - assertNotNull(expression) - - val prevDFGOperator: Set = rwUnaryOperator.prevDFG - val nextDFGOperator: Set = rwUnaryOperator.nextDFG - assertTrue(prevDFGOperator.contains(expression)) - assertTrue(nextDFGOperator.contains(expression)) - } - - /** - * Gets Integer Literal from the List of nodes to simplify the test syntax. The Literal is - * expected to be contained in the list and the function will throw an - * [IndexOutOfBoundsException] otherwise. - * - * @param nodes - * - The list of nodes to filter for the Literal. - * - * @param v - * - The integer value expected from the Literal. - * - * @return The Literal with the specified value. - */ - private fun getLiteral(nodes: List, v: Int): Literal<*> { - return nodes.filter { n: Node? -> n is Literal<*> && n.value == Integer.valueOf(v) }[0] - as Literal<*> - } - - /** - * Traverses the DFG Graph induced by the provided node in the specified direction and retrieves - * all nodes that are passed by and are therefore part of the incoming or outgoing data-flow. - * - * @param node - * - The node that induces the DFG-subgraph for which nodes are retrieved - * - * @param outgoing - * - true if the Data-Flow from this node should be considered, false if the data-flow is to - * this node. - * - * @return A set of nodes that are part of the data-flow - */ - private fun flattenDFGGraph(node: Node?, outgoing: Boolean): Set { - if (node == null) { - return setOf() - } - - val dfgNodes = mutableSetOf() - - dfgNodes.add(node) - val worklist = LinkedHashSet() - worklist.add(node) - - while (worklist.isNotEmpty()) { - val toProcess = worklist.iterator().next() - worklist.remove(toProcess) - // DataFlow direction - val nextDFGNodes = - if (outgoing) { - toProcess.nextDFG - } else { - toProcess.prevDFG - } - // Adding all NEWLY discovered df-nodes to the work-list. - for (dfgNode in nextDFGNodes) { - if (!dfgNodes.contains(dfgNode)) { - worklist.add(dfgNode) - dfgNodes.add(dfgNode) - } - } - } - return dfgNodes - } -} diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/EOGTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/EOGTest.kt index c3f70f3a47..db2020ec57 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/EOGTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/EOGTest.kt @@ -46,7 +46,6 @@ import de.fraunhofer.aisec.cpg.processing.IVisitor import de.fraunhofer.aisec.cpg.processing.strategy.Strategy import de.fraunhofer.aisec.cpg.sarif.PhysicalLocation import java.io.File -import java.util.function.Consumer import java.util.stream.Collectors import java.util.stream.Stream import kotlin.test.* @@ -82,18 +81,11 @@ internal class EOGTest : BaseTest() { } val ifs = nodes.filterIsInstance() assertEquals(2, ifs.size) - ifs.forEach(Consumer { ifnode: IfStatement -> assertNotNull(ifnode.thenStatement) }) - assertTrue( - ifs.stream().anyMatch { node: IfStatement -> node.elseStatement == null } && - ifs.stream().anyMatch { node: IfStatement -> node.elseStatement != null } - ) + ifs.forEach { assertNotNull(it.thenStatement) } + assertTrue(ifs.any { it.elseStatement == null } && ifs.any { it.elseStatement != null }) val ifSimple = ifs[0] val ifBranched = ifs[1] - val prints = - nodes - .stream() - .filter { node: Node -> node.code == refNodeString } - .collect(Collectors.toList()) + val prints = nodes.filter { it.code == refNodeString } val ifEOG = SubgraphWalker.getEOGPathEdges(ifSimple) var conditionEOG = SubgraphWalker.getEOGPathEdges(ifSimple.condition) var thenEOG = SubgraphWalker.getEOGPathEdges(ifSimple.thenStatement) @@ -235,11 +227,7 @@ internal class EOGTest : BaseTest() { @Throws(Exception::class) fun testCPPFor() { val nodes = translateToNodes("src/test/resources/cfg/forloop.cpp") - val prints = - nodes - .stream() - .filter { node: Node -> node.code == REFNODESTRINGCXX } - .collect(Collectors.toList()) + val prints = nodes.filter { it.code == REFNODESTRINGCXX } val fstat = nodes.filterIsInstance() var fs = fstat[0] assertTrue( @@ -474,12 +462,8 @@ internal class EOGTest : BaseTest() { @Throws(Exception::class) fun testLoops(relPath: String, refNodeString: String?) { val nodes = translateToNodes(relPath) - val prints = - nodes - .stream() - .filter { node: Node -> node.code == refNodeString } - .collect(Collectors.toList()) - assertEquals(1, nodes.stream().filter { node: Node? -> node is WhileStatement }.count()) + val prints = nodes.filter { it.code == refNodeString } + assertEquals(1, nodes.filterIsInstance().count()) val wstat = nodes.filterIsInstance().firstOrNull() assertNotNull(wstat) @@ -491,11 +475,11 @@ internal class EOGTest : BaseTest() { nodes[0].accept( Strategy::EOG_BACKWARD, object : IVisitor() { - override fun visit(n: Node) { + override fun visit(t: Node) { println( - PhysicalLocation.locationLink(n.location) + + PhysicalLocation.locationLink(t.location) + " -> " + - PhysicalLocation.locationLink(n.location) + PhysicalLocation.locationLink(t.location) ) } } @@ -804,12 +788,8 @@ internal class EOGTest : BaseTest() { @Throws(Exception::class) fun testBreakContinue(relPath: String, refNodeString: String) { val nodes = translateToNodes(relPath) - val prints = - nodes - .stream() - .filter { node: Node -> node.code == refNodeString } - .collect(Collectors.toList()) - assertEquals(1, nodes.stream().filter { node: Node? -> node is WhileStatement }.count()) + val prints = nodes.filter { it.code == refNodeString } + assertEquals(1, nodes.filterIsInstance().count()) val breaks = nodes.filterIsInstance() val continues = nodes.filterIsInstance() val wstat = nodes.filterIsInstance().firstOrNull() @@ -1030,10 +1010,8 @@ internal class EOGTest : BaseTest() { val tu = analyzeAndGetFirstTU(listOf(toTranslate), topLevel, true) var nodes = SubgraphWalker.flattenAST(tu) // TODO: until explicitly added Return Statements are either removed again or code and - // region - // set properly - nodes = - nodes.stream().filter { node: Node -> node.code != null }.collect(Collectors.toList()) + // region set properly + nodes = nodes.filter { it.code != null } return nodes } diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/InferenceTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/InferenceTest.kt index 4360bd8320..be5f5f8ada 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/InferenceTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/InferenceTest.kt @@ -25,13 +25,11 @@ */ package de.fraunhofer.aisec.cpg.enhancements -import de.fraunhofer.aisec.cpg.InferenceConfiguration -import de.fraunhofer.aisec.cpg.TestUtils.analyzeAndGetFirstTU +import de.fraunhofer.aisec.cpg.GraphExamples import de.fraunhofer.aisec.cpg.assertLocalName import de.fraunhofer.aisec.cpg.graph.byNameOrNull import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration import de.fraunhofer.aisec.cpg.graph.get -import java.io.File import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertNotNull @@ -40,13 +38,12 @@ class InferenceTest { @Test fun testRecordInference() { - val file = File("src/test/resources/inference/record.cpp") val tu = - analyzeAndGetFirstTU(listOf(file), file.parentFile.toPath(), true) { - it.inferenceConfiguration( - InferenceConfiguration.builder().inferRecords(true).build() - ) - } + GraphExamples.getInferenceRecord() + .components + .firstOrNull() + ?.translationUnits + ?.firstOrNull() assertNotNull(tu) @@ -69,13 +66,12 @@ class InferenceTest { @Test fun testRecordInferencePointer() { - val file = File("src/test/resources/inference/record_ptr.cpp") val tu = - analyzeAndGetFirstTU(listOf(file), file.parentFile.toPath(), true) { - it.inferenceConfiguration( - InferenceConfiguration.builder().inferRecords(true).build() - ) - } + GraphExamples.getInferenceRecordPtr() + .components + .firstOrNull() + ?.translationUnits + ?.firstOrNull() assertNotNull(tu) diff --git a/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/DFGTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/DFGTest.kt similarity index 64% rename from cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/DFGTest.kt rename to cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/DFGTest.kt index bdbb89dbae..f96265da3e 100644 --- a/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/DFGTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/DFGTest.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, Fraunhofer AISEC. All rights reserved. + * Copyright (c) 2023, 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. @@ -23,169 +23,92 @@ * \______/ \__| \______/ * */ -package de.fraunhofer.aisec.cpg.enhancements +package de.fraunhofer.aisec.cpg.passes +import de.fraunhofer.aisec.cpg.GraphExamples import de.fraunhofer.aisec.cpg.TestUtils -import de.fraunhofer.aisec.cpg.TestUtils.analyze -import de.fraunhofer.aisec.cpg.TestUtils.compareLineFromLocationIfExists -import de.fraunhofer.aisec.cpg.TestUtils.findByUniqueName -import de.fraunhofer.aisec.cpg.frontends.java.JavaLanguage import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration +import de.fraunhofer.aisec.cpg.graph.statements.ReturnStatement import de.fraunhofer.aisec.cpg.graph.statements.expressions.* import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker -import java.nio.file.Path import kotlin.test.* -internal class DFGTest { - // Test DFG - // Test ControlFlowSensitiveDFGPass - @Test - @Throws(Exception::class) - fun testControlSensitiveDFGPassIfMerge() { - val topLevel = Path.of("src", "test", "resources", "dfg") - val result = - analyze( - listOf(topLevel.resolve("ControlFlowSensitiveDFGIfMerge.java").toFile()), - topLevel, - true - ) { - it.registerLanguage(JavaLanguage()) - } - - // Test If-Block - val literal2 = result.literals[{ it.value == 2 }] - assertNotNull(literal2) - - val b = result.variables["b"] - assertNotNull(b) - - val a2 = result.refs[{ it.access == AccessValues.WRITE }] - assertNotNull(a2) - assertTrue(literal2.nextDFG.contains(a2)) - assertEquals( - 1, - a2.nextDFG.size - ) // Outgoing DFG Edges only to the DeclaredReferenceExpression in the assignment to b - assertEquals( - b.initializer!!, - a2.nextDFG.first(), - ) - - val refersTo = a2.getRefersToAs(VariableDeclaration::class.java) - assertNotNull(refersTo) - assertEquals(2, refersTo.nextDFG.size) // The print and assignment to b - // Outgoing DFG Edge to the DeclaredReferenceExpression in the assignment of b - assertTrue(refersTo.nextDFG.contains(b.initializer!!)) - - // Test Else-Block with System.out.println() - val literal1 = result.literals[{ it.value == 1 }] - assertNotNull(literal1) - val println = result.calls["println"] - assertNotNull(println) - val aPrintln = println.arguments[0] - assertTrue(refersTo.nextDFG.contains(aPrintln)) - - assertEquals(1, aPrintln.prevDFG.size) - assertEquals(refersTo, aPrintln.prevDFG.first()) - assertEquals(1, aPrintln.nextEOG.size) - assertEquals(println, aPrintln.nextEOG[0]) - - val ab = b.prevEOG[0] as DeclaredReferenceExpression - assertTrue(refersTo.nextDFG.contains(ab)) - assertTrue(a2.nextDFG.contains(ab)) - } +class DFGTest { + // Test DFGPass and ControlFlowSensitiveDFGPass /** - * Tests the ControlFlowSensitiveDFGPass and checks if an assignment located within one block - * clears the values from the map and includes only the new (assigned) value. + * To test assignments of different value in an expression that then has a joinPoint. a = a == b + * ? b = 2: b = 3; * - * @throws Exception Any exception that happens during the analysis process + * @throws Exception */ @Test @Throws(Exception::class) - fun testControlSensitiveDFGPassIfNoMerge() { - val topLevel = Path.of("src", "test", "resources", "dfg") - val result = - analyze( - listOf(topLevel.resolve("ControlFlowSensitiveDFGIfNoMerge.java").toFile()), - topLevel, - true - ) { - it.registerLanguage(JavaLanguage()) - } - - val b = result.variables["b"] - assertNotNull(b) - - val ab = b.prevEOG[0] as DeclaredReferenceExpression - val literal4 = result.literals[{ it.value == 4 }] - assertNotNull(literal4) - val a4 = ab.prevDFG.first { it is DeclaredReferenceExpression } - assertTrue(literal4.nextDFG.contains(a4)) - assertEquals(1, ab.prevDFG.size) - } - - @Test - @Throws(Exception::class) - fun testControlSensitiveDFGPassSwitch() { - val topLevel = Path.of("src", "test", "resources", "dfg") - val result = - analyze( - listOf(topLevel.resolve("ControlFlowSensitiveDFGSwitch.java").toFile()), - topLevel, - true - ) { - it.registerLanguage(JavaLanguage()) - } - - val a = result.variables["a"] - assertNotNull(a) - - val b = result.variables["b"] - assertNotNull(b) - - val ab = b.prevEOG[0] as DeclaredReferenceExpression - val a10 = result.refs[{ compareLineFromLocationIfExists(it, true, 8) }] - val a11 = result.refs[{ compareLineFromLocationIfExists(it, true, 11) }] - val a12 = result.refs[{ compareLineFromLocationIfExists(it, true, 14) }] - assertNotNull(a10) - assertNotNull(a11) - assertNotNull(a12) - - val literal0 = result.literals[{ it.value == 0 }] - val literal10 = result.literals[{ it.value == 10 }] - val literal11 = result.literals[{ it.value == 11 }] - val literal12 = result.literals[{ it.value == 12 }] - assertNotNull(literal0) - assertNotNull(literal10) - assertNotNull(literal11) - assertNotNull(literal12) - - assertEquals(1, literal10.nextDFG.size) - assertTrue(literal10.nextDFG.contains(a10)) - assertEquals(1, literal11.nextDFG.size) - assertTrue(literal11.nextDFG.contains(a11)) - assertEquals(1, literal12.nextDFG.size) - assertTrue(literal12.nextDFG.contains(a12)) - assertEquals(1, a.prevDFG.size) - assertTrue(a.prevDFG.contains(literal0)) - assertFalse(a.prevDFG.contains(a10)) - assertFalse(a.prevDFG.contains(a11)) - assertFalse(a.prevDFG.contains(a12)) - - assertEquals(1, ab.nextDFG.size) - assertTrue(ab.nextDFG.contains(b)) - - // Fallthrough test - val println = result.calls["println"] - assertNotNull(println) - - val aPrintln = result.refs[{ it.nextEOG.contains(println) }] - assertNotNull(aPrintln) - assertEquals(2, aPrintln.prevDFG.size) - assertTrue(aPrintln.prevDFG.contains(a)) - assertTrue(aPrintln.prevDFG.contains(a12)) + fun testConditionalExpression() { + val result = GraphExamples.getConditionalExpression() + + val bJoin = result.refs[{ it.name.localName == "b" && it.location?.region?.startLine == 6 }] + val a5 = + result.refs[ + { + it.name.localName == "a" && + it.location?.region?.startLine == 5 && + it.location?.region?.startColumn == 3 + }] + val a6 = result.refs[{ it.name.localName == "a" && it.location?.region?.startLine == 6 }] + val bCond = + result.refs[ + { + it.name.localName == "b" && + it.location?.region?.startLine == 5 && + it.location?.region?.startColumn == 12 + }] + val b2 = + result.refs[ + { + it.name.localName == "b" && + it.location?.region?.startLine == 5 && + it.location?.region?.startColumn == 16 + }] + val b3 = + result.refs[ + { + it.name.localName == "b" && + it.location?.region?.startLine == 5 && + it.location?.region?.startColumn == 23 + }] + assertNotNull(bJoin) + assertNotNull(bCond) + assertNotNull(b2) + assertNotNull(b3) + assertNotNull(a5) + assertNotNull(a6) + + val val2 = result.literals[{ it.value == 2 }] + assertNotNull(val2) + + val val3 = result.literals[{ it.value == 3 }] + assertNotNull(val3) + + assertEquals(1, b2.prevDFG.size) + assertTrue(b2.prevDFG.contains(val2)) + assertEquals(1, b3.prevDFG.size) + assertTrue(b3.prevDFG.contains(val3)) + + // We want the ConditionalExpression + assertEquals(1, a5.prevDFG.size) + assertTrue(a5.prevDFG.first() is ConditionalExpression) + assertTrue(flattenDFGGraph(a5, false).contains(val2)) + assertTrue(flattenDFGGraph(a5, false).contains(val3)) + + assertEquals(1, a6.prevDFG.size) + assertTrue(a6.prevDFG.contains(bJoin)) + assertEquals(2, bJoin.prevDFG.size) + // The b which got assigned 2 flows to the b in line 6 + assertTrue(bJoin.prevDFG.contains(b2)) + // The b which got assigned 3 flows to the b in line 6 + assertTrue(bJoin.prevDFG.contains(b3)) } /** @@ -199,31 +122,25 @@ internal class DFGTest { @Test @Throws(Exception::class) fun testDelayedAssignment() { - val topLevel = Path.of("src", "test", "resources", "dfg") - val result = - analyze( - listOf(topLevel.resolve("DelayedAssignmentAfterRHS.java").toFile()), - topLevel, - true - ) { - it.registerLanguage(JavaLanguage()) - } + val result = GraphExamples.getDelayedAssignmentAfterRHS() - val binaryOperatorAssignment = findByUniqueName(result.allChildren(), "=") + val binaryOperatorAssignment = + TestUtils.findByUniqueName(result.allChildren(), "=") assertNotNull(binaryOperatorAssignment) - val binaryOperatorAddition = findByUniqueName(result.allChildren(), "+") + val binaryOperatorAddition = + TestUtils.findByUniqueName(result.allChildren(), "+") assertNotNull(binaryOperatorAddition) - val varA = findByUniqueName(result.variables, "a") + val varA = TestUtils.findByUniqueName(result.variables, "a") assertNotNull(varA) - val varB = findByUniqueName(result.variables, "b") + val varB = TestUtils.findByUniqueName(result.variables, "b") assertNotNull(varB) val lhsA = binaryOperatorAssignment.lhs as DeclaredReferenceExpression val rhsA = binaryOperatorAddition.lhs as DeclaredReferenceExpression - val b = findByUniqueName(result.refs, "b") + val b = TestUtils.findByUniqueName(result.refs, "b") assertNotNull(b) val literal0 = result.literals[{ it.value == 0 }] @@ -257,50 +174,42 @@ internal class DFGTest { assertTrue(binaryOperatorAddition.nextDFG.contains(lhsA)) } - /** - * Tests that the outgoing DFG edges from a VariableDeclaration go to references with a path - * without a new assignment to the variable. - * - * @throws Exception - */ + /** Test DFG when ReadWrite access occurs, such as compound operators or unary operators. */ @Test @Throws(Exception::class) - fun testOutgoingDFGFromVariableDeclaration() { - val topLevel = Path.of("src", "test", "resources", "dfg") - val result = - analyze(listOf(topLevel.resolve("BasicSlice.java").toFile()), topLevel, true) { - it.registerLanguage(JavaLanguage()) - } - val varA = findByUniqueName(result.variables, "a") - assertNotNull(varA) - // The variable can flow to lines 19, 23, 24, 26, 31, 34 without modifications. - assertEquals(6, varA.nextDFG.size) - assertEquals(1, varA.prevDFG.size) // Only the initializer should flow there. + fun testCompoundOperatorDFG() { + val result = GraphExamples.getCompoundOperator() + + val rwCompoundOperator = TestUtils.findByUniqueName(result.allChildren(), "+=") + assertNotNull(rwCompoundOperator) + + val expression = TestUtils.findByUniqueName(result.refs, "i") + assertNotNull(expression) + + val prevDFGOperator = rwCompoundOperator.prevDFG + assertNotNull(prevDFGOperator) + assertTrue(prevDFGOperator.contains(expression)) + + val nextDFGOperator = rwCompoundOperator.nextDFG + assertNotNull(nextDFGOperator) + assertTrue(nextDFGOperator.contains(expression)) } @Test @Throws(Exception::class) - fun testSensitivityThroughLoop() { - val topLevel = Path.of("src", "test", "resources", "dfg") - val result = - analyze(listOf(topLevel.resolve("LoopDFGs.java").toFile()), topLevel, true) { - it.registerLanguage(JavaLanguage()) - } - val looping = result.methods["looping"] - val methodNodes = SubgraphWalker.flattenAST(looping) - val l0 = getLiteral(methodNodes, 0) - val l1 = getLiteral(methodNodes, 1) - val l2 = getLiteral(methodNodes, 2) - val l3 = getLiteral(methodNodes, 3) - val calls = - SubgraphWalker.flattenAST(looping).filter { n: Node -> - n is CallExpression && n.name.localName == "println" - } - val dfgNodes = flattenDFGGraph(calls[0].refs["a"], false) - assertTrue(dfgNodes.contains(l0)) - assertTrue(dfgNodes.contains(l1)) - assertTrue(dfgNodes.contains(l2)) - assertFalse(dfgNodes.contains(l3)) + fun testUnaryOperatorDFG() { + val result = GraphExamples.getUnaryOperator() + + val rwUnaryOperator = TestUtils.findByUniqueName(result.allChildren(), "++") + assertNotNull(rwUnaryOperator) + + val expression = TestUtils.findByUniqueName(result.refs, "i") + assertNotNull(expression) + + val prevDFGOperator: Set = rwUnaryOperator.prevDFG + val nextDFGOperator: Set = rwUnaryOperator.nextDFG + assertTrue(prevDFGOperator.contains(expression)) + assertTrue(nextDFGOperator.contains(expression)) } /** @@ -321,48 +230,6 @@ internal class DFGTest { as Literal<*> } - @Test - @Throws(Exception::class) - fun testSensitivityWithLabels() { - val topLevel = Path.of("src", "test", "resources", "dfg") - val tu = - TestUtils.analyzeAndGetFirstTU( - listOf(topLevel.resolve("LoopDFGs.java").toFile()), - topLevel, - true - ) { - it.registerLanguage(JavaLanguage()) - } - val looping = tu.methods["labeledBreakContinue"] - val methodNodes = SubgraphWalker.flattenAST(looping) - val l0 = getLiteral(methodNodes, 0) - val l1 = getLiteral(methodNodes, 1) - val l2 = getLiteral(methodNodes, 2) - val l3 = getLiteral(methodNodes, 3) - val l4 = getLiteral(methodNodes, 4) - val calls = - SubgraphWalker.flattenAST(looping) - .filter { n: Node -> n is CallExpression && n.name.localName == "println" } - .toMutableList() - val dfgNodesA0 = flattenDFGGraph(calls[0].refs["a"], false) - val dfgNodesA1 = flattenDFGGraph(calls[1].refs["a"], false) - val dfgNodesA2 = flattenDFGGraph(calls[2].refs["a"], false) - assertEquals(3, calls[0].refs["a"]?.prevDFG?.size) - assertTrue(dfgNodesA0.contains(l0)) - assertTrue(dfgNodesA0.contains(l1)) - assertTrue(dfgNodesA0.contains(l3)) - assertFalse(dfgNodesA0.contains(l4)) - assertTrue(dfgNodesA1.contains(l0)) - assertTrue(dfgNodesA1.contains(l1)) - assertTrue(dfgNodesA1.contains(l3)) - assertFalse(dfgNodesA1.contains(l4)) - assertTrue(dfgNodesA2.contains(l0)) - assertTrue(dfgNodesA2.contains(l1)) - assertTrue(dfgNodesA2.contains(l2)) - assertTrue(dfgNodesA2.contains(l3)) - assertFalse(dfgNodesA2.contains(l4)) - } - /** * Traverses the DFG Graph induced by the provided node in the specified direction and retrieves * all nodes that are passed by and are therefore part of the incoming or outgoing data-flow. @@ -407,4 +274,227 @@ internal class DFGTest { } return dfgNodes } + + /** + * Tests if the last artificial (implicit) return statement is removed by the + * [ControlFlowSensitiveDFGPass]. + */ + @Test + fun testReturnStatement() { + val result = GraphExamples.getReturnTest() + + val returnFunction = result.functions["testReturn"] + assertNotNull(returnFunction) + + assertEquals(2, returnFunction.prevDFG.size) + + val allRealReturns = returnFunction.allChildren { it.location != null } + assertEquals(allRealReturns.toSet() as Set, returnFunction.prevDFG) + + assertEquals(1, allRealReturns[0].prevDFG.size) + assertTrue(returnFunction.literals.first { it.value == 2 } in allRealReturns[0].prevDFG) + assertEquals(1, allRealReturns[1].prevDFG.size) + assertTrue( + returnFunction.refs.last { it.name.localName == "a" } in allRealReturns[1].prevDFG + ) + } + + @Test + @Throws(Exception::class) + fun testSensitivityThroughLoop() { + val result = GraphExamples.getLoopingDFG() + val looping = result.methods["looping"] + val methodNodes = SubgraphWalker.flattenAST(looping) + val l0 = getLiteral(methodNodes, 0) + val l1 = getLiteral(methodNodes, 1) + val l2 = getLiteral(methodNodes, 2) + val l3 = getLiteral(methodNodes, 3) + val calls = + SubgraphWalker.flattenAST(looping).filter { n: Node -> + n is CallExpression && n.name.localName == "println" + } + val dfgNodes = flattenDFGGraph(calls[0].refs["a"], false) + assertTrue(dfgNodes.contains(l0)) + assertTrue(dfgNodes.contains(l1)) + assertTrue(dfgNodes.contains(l2)) + assertFalse(dfgNodes.contains(l3)) + } + + @Test + @Throws(Exception::class) + fun testSensitivityWithLabels() { + val result = GraphExamples.getLabeledBreakContinueLoopDFG() + val looping = result.methods["labeledBreakContinue"] + val methodNodes = SubgraphWalker.flattenAST(looping) + val l0 = getLiteral(methodNodes, 0) + val l1 = getLiteral(methodNodes, 1) + val l2 = getLiteral(methodNodes, 2) + val l3 = getLiteral(methodNodes, 3) + val l4 = getLiteral(methodNodes, 4) + val calls = + SubgraphWalker.flattenAST(looping) + .filter { n: Node -> n is CallExpression && n.name.localName == "println" } + .toMutableList() + val dfgNodesA0 = flattenDFGGraph(calls[0].refs["a"], false) + val dfgNodesA1 = flattenDFGGraph(calls[1].refs["a"], false) + val dfgNodesA2 = flattenDFGGraph(calls[2].refs["a"], false) + assertEquals(3, calls[0].refs["a"]?.prevDFG?.size) + assertTrue(dfgNodesA0.contains(l0)) + assertTrue(dfgNodesA0.contains(l1)) + assertTrue(dfgNodesA0.contains(l3)) + assertFalse(dfgNodesA0.contains(l4)) + assertTrue(dfgNodesA1.contains(l0)) + assertTrue(dfgNodesA1.contains(l1)) + assertTrue(dfgNodesA1.contains(l3)) + assertFalse(dfgNodesA1.contains(l4)) + assertTrue(dfgNodesA2.contains(l0)) + assertTrue(dfgNodesA2.contains(l1)) + assertTrue(dfgNodesA2.contains(l2)) + assertTrue(dfgNodesA2.contains(l3)) + assertFalse(dfgNodesA2.contains(l4)) + } + + /** + * Tests the ControlFlowSensitiveDFGPass and checks if an assignment located within one block + * clears the values from the map and includes only the new (assigned) value. + * + * @throws Exception Any exception that happens during the analysis process + */ + @Test + @Throws(Exception::class) + fun testControlSensitiveDFGPassIfNoMerge() { + val result = GraphExamples.getControlFlowSensitiveDFGIfNoMerge() + + val b = result.variables["b"] + assertNotNull(b) + + val ab = b.prevEOG[0] as DeclaredReferenceExpression + val literal4 = result.literals[{ it.value == 4 }] + assertNotNull(literal4) + val a4 = ab.prevDFG.first { it is DeclaredReferenceExpression } + assertTrue(literal4.nextDFG.contains(a4)) + assertEquals(1, ab.prevDFG.size) + } + + @Test + @Throws(Exception::class) + fun testControlSensitiveDFGPassIfMerge() { + val result = GraphExamples.getControlFlowSensitiveDFGIfMerge() + + // Test If-Block + val literal2 = result.literals[{ it.value == 2 }] + assertNotNull(literal2) + + val b = result.variables["b"] + assertNotNull(b) + + val a2 = result.refs[{ it.access == AccessValues.WRITE }] + assertNotNull(a2) + assertTrue(literal2.nextDFG.contains(a2)) + assertEquals( + 1, + a2.nextDFG.size + ) // Outgoing DFG Edges only to the DeclaredReferenceExpression in the assignment to b + assertEquals( + b.initializer!!, + a2.nextDFG.first(), + ) + + val refersTo = a2.getRefersToAs(VariableDeclaration::class.java) + assertNotNull(refersTo) + assertEquals(2, refersTo.nextDFG.size) // The print and assignment to b + // Outgoing DFG Edge to the DeclaredReferenceExpression in the assignment of b + assertTrue(refersTo.nextDFG.contains(b.initializer!!)) + + // Test Else-Block with System.out.println() + val literal1 = result.literals[{ it.value == 1 }] + assertNotNull(literal1) + val println = result.calls["println"] + assertNotNull(println) + val aPrintln = println.arguments[0] + assertTrue(refersTo.nextDFG.contains(aPrintln)) + + assertEquals(1, aPrintln.prevDFG.size) + assertEquals(refersTo, aPrintln.prevDFG.first()) + assertEquals(1, aPrintln.nextEOG.size) + assertEquals(println, aPrintln.nextEOG[0]) + + val ab = b.prevEOG[0] as DeclaredReferenceExpression + assertTrue(refersTo.nextDFG.contains(ab)) + assertTrue(a2.nextDFG.contains(ab)) + } + + @Test + @Throws(Exception::class) + fun testControlSensitiveDFGPassSwitch() { + val result = GraphExamples.getControlFlowSesitiveDFGSwitch() + + val a = result.variables["a"] + assertNotNull(a) + + val b = result.variables["b"] + assertNotNull(b) + + val ab = b.prevEOG[0] as DeclaredReferenceExpression + val a10 = result.refs[{ TestUtils.compareLineFromLocationIfExists(it, true, 8) }] + val a11 = result.refs[{ TestUtils.compareLineFromLocationIfExists(it, true, 11) }] + val a12 = result.refs[{ TestUtils.compareLineFromLocationIfExists(it, true, 14) }] + assertNotNull(a10) + assertNotNull(a11) + assertNotNull(a12) + + val literal0 = result.literals[{ it.value == 0 }] + val literal10 = result.literals[{ it.value == 10 }] + val literal11 = result.literals[{ it.value == 11 }] + val literal12 = result.literals[{ it.value == 12 }] + assertNotNull(literal0) + assertNotNull(literal10) + assertNotNull(literal11) + assertNotNull(literal12) + + assertEquals(1, literal10.nextDFG.size) + assertTrue(literal10.nextDFG.contains(a10)) + assertEquals(1, literal11.nextDFG.size) + assertTrue(literal11.nextDFG.contains(a11)) + assertEquals(1, literal12.nextDFG.size) + assertTrue(literal12.nextDFG.contains(a12)) + assertEquals(1, a.prevDFG.size) + assertTrue(a.prevDFG.contains(literal0)) + assertFalse(a.prevDFG.contains(a10)) + assertFalse(a.prevDFG.contains(a11)) + assertFalse(a.prevDFG.contains(a12)) + + assertEquals(1, ab.nextDFG.size) + assertTrue(ab.nextDFG.contains(b)) + + // Fallthrough test + val println = result.calls["println"] + assertNotNull(println) + + val aPrintln = result.refs[{ it.nextEOG.contains(println) }] + assertNotNull(aPrintln) + assertEquals(2, aPrintln.prevDFG.size) + assertTrue(aPrintln.prevDFG.contains(a)) + assertTrue(aPrintln.prevDFG.contains(a12)) + } + + /** + * Tests that the outgoing DFG edges from a VariableDeclaration go to references with a path + * without a new assignment to the variable. + * + * @throws Exception + */ + @Test + @Throws(Exception::class) + fun testOutgoingDFGFromVariableDeclaration() { + // TODO: IMHO this test is quite useless and can be merged into another one (e.g. + // testControlSensitiveDFGPassIfMerge). + val result = GraphExamples.getBasicSlice() + + val varA = TestUtils.findByUniqueName(result.variables, "a") + assertNotNull(varA) + // The variable can flow to lines 19, 23, 24, 26, 31, 34 without modifications. + assertEquals(6, varA.nextDFG.size) + assertEquals(1, varA.prevDFG.size) // Only the initializer should flow there. + } } diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/UnresolvedDFGPassTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/UnresolvedDFGPassTest.kt new file mode 100644 index 0000000000..76fef5fab4 --- /dev/null +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/UnresolvedDFGPassTest.kt @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2022, 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.InferenceConfiguration +import de.fraunhofer.aisec.cpg.ScopeManager +import de.fraunhofer.aisec.cpg.TranslationConfiguration +import de.fraunhofer.aisec.cpg.frontends.TestLanguage +import de.fraunhofer.aisec.cpg.frontends.TestLanguageFrontend +import de.fraunhofer.aisec.cpg.graph.* +import de.fraunhofer.aisec.cpg.graph.builder.* +import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration +import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Literal +import de.fraunhofer.aisec.cpg.graph.types.ObjectType +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +class UnresolvedDFGPassTest { + @Test + fun testUnresolvedCalls() { + val result = getDfgUnresolvedCalls(true) + + // Flow from base to return value + val firstCall = result.calls { it.name.localName == "get" }[0] + val osDecl = result.variables["os"] + assertEquals(1, firstCall.prevDFG.size) + assertEquals( + osDecl, + (firstCall.prevDFG.firstOrNull() as? DeclaredReferenceExpression)?.refersTo + ) + + // Flow from base and argument to return value + val callWithParam = result.calls { it.name.localName == "get" }[1] + assertEquals(2, callWithParam.prevDFG.size) + assertEquals( + osDecl, + callWithParam.prevDFG + .filterIsInstance() + .firstOrNull() + ?.refersTo + ) + assertEquals(4, callWithParam.prevDFG.filterIsInstance>().firstOrNull()?.value) + + // No specific flows for resolved functions + // => Goes through the method declaration and then follows the instructions in the method's + // implementation + val knownCall = result.calls { it.name.localName == "knownFunction" }[0] + assertEquals(1, knownCall.prevDFG.size) + assertTrue(knownCall.prevDFG.firstOrNull() is MethodDeclaration) + } + + @Test + fun testUnresolvedCallsNoInference() { + val result = getDfgUnresolvedCalls(false) + + // No flow from base to return value + val firstCall = result.calls { it.name.localName == "get" }[0] + val osDecl = result.variables["os"] + assertEquals(0, firstCall.prevDFG.size) + + // No flow from base or argument to return value + val callWithParam = result.calls { it.name.localName == "get" }[1] + assertEquals(0, callWithParam.prevDFG.size) + + // No specific flows for resolved functions + // => Goes through the method declaration and then follows the instructions in the method's + // implementation + val knownCall = result.calls { it.name.localName == "knownFunction" }[0] + assertEquals(1, knownCall.prevDFG.size) + assertTrue(knownCall.prevDFG.firstOrNull() is MethodDeclaration) + } + + companion object { + + fun getDfgUnresolvedCalls(inferUnresolved: Boolean) = + TestLanguageFrontend(ScopeManager(), ".").build { + translationResult( + TranslationConfiguration.builder() + .defaultPasses() + .registerLanguage(TestLanguage(".")) + .inferenceConfiguration( + InferenceConfiguration.builder() + .inferDfgForUnresolvedCalls(inferUnresolved) + .build() + ) + .build() + ) { + translationUnit("DfgUnresolvedCalls.java") { + record("DfgUnresolvedCalls") { + field("i", t("int")) { modifiers = listOf("private") } + constructor() { + receiver = newVariableDeclaration("this", t("DfgUnresolvedCalls")) + param("i", t("int")) + body { + member("i", ref("this")) assign { ref("i") } + returnStmt { isImplicit = true } + } + } + method("knownFunction", t("int")) { + receiver = newVariableDeclaration("this", t("DfgUnresolvedCalls")) + param("arg", t("int")) + body { returnStmt { member("i", ref("this")) + ref("arg") } } + } + + // The main method + method("main") { + this.isStatic = true + param("args", t("String[]")) + body { + declare { + variable( + "os", + t("Optional") { + (this as ObjectType).generics = listOf(t("String")) + } + ) { + memberCall("getOptionalString", ref("RandomClass")) { + isStatic = true + } + } + } + declare { + variable("s", t("String")) { memberCall("get", ref("os")) } + } + declare { + variable("s2", t("String")) { + memberCall("get", ref("os")) { + addArgument(literal(4, t("int"))) + } + } + } + declare { + variable("duc", t("DfgUnresolvedCalls")) { + new { + construct("DfgUnresolvedCalls") { + addArgument(literal(3, t("int"))) + } + } + } + } + declare { + variable("i", t("int")) { + memberCall("knownFunction", ref("duc")) { + addArgument(literal(2, t("int"))) + } + } + } + } + } + } + } + } + } + } +} diff --git a/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/VariableResolverTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/VariableResolverTest.kt similarity index 90% rename from cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/VariableResolverTest.kt rename to cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/VariableResolverTest.kt index 6fab868341..117b701d3b 100644 --- a/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/VariableResolverTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/VariableResolverTest.kt @@ -26,9 +26,8 @@ package de.fraunhofer.aisec.cpg.passes import de.fraunhofer.aisec.cpg.BaseTest -import de.fraunhofer.aisec.cpg.TestUtils.analyze +import de.fraunhofer.aisec.cpg.GraphExamples import de.fraunhofer.aisec.cpg.TestUtils.findByUniqueName -import de.fraunhofer.aisec.cpg.frontends.java.JavaLanguage import de.fraunhofer.aisec.cpg.graph.allChildren import de.fraunhofer.aisec.cpg.graph.fields import de.fraunhofer.aisec.cpg.graph.methods @@ -36,19 +35,17 @@ import de.fraunhofer.aisec.cpg.graph.statements.ReturnStatement import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberExpression import de.fraunhofer.aisec.cpg.graph.variables -import java.nio.file.Path import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertNotEquals import kotlin.test.assertNotNull internal class VariableResolverTest : BaseTest() { - private val topLevel = Path.of("src", "test", "resources", "variables") @Test @Throws(Exception::class) fun testFields() { - val result = analyze("java", topLevel, true) { it.registerLanguage(JavaLanguage()) } + val result = GraphExamples.getVariables() val methods = result.methods val fields = result.fields val field = findByUniqueName(fields, "field") @@ -67,7 +64,7 @@ internal class VariableResolverTest : BaseTest() { @Test @Throws(Exception::class) fun testLocalVars() { - val result = analyze("java", topLevel, true) { it.registerLanguage(JavaLanguage()) } + val result = GraphExamples.getVariables() val methods = result.methods val fields = result.fields val field = findByUniqueName(fields, "field") diff --git a/cpg-core/src/test/resources/dfg/compoundoperator.cpp b/cpg-core/src/test/resources/dfg/compoundoperator.cpp deleted file mode 100644 index 8a7a9d2a2c..0000000000 --- a/cpg-core/src/test/resources/dfg/compoundoperator.cpp +++ /dev/null @@ -1,6 +0,0 @@ -class MyClass { - void somefun() { - int i = 0; - i += 2; - } -}; diff --git a/cpg-core/src/test/resources/dfg/conditional_expression.cpp b/cpg-core/src/test/resources/dfg/conditional_expression.cpp deleted file mode 100644 index fcd148dee6..0000000000 --- a/cpg-core/src/test/resources/dfg/conditional_expression.cpp +++ /dev/null @@ -1,7 +0,0 @@ -int main() { - int a = 0; - int b = 1; - - a = a == b ? b = 2: b = 3; - a = b; -} \ No newline at end of file diff --git a/cpg-core/src/test/resources/dfg/unaryoperator.cpp b/cpg-core/src/test/resources/dfg/unaryoperator.cpp deleted file mode 100644 index 4cc7741dbc..0000000000 --- a/cpg-core/src/test/resources/dfg/unaryoperator.cpp +++ /dev/null @@ -1,6 +0,0 @@ -class MyClass { - void somefun() { - int i = 0; - i++; - } -}; diff --git a/cpg-core/src/test/resources/inference/record.cpp b/cpg-core/src/test/resources/inference/record.cpp deleted file mode 100644 index 5630a67189..0000000000 --- a/cpg-core/src/test/resources/inference/record.cpp +++ /dev/null @@ -1,5 +0,0 @@ -int main() { - T node; - node.value = 42; - node.next = &node; -} \ No newline at end of file diff --git a/cpg-core/src/test/resources/inference/record_ptr.cpp b/cpg-core/src/test/resources/inference/record_ptr.cpp deleted file mode 100644 index 2fb8375d2c..0000000000 --- a/cpg-core/src/test/resources/inference/record_ptr.cpp +++ /dev/null @@ -1,7 +0,0 @@ -int main() { - T* node = new T(); - node->value = 42; - node->next = node; - - node->dump(); -} \ No newline at end of file diff --git a/cpg-core/src/testFixtures/kotlin/de/fraunhofer/aisec/cpg/frontends/TestLanguage.kt b/cpg-core/src/testFixtures/kotlin/de/fraunhofer/aisec/cpg/frontends/TestLanguage.kt index d97d0bd89d..a618819497 100644 --- a/cpg-core/src/testFixtures/kotlin/de/fraunhofer/aisec/cpg/frontends/TestLanguage.kt +++ b/cpg-core/src/testFixtures/kotlin/de/fraunhofer/aisec/cpg/frontends/TestLanguage.kt @@ -43,7 +43,7 @@ import kotlin.reflect.KClass * This is a test language that can be used for unit test, where we need a language but do not have * a specific one. */ -class TestLanguage(namespaceDelimiter: String = "::") : Language() { +open class TestLanguage(namespaceDelimiter: String = "::") : Language() { override val fileExtensions: List = listOf() override val namespaceDelimiter: String override val frontend: KClass = TestLanguageFrontend::class @@ -73,14 +73,18 @@ class TestLanguage(namespaceDelimiter: String = "::") : Language = TestLanguage(namespaceDelimiter) ) : LanguageFrontend( - TestLanguage(namespaceDelimiter), + language, TranslationConfiguration.builder().build(), - ScopeManager(), + scopeManager, ) { override fun parse(file: File): TranslationUnitDeclaration { TODO("Not yet implemented") diff --git a/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/EOGTest.kt b/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/EOGTest.kt index 6b0351bc01..543adfc00d 100644 --- a/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/EOGTest.kt +++ b/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/EOGTest.kt @@ -47,7 +47,6 @@ import de.fraunhofer.aisec.cpg.processing.strategy.Strategy import de.fraunhofer.aisec.cpg.sarif.PhysicalLocation import java.io.File import java.nio.file.Path -import java.util.function.Consumer import java.util.stream.Collectors import java.util.stream.Stream import kotlin.test.* @@ -83,18 +82,11 @@ internal class EOGTest : BaseTest() { } val ifs = nodes.filterIsInstance() assertEquals(2, ifs.size) - ifs.forEach(Consumer { ifnode: IfStatement -> assertNotNull(ifnode.thenStatement) }) - assertTrue( - ifs.stream().anyMatch { node: IfStatement -> node.elseStatement == null } && - ifs.stream().anyMatch { node: IfStatement -> node.elseStatement != null } - ) + ifs.forEach { assertNotNull(it.thenStatement) } + assertTrue(ifs.any { it.elseStatement == null } && ifs.any { it.elseStatement != null }) val ifSimple = ifs[0] val ifBranched = ifs[1] - val prints = - nodes - .stream() - .filter { node: Node -> node.code == refNodeString } - .collect(Collectors.toList()) + val prints = nodes.filter { it.code == refNodeString } val ifEOG = SubgraphWalker.getEOGPathEdges(ifSimple) var conditionEOG = SubgraphWalker.getEOGPathEdges(ifSimple.condition) var thenEOG = SubgraphWalker.getEOGPathEdges(ifSimple.thenStatement) @@ -237,7 +229,7 @@ internal class EOGTest : BaseTest() { fun testConditionShortCircuit() { val nodes = translateToNodes("src/test/resources/cfg/ShortCircuit.java") val binaryOperators = - nodes.filterIsInstance().filter { bo: BinaryOperator -> + nodes.filterIsInstance().filter { bo -> bo.operatorCode == "&&" || bo.operatorCode == "||" } for (bo in binaryOperators) { @@ -291,11 +283,7 @@ internal class EOGTest : BaseTest() { @Throws(Exception::class) fun testJavaFor() { val nodes = translateToNodes("src/test/resources/cfg/ForLoop.java") - val prints = - nodes - .stream() - .filter { node: Node -> node.code == REFNODESTRINGJAVA } - .collect(Collectors.toList()) + val prints = nodes.filter { it.code == REFNODESTRINGJAVA } val fstat = nodes.filterIsInstance() var fs = fstat[0] assertTrue( @@ -436,12 +424,8 @@ internal class EOGTest : BaseTest() { @Throws(Exception::class) fun testLoops(relPath: String, refNodeString: String?) { val nodes = translateToNodes(relPath) - val prints = - nodes - .stream() - .filter { node: Node -> node.code == refNodeString } - .collect(Collectors.toList()) - assertEquals(1, nodes.stream().filter { node: Node? -> node is WhileStatement }.count()) + val prints = nodes.filter { it.code == refNodeString } + assertEquals(1, nodes.filterIsInstance().count()) val wstat = nodes.filterIsInstance().firstOrNull() assertNotNull(wstat) @@ -771,7 +755,7 @@ internal class EOGTest : BaseTest() { } // Test If-Block - val firstIf: IfStatement = + val firstIf = result.allChildren().filter { l -> l.location?.region?.startLine == 6 }[0] val a = result.refs[ @@ -828,12 +812,8 @@ internal class EOGTest : BaseTest() { @Throws(Exception::class) fun testBreakContinue(relPath: String, refNodeString: String) { val nodes = translateToNodes(relPath) - val prints = - nodes - .stream() - .filter { node: Node -> node.code == refNodeString } - .collect(Collectors.toList()) - assertEquals(1, nodes.stream().filter { node: Node? -> node is WhileStatement }.count()) + val prints = nodes.filter { it.code == refNodeString } + assertEquals(1, nodes.filterIsInstance().count()) val breaks = nodes.filterIsInstance() val continues = nodes.filterIsInstance() val wstat = nodes.filterIsInstance().firstOrNull() @@ -981,10 +961,8 @@ internal class EOGTest : BaseTest() { } var nodes = SubgraphWalker.flattenAST(tu) // TODO: until explicitly added Return Statements are either removed again or code and - // region - // set properly - nodes = - nodes.stream().filter { node: Node -> node.code != null }.collect(Collectors.toList()) + // region set properly + nodes = nodes.filter { it.code != null } return nodes } diff --git a/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/DFGTest.kt b/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/DFGTest.kt deleted file mode 100644 index b639b1af94..0000000000 --- a/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/DFGTest.kt +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (c) 2023, 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.TestUtils -import de.fraunhofer.aisec.cpg.frontends.java.JavaLanguage -import de.fraunhofer.aisec.cpg.graph.* -import de.fraunhofer.aisec.cpg.graph.statements.ReturnStatement -import java.nio.file.Path -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertNotNull -import kotlin.test.assertTrue - -class DFGTest { - companion object { - private val topLevel = Path.of("src", "test", "resources", "dfg") - } - @Test - fun testReturnStatement() { - val result = - TestUtils.analyze( - listOf(Path.of(topLevel.toString(), "ReturnTest.java").toFile()), - topLevel, - true - ) { - it.registerLanguage(JavaLanguage()) - } - val returnFunction = result.functions["testReturn"] - assertNotNull(returnFunction) - - assertEquals(2, returnFunction.prevDFG.size) - - val allRealReturns = returnFunction.allChildren { it.location != null } - assertEquals(allRealReturns.toSet() as Set, returnFunction.prevDFG) - - assertEquals(1, allRealReturns[0].prevDFG.size) - assertTrue(returnFunction.literals.first { it.value == 2 } in allRealReturns[0].prevDFG) - assertEquals(1, allRealReturns[1].prevDFG.size) - assertTrue( - returnFunction.refs.last { it.name.localName == "a" } in allRealReturns[1].prevDFG - ) - } -} diff --git a/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/UnresolvedDFGPassTest.kt b/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/UnresolvedDFGPassTest.kt deleted file mode 100644 index 07061e2513..0000000000 --- a/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/UnresolvedDFGPassTest.kt +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright (c) 2022, 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.InferenceConfiguration -import de.fraunhofer.aisec.cpg.TestUtils -import de.fraunhofer.aisec.cpg.frontends.java.JavaLanguage -import de.fraunhofer.aisec.cpg.graph.* -import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration -import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression -import de.fraunhofer.aisec.cpg.graph.statements.expressions.Literal -import java.nio.file.Path -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertTrue - -class UnresolvedDFGPassTest { - companion object { - private val topLevel = Path.of("src", "test", "resources", "dfg") - } - - @Test - fun testUnresolvedCalls() { - val result = - TestUtils.analyze( - listOf(Path.of(topLevel.toString(), "DfgUnresolvedCalls.java").toFile()), - topLevel, - true - ) { - it.inferenceConfiguration( - InferenceConfiguration.builder().inferDfgForUnresolvedCalls(true).build() - ) - it.registerLanguage(JavaLanguage()) - } - - // Flow from base to return value - val firstCall = result.calls { it.name.localName == "get" }[0] - val osDecl = result.variables["os"] - assertEquals(1, firstCall.prevDFG.size) - assertEquals( - osDecl, - (firstCall.prevDFG.firstOrNull() as? DeclaredReferenceExpression)?.refersTo - ) - - // Flow from base and argument to return value - val callWithParam = result.calls { it.name.localName == "get" }[1] - assertEquals(2, callWithParam.prevDFG.size) - assertEquals( - osDecl, - callWithParam.prevDFG - .filterIsInstance() - .firstOrNull() - ?.refersTo - ) - assertEquals(4, callWithParam.prevDFG.filterIsInstance>().firstOrNull()?.value) - - // No specific flows for resolved functions - // => Goes through the method declaration and then follows the instructions in the method's - // implementation - val knownCall = result.calls { it.name.localName == "knownFunction" }[0] - assertEquals(1, knownCall.prevDFG.size) - assertTrue(knownCall.prevDFG.firstOrNull() is MethodDeclaration) - } - - @Test - fun testUnresolvedCallsNoInference() { - val result = - TestUtils.analyze( - listOf(Path.of(topLevel.toString(), "DfgUnresolvedCalls.java").toFile()), - topLevel, - true - ) { - it.inferenceConfiguration( - InferenceConfiguration.builder().inferDfgForUnresolvedCalls(false).build() - ) - it.registerLanguage(JavaLanguage()) - } - - // No flow from base to return value - val firstCall = result.calls { it.name.localName == "get" }[0] - val osDecl = result.variables["os"] - assertEquals(0, firstCall.prevDFG.size) - - // No flow from base or argument to return value - val callWithParam = result.calls { it.name.localName == "get" }[1] - assertEquals(0, callWithParam.prevDFG.size) - - // No specific flows for resolved functions - // => Goes through the method declaration and then follows the instructions in the method's - // implementation - val knownCall = result.calls { it.name.localName == "knownFunction" }[0] - assertEquals(1, knownCall.prevDFG.size) - assertTrue(knownCall.prevDFG.firstOrNull() is MethodDeclaration) - } -} diff --git a/cpg-language-java/src/test/resources/dfg/BasicSlice.java b/cpg-language-java/src/test/resources/dfg/BasicSlice.java deleted file mode 100644 index eac917624f..0000000000 --- a/cpg-language-java/src/test/resources/dfg/BasicSlice.java +++ /dev/null @@ -1,50 +0,0 @@ -public class BasicSlice { - - public static void main(String[] args) { // samples/BasicSlice.java:a:12:13 samples/BasicSlice.java - int a = 0; - int b = 1, c = 0, d = 0; - boolean sunShines = true; // and i am still inside :( - - /* - if (b > 2) { - a +=2; // 3 - } else { - a -=2; // 2 - } - b = a; // 1 - a -= 4; // 4 - - */ - - if (a > 0) { - d = 5; - c = 2; - if (b > 0) { - d = a * 2; - a = a + d * 2; - } else if (b < -2) { - a = a - 10; - } - } else { - b = -2; - d = -2; - a--; - } - - a = a + b; - - switch (sunShines) { - case True: - a = a * 2; - c = -2; - break; - case False: - a = 290; - d = -2; - b = -2; - break; - } - } - - -} diff --git a/cpg-language-java/src/test/resources/dfg/ControlFlowSensitiveDFGIfMerge.java b/cpg-language-java/src/test/resources/dfg/ControlFlowSensitiveDFGIfMerge.java deleted file mode 100644 index d4e19d91ec..0000000000 --- a/cpg-language-java/src/test/resources/dfg/ControlFlowSensitiveDFGIfMerge.java +++ /dev/null @@ -1,20 +0,0 @@ -public class ControlFlowSensitiveDFGIfMerge { - void func(int[] args) { - int a = 1; - - if (args.length > 3) { - a = 2; - } else { - System.out.println(a); - } - - int b = a; - } - - int bla; - - public static void main(String[] args) { - ControlFlowSensitiveDFGIfMerge obj = new ControlFlowSensitiveDFGIfMerge(); - obj.bla = 3; - } -} \ No newline at end of file diff --git a/cpg-language-java/src/test/resources/dfg/ControlFlowSensitiveDFGIfNoMerge.java b/cpg-language-java/src/test/resources/dfg/ControlFlowSensitiveDFGIfNoMerge.java deleted file mode 100644 index c0d9f24213..0000000000 --- a/cpg-language-java/src/test/resources/dfg/ControlFlowSensitiveDFGIfNoMerge.java +++ /dev/null @@ -1,11 +0,0 @@ -public class ControlFlowSensitiveDFGIfNoMerge { - void func2() { - int a = 1; - if (args.length > 3) { - a = 2; - } else { - a = 4; - int b = a; - } - } -} diff --git a/cpg-language-java/src/test/resources/dfg/ControlFlowSensitiveDFGSwitch.java b/cpg-language-java/src/test/resources/dfg/ControlFlowSensitiveDFGSwitch.java deleted file mode 100644 index 08c9175c4b..0000000000 --- a/cpg-language-java/src/test/resources/dfg/ControlFlowSensitiveDFGSwitch.java +++ /dev/null @@ -1,22 +0,0 @@ -public class ControlFlowSesitiveDFGSwitch { - void func3() { - int swithVal = 3; - int a = 0; - - switch (swithVal) { - case 1: - a = 10; - break; - case 2: - a = 11; - break; - case 3: - a = 12; // Fall through - default: - System.out.println(a); - break; - } - - int b = a; - } -} diff --git a/cpg-language-java/src/test/resources/dfg/DelayedAssignmentAfterRHS.java b/cpg-language-java/src/test/resources/dfg/DelayedAssignmentAfterRHS.java deleted file mode 100644 index 83859db26e..0000000000 --- a/cpg-language-java/src/test/resources/dfg/DelayedAssignmentAfterRHS.java +++ /dev/null @@ -1,9 +0,0 @@ -public class DelayedAssignmentAfterRHS { - - public static void main(String[] args) { - int a = 0; - int b = 1; - - a = a + b; - } -} diff --git a/cpg-language-java/src/test/resources/dfg/DfgUnresolvedCalls.java b/cpg-language-java/src/test/resources/dfg/DfgUnresolvedCalls.java deleted file mode 100644 index 75dd064b27..0000000000 --- a/cpg-language-java/src/test/resources/dfg/DfgUnresolvedCalls.java +++ /dev/null @@ -1,19 +0,0 @@ -public class DfgUnresolvedCalls { - private int i; - public DfgUnresolvedCalls(int i) { - this.i = i; - } - - public int knownFunction(int arg) { - return this.i + arg; - } - - public static void main(String[] args) { - Optional os = RandomClass.getOptionalString(); - String s = os.get(); - String s2 = os.get(4); - - DfgUnresolvedCalls duc = new DfgUnresolvedCalls(3); - int i = duc.knownFunction(2); - } -} \ No newline at end of file diff --git a/cpg-language-java/src/test/resources/dfg/LoopDFGs.java b/cpg-language-java/src/test/resources/dfg/LoopDFGs.java deleted file mode 100644 index 9612587156..0000000000 --- a/cpg-language-java/src/test/resources/dfg/LoopDFGs.java +++ /dev/null @@ -1,35 +0,0 @@ -public class LoopDFGs { - - public void looping(int param){ - int a = 0; - while(param % 6 == 5){ - if(param > 7){ - a = 1; - }else{ - System.out.println(a); // Should have a dfg path from 0,1,2 but not 3 - a = 2; - } - } - a = 3; - } - - public void labeledBreakContinue(int param){ - int a = 0; - lab1: while(param < 5){ - while(param > 6) { - if (param > 7) { - a = 1; - continue lab1; - } else { - System.out.println(a); // Should have a dfg path from 0, 1, 3 - a = 2; - break lab1; - } - a = 4; - } - System.out.println(a); // Should have a dfg path from 0, 1, 3 - a = 3; - } - System.out.println(a); // Should have a dfg path from 0,1,2,3 - } -} diff --git a/cpg-language-java/src/test/resources/dfg/ReturnTest.java b/cpg-language-java/src/test/resources/dfg/ReturnTest.java deleted file mode 100644 index ed112acbe6..0000000000 --- a/cpg-language-java/src/test/resources/dfg/ReturnTest.java +++ /dev/null @@ -1,10 +0,0 @@ -public class ReturnTest { - public int testReturn() { - int a = 1; - if(a == 5) { - return 2; - } else { - return a; - } - } -} \ No newline at end of file diff --git a/cpg-language-java/src/test/resources/variables/Variables.java b/cpg-language-java/src/test/resources/variables/Variables.java deleted file mode 100644 index d319eb29c5..0000000000 --- a/cpg-language-java/src/test/resources/variables/Variables.java +++ /dev/null @@ -1,22 +0,0 @@ -public class Variables { - private int field = 42; - - private int getField() { - return field; - } - - private int getLocal() { - int local = 42; - return local; - } - - private int getShadow() { - int field = 43; - return field; - } - - private int noShadow() { - int field = 43; - return this.field; - } -} \ No newline at end of file From 8f140f7eb38ea84579bc8147d1d656e235b93853 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 6 May 2023 09:35:53 +0000 Subject: [PATCH 041/143] Update plugin com.gradle.enterprise to v3.13.1 (#1154) * Update plugin com.gradle.enterprise to v3.13.1 * Removing plugin --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Christian Banse --- settings.gradle.kts | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/settings.gradle.kts b/settings.gradle.kts index b2c4fcaa36..275ab02053 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -2,17 +2,6 @@ rootProject.name = "cpg" enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") -plugins { - id("com.gradle.enterprise") version("3.11.3") -} - -gradleEnterprise { - buildScan { - termsOfServiceUrl = "https://gradle.com/terms-of-service" - termsOfServiceAgree = "yes" - } -} - include(":cpg-all") include(":cpg-core") include(":cpg-analysis") From ab43347f322882fea20e996f0db35f25e00f0515 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 7 May 2023 11:32:53 +0200 Subject: [PATCH 042/143] Update dependency webpack-cli to v5.1.0 (#1170) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .../src/main/nodejs/package.json | 2 +- .../src/main/nodejs/yarn.lock | 50 +++++++++---------- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/cpg-language-typescript/src/main/nodejs/package.json b/cpg-language-typescript/src/main/nodejs/package.json index 00d16a6856..4539efb658 100644 --- a/cpg-language-typescript/src/main/nodejs/package.json +++ b/cpg-language-typescript/src/main/nodejs/package.json @@ -12,6 +12,6 @@ "license": "Apache-2.0", "devDependencies": { "webpack": "5.82.0", - "webpack-cli": "5.0.0" + "webpack-cli": "5.1.0" } } \ No newline at end of file diff --git a/cpg-language-typescript/src/main/nodejs/yarn.lock b/cpg-language-typescript/src/main/nodejs/yarn.lock index 90bfc3f9cd..52a915e9c7 100644 --- a/cpg-language-typescript/src/main/nodejs/yarn.lock +++ b/cpg-language-typescript/src/main/nodejs/yarn.lock @@ -217,20 +217,20 @@ "@webassemblyjs/ast" "1.11.5" "@xtuc/long" "4.2.2" -"@webpack-cli/configtest@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@webpack-cli/configtest/-/configtest-2.0.0.tgz#5e1bc37064c7d00e1330641fa523f8ff85a39513" - integrity sha512-war4OU8NGjBqU3DP3bx6ciODXIh7dSXcpQq+P4K2Tqyd8L5OjZ7COx9QXx/QdCIwL2qoX09Wr4Cwf7uS4qdEng== +"@webpack-cli/configtest@^2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@webpack-cli/configtest/-/configtest-2.1.0.tgz#b59b33377b1b896a9a7357cfc643b39c1524b1e6" + integrity sha512-K/vuv72vpfSEZoo5KIU0a2FsEoYdW0DUMtMpB5X3LlUwshetMZRZRxB7sCsVji/lFaSxtQQ3aM9O4eMolXkU9w== -"@webpack-cli/info@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@webpack-cli/info/-/info-2.0.0.tgz#5a58476b129ee9b462117b23393596e726bf3b80" - integrity sha512-NNxDgbo4VOkNhOlTgY0Elhz3vKpOJq4/PKeKg7r8cmYM+GQA9vDofLYyup8jS6EpUvhNmR30cHTCEIyvXpskwA== +"@webpack-cli/info@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@webpack-cli/info/-/info-2.0.1.tgz#eed745799c910d20081e06e5177c2b2569f166c0" + integrity sha512-fE1UEWTwsAxRhrJNikE7v4EotYflkEhBL7EbajfkPlf6E37/2QshOy/D48Mw8G5XMFlQtS6YV42vtbG9zBpIQA== -"@webpack-cli/serve@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-2.0.0.tgz#f08ea194e01ed45379383a8886e8c85a65a5f26a" - integrity sha512-Rumq5mHvGXamnOh3O8yLk1sjx8dB30qF1OeR6VC00DIR6SLJ4bwwUGKC4pE7qBFoQyyh0H9sAg3fikYgAqVR0w== +"@webpack-cli/serve@^2.0.3": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-2.0.3.tgz#c00c48d19340224242842e38b8f7b76c308bbd3f" + integrity sha512-Bwxd73pHuYc0cyl7vulPp2I6kAYtmJPkfUivbts7by6wDAVyFdKzGX3AksbvCRyNVFUJu7o2ZTcWXdT90T3qbg== "@xtuc/ieee754@^1.2.0": version "1.2.0" @@ -312,16 +312,16 @@ colorette@^2.0.14: resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.16.tgz#713b9af84fdb000139f04546bd4a93f62a5085da" integrity sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g== +commander@^10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06" + integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug== + commander@^2.20.0: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== -commander@^9.4.1: - version "9.4.1" - resolved "https://registry.yarnpkg.com/commander/-/commander-9.4.1.tgz#d1dd8f2ce6faf93147295c0df13c7c21141cfbdd" - integrity sha512-5EEkTNyHNGFPD2H+c/dXXfQZYa/scCKasxWcXJaWnNJ99pnQN9Vnmqow+p+PlFPE63Q6mThaZws1T+HxfpgtPw== - cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" @@ -740,17 +740,17 @@ watchpack@^2.4.0: glob-to-regexp "^0.4.1" graceful-fs "^4.1.2" -webpack-cli@5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-5.0.0.tgz#bd380a9653e0cd1a08916c4ff1adea17201ef68f" - integrity sha512-AACDTo20yG+xn6HPW5xjbn2Be4KUzQPebWXsDMHwPPyKh9OnTOJgZN2Nc+g/FZKV3ObRTYsGvibAvc+5jAUrVA== +webpack-cli@5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-5.1.0.tgz#abc4b1f44b50250f2632d8b8b536cfe2f6257891" + integrity sha512-a7KRJnCxejFoDpYTOwzm5o21ZXMaNqtRlvS183XzGDUPRdVEzJNImcQokqYZ8BNTnk9DkKiuWxw75+DCCoZ26w== dependencies: "@discoveryjs/json-ext" "^0.5.0" - "@webpack-cli/configtest" "^2.0.0" - "@webpack-cli/info" "^2.0.0" - "@webpack-cli/serve" "^2.0.0" + "@webpack-cli/configtest" "^2.1.0" + "@webpack-cli/info" "^2.0.1" + "@webpack-cli/serve" "^2.0.3" colorette "^2.0.14" - commander "^9.4.1" + commander "^10.0.1" cross-spawn "^7.0.3" envinfo "^7.7.3" fastest-levenshtein "^1.0.12" From 56d7019dfdf75464d4854fa0f3eddecdd1a4449b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 14 May 2023 10:06:52 +0200 Subject: [PATCH 043/143] Update plugin node to v5 (#1172) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 83653baaf7..4ced97c1e1 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -53,4 +53,4 @@ kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin"} dokka = { id = "org.jetbrains.dokka", version.ref = "kotlin" } sonarqube = { id = "org.sonarqube", version.ref = "sonarqube" } spotless = { id = "com.diffplug.spotless", version.ref = "spotless" } -node = { id = "com.github.node-gradle.node", version = "4.0.0"} +node = { id = "com.github.node-gradle.node", version = "5.0.0"} From f973b69032dc02b0605ac6159cf502439fb39052 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 18 May 2023 07:34:30 +0200 Subject: [PATCH 044/143] Update dependency commons-io:commons-io to v2.12.0 (#1173) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4ced97c1e1..fdf747181a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -27,7 +27,7 @@ eclipse-runtime = { module = "org.eclipse.platform:org.eclipse.core.runtime", ve osgi-service = { module = "org.osgi:org.osgi.service.prefs", version = "1.1.2"} icu4j = { module = "com.ibm.icu:icu4j", version = "73.1"} eclipse-cdt-core = { module = "org.eclipse.cdt:core", version = "8.0.0.202211292120"} -commons-io = { module = "commons-io:commons-io", version = "2.11.0"} +commons-io = { module = "commons-io:commons-io", version = "2.12.0"} jetbrains-annotations = { module = "org.jetbrains:annotations", version = "24.0.0"} picocli = { module = "info.picocli:picocli", version = "4.7.0"} picocli-codegen = { module = "info.picocli:picocli-codegen", version = "4.7.0"} From 623fa0fac9b5b49764cea3f1be28770bb809a582 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 18 May 2023 05:55:24 +0000 Subject: [PATCH 045/143] Update dependency webpack to v5.83.1 (#1174) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .../src/main/nodejs/package.json | 2 +- .../src/main/nodejs/yarn.lock | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/cpg-language-typescript/src/main/nodejs/package.json b/cpg-language-typescript/src/main/nodejs/package.json index 4539efb658..b3f4856330 100644 --- a/cpg-language-typescript/src/main/nodejs/package.json +++ b/cpg-language-typescript/src/main/nodejs/package.json @@ -11,7 +11,7 @@ }, "license": "Apache-2.0", "devDependencies": { - "webpack": "5.82.0", + "webpack": "5.83.1", "webpack-cli": "5.1.0" } } \ No newline at end of file diff --git a/cpg-language-typescript/src/main/nodejs/yarn.lock b/cpg-language-typescript/src/main/nodejs/yarn.lock index 52a915e9c7..ff43cd090e 100644 --- a/cpg-language-typescript/src/main/nodejs/yarn.lock +++ b/cpg-language-typescript/src/main/nodejs/yarn.lock @@ -336,10 +336,10 @@ electron-to-chromium@^1.3.723: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.772.tgz#fd1ed39f9f3149f62f581734e4f026e600369479" integrity sha512-X/6VRCXWALzdX+RjCtBU6cyg8WZgoxm9YA02COmDOiNJEZ59WkQggDbWZ4t/giHi/3GS+cvdrP6gbLISANAGYA== -enhanced-resolve@^5.13.0: - version "5.13.0" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.13.0.tgz#26d1ecc448c02de997133217b5c1053f34a0a275" - integrity sha512-eyV8f0y1+bzyfh8xAwW/WTSZpLbjhqc4ne9eGSH4Zo2ejdyiNG9pU6mf9DG8a7+Auk6MFTlNOT4Y2y/9k8GKVg== +enhanced-resolve@^5.14.0: + version "5.14.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.14.0.tgz#0b6c676c8a3266c99fa281e4433a706f5c0c61c4" + integrity sha512-+DCows0XNwLDcUhbFJPdlQEVnT2zXlCv7hPxemTz86/O+B/hCQ+mb7ydkPKiflpVraqLPCAfu7lDy+hBXueojw== dependencies: graceful-fs "^4.2.4" tapable "^2.2.0" @@ -772,10 +772,10 @@ webpack-sources@^3.2.3: resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== -webpack@5.82.0: - version "5.82.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.82.0.tgz#3c0d074dec79401db026b4ba0fb23d6333f88e7d" - integrity sha512-iGNA2fHhnDcV1bONdUu554eZx+XeldsaeQ8T67H6KKHl2nUSwX8Zm7cmzOA46ox/X1ARxf7Bjv8wQ/HsB5fxBg== +webpack@5.83.1: + version "5.83.1" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.83.1.tgz#fcb69864a0669ac3539a471081952c45b15d1c40" + integrity sha512-TNsG9jDScbNuB+Lb/3+vYolPplCS3bbEaJf+Bj0Gw4DhP3ioAflBb1flcRt9zsWITyvOhM96wMQNRWlSX52DgA== dependencies: "@types/eslint-scope" "^3.7.3" "@types/estree" "^1.0.0" @@ -786,7 +786,7 @@ webpack@5.82.0: acorn-import-assertions "^1.7.6" browserslist "^4.14.5" chrome-trace-event "^1.0.2" - enhanced-resolve "^5.13.0" + enhanced-resolve "^5.14.0" es-module-lexer "^1.2.1" eslint-scope "5.1.1" events "^3.2.0" From eed6a9da549dc2839428eddbb37ba3e3acc1ca2e Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Mon, 22 May 2023 19:14:17 +0200 Subject: [PATCH 046/143] Made `additionalNodes` a mutable set on public interface (#1176) * Made `additionalNodes` a mutable list on public interface Fixes #1175 * Apply suggestions from code review Co-authored-by: KuechA <31155350+KuechA@users.noreply.github.com> * Address review comments --------- Co-authored-by: KuechA <31155350+KuechA@users.noreply.github.com> --- .../fraunhofer/aisec/cpg/TranslationResult.kt | 2 +- .../aisec/cpg/helpers/SubgraphWalker.kt | 41 +++++++++---------- 2 files changed, 21 insertions(+), 22 deletions(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationResult.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationResult.kt index c98b0a86e7..6866f515bd 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationResult.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationResult.kt @@ -68,7 +68,7 @@ class TranslationResult( /** * A free-for-use collection of unique nodes. Nodes stored here will be exported to Neo4j, too. */ - val additionalNodes: Set = HashSet() + val additionalNodes = mutableSetOf() override val benchmarks: MutableSet = LinkedHashSet() val isCancelled: Boolean 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 79db52911e..a7015f0c6f 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 @@ -224,7 +224,7 @@ object SubgraphWalker { /** * Function returns two lists in a list. The first list contains all eog nodes with no - * predecesor in the subgraph with root 'n'. The second list contains eog edges that have no + * predecessor in the subgraph with root 'n'. The second list contains eog edges that have no * successor in the subgraph with root 'n'. The first List marks the entry and the second marks * the exit nodes of the cfg in this subgraph. * @@ -235,19 +235,18 @@ object SubgraphWalker { val border = Border() val flattedASTTree = flattenAST(n) val eogNodes = - flattedASTTree - .stream() - .filter { node: Node -> node.prevEOG.isNotEmpty() || node.nextEOG.isNotEmpty() } - .collect(Collectors.toList()) + flattedASTTree.filter { node: Node -> + node.prevEOG.isNotEmpty() || node.nextEOG.isNotEmpty() + } // Nodes that are incoming edges, no other node border.entries = - eogNodes.filter { node: Node -> - node.prevEOG.any { prev: Node -> !eogNodes.contains(prev) } - } + eogNodes + .filter { node: Node -> node.prevEOG.any { prev -> prev !in eogNodes } } + .toMutableList() border.exits = - eogNodes.filter { node: Node -> - node.nextEOG.any { next: Node -> !eogNodes.contains(next) } - } + eogNodes + .filter { node: Node -> node.nextEOG.any { next -> next !in eogNodes } } + .toMutableList() return border } @@ -271,8 +270,8 @@ object SubgraphWalker { * EOG subgraph, EOG entries and exits in a CFG subgraph. */ class Border { - var entries: List = ArrayList() - var exits: List = ArrayList() + var entries = mutableListOf() + var exits = mutableListOf() } class IterativeGraphWalker { @@ -379,10 +378,10 @@ object SubgraphWalker { * Handles declaration scope monitoring for iterative traversals. If this is not required, use * [IterativeGraphWalker] for less overhead. * - * Declaration scopes are similar to [de.fraunhofer.aisec.cpg.passes.scopes.ScopeManager] - * scopes: [ValueDeclaration]s located inside a scope (i.e. are children of the scope root) are - * visible to any children of the scope root. Scopes can be layered, where declarations from - * parent scopes are visible to the children but not the other way around. + * Declaration scopes are similar to [de.fraunhofer.aisec.cpg.ScopeManager] scopes: + * [ValueDeclaration]s located inside a scope (i.e. are children of the scope root) are visible + * to any children of the scope root. Scopes can be layered, where declarations from parent + * scopes are visible to the children but not the other way around. */ class ScopedWalker { // declarationScope -> (parentScope, declarations) @@ -461,10 +460,10 @@ object SubgraphWalker { if ( node is RecordDeclaration || node is CompoundStatement || - node is FunctionDeclaration // can also be a translationunit for global (c) - // functions - || - node is TranslationUnitDeclaration + node is FunctionDeclaration || + node is + TranslationUnitDeclaration // can also be a translation unit for global + // (C) functions ) { parentBlock = node break From 40ee092debf9ab7af8c65bbf15f86c1d9bd1e268 Mon Sep 17 00:00:00 2001 From: shenniger Date: Thu, 25 May 2023 08:48:20 +0200 Subject: [PATCH 047/143] Add --custom-pass-list and --list-passes (#1147) * Add --custom-pass-list and --list-passes Adds a CLI option to cpg-neo4j that allows a user to choose specific passes to run. Example: Use `--custom-pass-list=DFGPass` to run only DFGPass. This is especially useful for debugging. This feature requires TranslationConfiguration to keep a list of all choosable passes. Currently, the list is stored in a global variable of `KClass` objects (that reflection is used on in order to get their names and create instances of them). For now, this variable only contains passes that are part of `defaultPasses()`. This could be easily changed by adding other passes to the variable. * Miscellaneous small fixes * Reformat * Fix wrong member type * Reformat (again, sorry! syntax error must have tripped up the formatter) * Fix --list-passes as sole argument * Moved custom pass list to cpg-neo4j; added FQDN --------- Co-authored-by: Tobias Specht --- .../aisec/cpg_vis_neo4j/Application.kt | 64 ++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) diff --git a/cpg-neo4j/src/main/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/Application.kt b/cpg-neo4j/src/main/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/Application.kt index 5f27fec557..01931ee4d8 100644 --- a/cpg-neo4j/src/main/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/Application.kt +++ b/cpg-neo4j/src/main/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/Application.kt @@ -30,10 +30,14 @@ import de.fraunhofer.aisec.cpg.frontends.CompilationDatabase.Companion.fromFile import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.helpers.Benchmark import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker +import de.fraunhofer.aisec.cpg.passes.* +import de.fraunhofer.aisec.cpg.passes.order.* import java.io.File +import java.lang.Class import java.net.ConnectException import java.nio.file.Paths import java.util.concurrent.Callable +import kotlin.reflect.full.createInstance import kotlin.system.exitProcess import org.neo4j.driver.exceptions.AuthenticationException import org.neo4j.ogm.config.Configuration @@ -101,6 +105,12 @@ class Application : Callable { description = ["The path to an optional a JSON compilation database"] ) var jsonCompilationDatabase: File? = null + + @CommandLine.Option( + names = ["--list-passes"], + description = ["Prints the list available passes"] + ) + var listPasses: Boolean = false } @CommandLine.Option( @@ -165,6 +175,18 @@ class Application : Callable { ) private var noDefaultPasses: Boolean = false + @CommandLine.Option( + names = ["--custom-pass-list"], + description = + [ + "Add custom list of passes (includes --no-default-passes) which is" + + " passed as a comma-separated list; give either pass name if pass is in list," + + " or its FQDN" + + " (e.g. --custom-pass-list=DFGPass,CallResolver)" + ] + ) + private var customPasses: String = "DEFAULT" + @CommandLine.Option( names = ["--no-neo4j"], description = ["Do not push cpg into neo4j [used for debugging]"] @@ -198,6 +220,24 @@ class Application : Callable { ) private var benchmarkJson: File? = null + private var passClassList = + listOf( + TypeHierarchyResolver::class, + ImportResolver::class, + VariableUsageResolver::class, + CallResolver::class, + DFGPass::class, + EvaluationOrderGraphPass::class, + TypeResolver::class, + ControlFlowSensitiveDFGPass::class, + FunctionPointerCallResolver::class, + FilenameMapper::class + ) + private var passClassMap = passClassList.map { Pair(it.simpleName, it) }.toMap() + /** The list of available passes that can be registered. */ + private val passList: List + get() = passClassList.map { it.simpleName!! } + /** * Pushes the whole translationResult to the neo4j db. * @@ -334,8 +374,22 @@ class Application : Callable { translationConfiguration.sourceLocations(filePaths) } - if (!noDefaultPasses) { + if (!noDefaultPasses && customPasses == "DEFAULT") { translationConfiguration.defaultPasses() + } else if (!noDefaultPasses && customPasses != "DEFAULT") { + val pieces = customPasses.split(",") + for (pass in pieces) { + if (pass.contains(".")) { + translationConfiguration.registerPass( + Class.forName(pass).kotlin.createInstance() as Pass + ) + } else { + if (pass !in passClassMap) { + throw ConfigurationException("Asked to produce unknown pass") + } + translationConfiguration.registerPass(passClassMap[pass]!!.createInstance()) + } + } } if (mutuallyExclusiveParameters.jsonCompilationDatabase != null) { @@ -378,6 +432,14 @@ class Application : Callable { */ @Throws(Exception::class, ConnectException::class, IllegalArgumentException::class) override fun call(): Int { + if (mutuallyExclusiveParameters.listPasses) { + log.info("List of passes:") + passList.iterator().forEach { log.info("- " + it) } + log.info("--") + log.info("End of list. Stopping.") + return EXIT_SUCCESS + } + val translationConfiguration = setupTranslationConfiguration() val startTime = System.currentTimeMillis() From a8233c100b77b4f955d3886d5212dad04c4464cc Mon Sep 17 00:00:00 2001 From: KuechA <31155350+KuechA@users.noreply.github.com> Date: Fri, 26 May 2023 07:32:04 +0200 Subject: [PATCH 048/143] Reduce Sonar bugs warnings (#1181) * Reduce Sonar bugs * Fix failing test * Fix another potential null pointer exception * Get rid of more !! operators * Get rid of more !! operators * Remove more !! operators --- .../aisec/cpg/analysis/MultiValueEvaluator.kt | 4 +- .../fraunhofer/aisec/cpg/analysis/fsm/DFA.kt | 7 +- .../cpg/analysis/fsm/DFAOrderEvaluator.kt | 5 +- .../fraunhofer/aisec/cpg/analysis/fsm/FSM.kt | 2 + .../aisec/cpg/analysis/fsm/FSMEquality.kt | 12 ++-- .../fraunhofer/aisec/cpg/analysis/fsm/NFA.kt | 2 +- .../aisec/cpg/console/Extensions.kt | 7 +- .../aisec/cpg/TranslationManager.kt | 2 +- .../aisec/cpg/frontends/LanguageFrontend.kt | 4 +- .../aisec/cpg/frontends/ProcessedListener.kt | 2 +- .../cpg/frontends/cpp/DeclarationHandler.kt | 4 +- .../graph/declarations/FunctionDeclaration.kt | 2 +- .../graph/declarations/VariableDeclaration.kt | 2 +- .../aisec/cpg/graph/edge/PropertyEdge.kt | 2 +- .../cpg/graph/edge/PropertyEdgeConverter.kt | 9 ++- .../aisec/cpg/graph/scopes/LoopScope.kt | 2 +- .../statements/expressions/BinaryOperator.kt | 9 +-- .../statements/expressions/CallExpression.kt | 16 ++--- .../DeclaredReferenceExpression.kt | 9 ++- .../statements/expressions/UnaryOperator.kt | 2 +- .../aisec/cpg/graph/types/IncompleteType.kt | 2 +- .../aisec/cpg/graph/types/PointerType.kt | 2 +- .../fraunhofer/aisec/cpg/graph/types/Type.kt | 2 +- .../aisec/cpg/passes/CXXCallResolverHelper.kt | 6 +- .../aisec/cpg/passes/TypeResolver.kt | 2 +- .../aisec/cpg/passes/VariableUsageResolver.kt | 2 +- .../aisec/cpg/passes/inference/Inference.kt | 4 +- .../cpg/frontends/java/DeclarationHandler.kt | 35 ++++------ .../cpg/frontends/java/ExpressionHandler.kt | 64 ++++++++++--------- .../aisec/cpg/frontends/java/JavaLanguage.kt | 2 +- .../cpg/frontends/java/StatementHandler.kt | 58 +++++++++-------- .../cpg/frontends/llvm/DeclarationHandler.kt | 5 +- .../frontends/llvm/LLVMIRLanguageFrontend.kt | 12 ++-- .../cpg/frontends/llvm/StatementHandler.kt | 8 +-- .../aisec/cpg_vis_neo4j/Application.kt | 10 +-- 35 files changed, 159 insertions(+), 159 deletions(-) diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluator.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluator.kt index c572840787..2c7e14a9d2 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluator.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluator.kt @@ -339,7 +339,7 @@ class MultiValueEvaluator : ValueEvaluator() { (loopOp.input as? DeclaredReferenceExpression)?.refersTo == expr.refersTo ) { - loopVar!! + loopVar } else { loopOp.input }, @@ -367,7 +367,7 @@ class MultiValueEvaluator : ValueEvaluator() { return result } - private fun computeUnaryOpEffect(input: Any, expr: UnaryOperator): Any? { + private fun computeUnaryOpEffect(input: Any?, expr: UnaryOperator): Any? { return when (expr.operatorCode) { "-" -> { when (input) { diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/fsm/DFA.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/fsm/DFA.kt index be554ac6f9..421c20392e 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/fsm/DFA.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/fsm/DFA.kt @@ -82,6 +82,7 @@ class DFA(states: Set = setOf()) : FSM(states) { * Before calling this, initialize the orderEvaluation with [initializeOrderEvaluation] */ fun makeTransitionWithOp(op: Set, cpgNode: Node): Boolean { + val currentState = currentState checkNotNull(currentState) { "Cannot perform a transition because the FSM does not have a starting state!" } @@ -89,12 +90,10 @@ class DFA(states: Set = setOf()) : FSM(states) { "Before performing transitions, you must call [initializeOrderEvaluation] first." } - val possibleEdges = currentState!!.outgoingEdges.filter { e -> e.op in op } + val possibleEdges = currentState.outgoingEdges.filter { e -> e.op in op } val edgeToFollow = possibleEdges.singleOrNull() return if (edgeToFollow != null) { - _executionTrace.add( - Trace(state = currentState!!, cpgNode = cpgNode, edge = edgeToFollow) - ) + _executionTrace.add(Trace(state = currentState, cpgNode = cpgNode, edge = edgeToFollow)) true } else { false diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/fsm/DFAOrderEvaluator.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/fsm/DFAOrderEvaluator.kt index f27e129d3f..60e0f9d065 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/fsm/DFAOrderEvaluator.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/fsm/DFAOrderEvaluator.kt @@ -359,7 +359,8 @@ open class DFAOrderEvaluator( // The base was the parameter of the function? We have an inter-procedural flow! interproceduralFlows[prefixedBase] = true } - return Pair(prefixedBase, nodeToRelevantMethod[node]!!) + val relevantMethod = nodeToRelevantMethod[node] + if (relevantMethod != null) return Pair(prefixedBase, relevantMethod) } if (base == null) { @@ -513,7 +514,7 @@ open class DFAOrderEvaluator( baseToFSM.entries .groupBy { e -> e.key.split("|")[1] } .map { x -> - "${x.key}(${x.value.map { y -> y.value.currentState!! }.toSet().joinToString(",")})" + "${x.key}(${x.value.mapNotNull { y -> y.value.currentState }.toSet().joinToString(",")})" } .sorted() .joinToString(",") diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/fsm/FSM.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/fsm/FSM.kt index 5faada50c5..74f6ebcf78 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/fsm/FSM.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/fsm/FSM.kt @@ -193,4 +193,6 @@ sealed class FSM(states: Set) { return newFSM } + + override fun hashCode() = _states.hashCode() } diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/fsm/FSMEquality.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/fsm/FSMEquality.kt index f50068c862..d5cc8e641e 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/fsm/FSMEquality.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/fsm/FSMEquality.kt @@ -150,11 +150,11 @@ internal fun acceptsSameLanguage( // if the algorithm does not return before here, the graphs are equal! // But most of the time, we want to additionally make sure that both DFAs // are in the same state currently - return if (compareCurrentState) { - val currentState1 = findSet(dfa.currentState!!) // FIND-SET(p') - val currentState2 = findSet(otherDfa.currentState!!) // FIND-SET(q') + val dfaCurrentState = dfa.currentState + val otherDfaCurrentState = otherDfa.currentState + return if (compareCurrentState && dfaCurrentState != null && otherDfaCurrentState != null) { + val currentState1 = findSet(dfaCurrentState) // FIND-SET(p') + val currentState2 = findSet(otherDfaCurrentState) // FIND-SET(q') currentState1 == currentState2 - } else { - true - } + } else !(dfaCurrentState == null || otherDfaCurrentState == null) } diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/fsm/NFA.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/fsm/NFA.kt index 0e45ef47b1..2013699c29 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/fsm/NFA.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/fsm/NFA.kt @@ -106,7 +106,7 @@ class NFA(states: Set = setOf()) : FSM(states) { if (transitionClosure in epsilonClosures) { // if the transitionClosure is already in the DFA, get the DFA state it // corresponds to - nextDfaState = epsilonClosures[transitionClosure]!! + epsilonClosures[transitionClosure]?.let { nextDfaState = it } } else { // else create a new DFA state and add it to the known and to be explored states nextDfaState = diff --git a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/console/Extensions.kt b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/console/Extensions.kt index 1f33c376e2..a1c6177590 100644 --- a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/console/Extensions.kt +++ b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/console/Extensions.kt @@ -187,20 +187,21 @@ fun getFanciesFor(original: Node?, node: Node?): List { // color the member - node.location?.let { list += Pair(styles.identifier!!, it.region) } + node.location?.let { styles.identifier?.let { id -> list += Pair(id, it.region) } } return list } is DeclaredReferenceExpression -> { // also color it, if it's on its own if (original == node) { - node.location?.let { list += Pair(styles.identifier!!, it.region) } + node.location?.let { styles.identifier?.let { id -> list += Pair(id, it.region) } } } return list } is DeclarationStatement -> { - fancyType(node, (node.singleDeclaration as? HasType)!!, list) + if (node.singleDeclaration is HasType) + fancyType(node, (node.singleDeclaration as HasType), list) for (declaration in node.declarations) { list.addAll(getFanciesFor(original, declaration)) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationManager.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationManager.kt index bf36d78f08..d619f1be14 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationManager.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationManager.kt @@ -150,7 +150,7 @@ private constructor( component.name = Name(sc) result.addComponent(component) - var sourceLocations: List = this.config.softwareComponents[sc]!! + var sourceLocations: List = this.config.softwareComponents[sc] ?: listOf() var useParallelFrontends = config.useParallelFrontends diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/LanguageFrontend.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/LanguageFrontend.kt index cca4f62d10..7978162ba7 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/LanguageFrontend.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/LanguageFrontend.kt @@ -72,8 +72,8 @@ abstract class LanguageFrontend( @Throws(TranslationException::class) fun parseAll(): List { val units = ArrayList() - for (component in config.softwareComponents.keys) { - for (sourceFile in config.softwareComponents[component]!!) { + for (componentFiles in config.softwareComponents.values) { + for (sourceFile in componentFiles) { units.add(parse(sourceFile)) } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/ProcessedListener.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/ProcessedListener.kt index b3980889df..c6f0a9ab1a 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/ProcessedListener.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/ProcessedListener.kt @@ -95,7 +95,7 @@ open class ProcessedListener { */ open fun registerObjectListener(from: Any, biConsumer: BiConsumer) { if (from in processedMapping) { - biConsumer.accept(from, processedMapping[from]!!) + processedMapping[from]?.let { biConsumer.accept(from, it) } } objectListeners[from] = biConsumer } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/DeclarationHandler.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/DeclarationHandler.kt index 8fb469235d..563e886a20 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/DeclarationHandler.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/DeclarationHandler.kt @@ -749,8 +749,8 @@ class DeclarationHandler(lang: CXXLanguageFrontend) : } // attach to root note - for (incl in allIncludes[translationUnit.filePath]!!) { - node.addDeclaration(includeMap[incl]!!) + for (incl in allIncludes[translationUnit.filePath] ?: listOf()) { + includeMap[incl]?.let { node.addDeclaration(it) } } allIncludes.remove(translationUnit.filePath) // attach to remaining nodes diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/FunctionDeclaration.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/FunctionDeclaration.kt index 4f6cf72107..12ec422885 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/FunctionDeclaration.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/FunctionDeclaration.kt @@ -175,7 +175,7 @@ open class FunctionDeclaration : ValueDeclaration(), DeclarationHolder { fun getBodyStatementAs(i: Int, clazz: Class): T? { if (body is CompoundStatement) { - val statement = (body as CompoundStatement?)!!.statements[i] + val statement = (body as CompoundStatement).statements[i] return if (clazz.isAssignableFrom(statement.javaClass)) clazz.cast(statement) else null } return null diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/VariableDeclaration.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/VariableDeclaration.kt index 3b61ae3786..94b8476f18 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/VariableDeclaration.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/VariableDeclaration.kt @@ -72,7 +72,7 @@ class VariableDeclaration : ValueDeclaration(), HasType.TypeListener, HasInitial // since the type is tied to the declaration, but it is convenient to have the type // information in the initializer, i.e. in a ConstructExpression. if (value is HasType.TypeListener) { - registerTypeListener((value as HasType.TypeListener?)!!) + registerTypeListener(value as HasType.TypeListener) } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edge/PropertyEdge.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edge/PropertyEdge.kt index c839eec489..153a6694f5 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edge/PropertyEdge.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edge/PropertyEdge.kt @@ -100,7 +100,7 @@ open class PropertyEdge : Persistable { } fun addProperties(propertyMap: Map?) { - properties.putAll(propertyMap!!) + propertyMap?.let { properties.putAll(it) } } override fun equals(other: Any?): Boolean { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edge/PropertyEdgeConverter.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edge/PropertyEdgeConverter.kt index 121a24fc78..5f966581cd 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edge/PropertyEdgeConverter.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edge/PropertyEdgeConverter.kt @@ -56,10 +56,8 @@ class PropertyEdgeConverter : CompositeAttributeConverter> override fun toGraphProperties(value: Map): Map { val result: MutableMap = HashMap() for ((key, propertyValue) in value) { - if (serializer.containsKey(propertyValue!!.javaClass.name)) { - val serializedProperty: Any = - serializer[propertyValue.javaClass.name]!!.apply(propertyValue) - result[key.name] = serializedProperty + if (propertyValue != null && serializer.containsKey(propertyValue.javaClass.name)) { + result[key.name] = serializer[propertyValue.javaClass.name]?.apply(propertyValue) } else { result[key.name] = propertyValue } @@ -71,7 +69,8 @@ class PropertyEdgeConverter : CompositeAttributeConverter> val result: MutableMap = EnumMap(Properties::class.java) for (prop in Properties.values()) { if (deserializer.containsKey(prop.name)) { - val deserializedProperty = deserializer[prop.name]!!.apply(value[prop.name]!!) + val deserializedProperty = + value[prop.name]?.let { deserializer[prop.name]?.apply(it) } result[prop] = deserializedProperty } else { result[prop] = value[prop.name] diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/scopes/LoopScope.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/scopes/LoopScope.kt index 5a4f0dcf68..8fa03d867c 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/scopes/LoopScope.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/scopes/LoopScope.kt @@ -61,7 +61,7 @@ class LoopScope(loopStatement: Statement) : else -> { LOGGER.error( "Currently the component {} is not supported as loop scope.", - astNode!!.javaClass + astNode?.javaClass ) ArrayList() } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/BinaryOperator.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/BinaryOperator.kt index d2df7c63d6..cf6e13ca5f 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/BinaryOperator.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/BinaryOperator.kt @@ -130,12 +130,9 @@ class BinaryOperator : } val previous = type if (operatorCode == "=") { - if ( - src == rhs && - lhs.type is NumericType && - src.type is NumericType && - (lhs.type as NumericType).bitWidth!! < (src.type as NumericType).bitWidth!! - ) { + val srcWidth = (src.type as? NumericType)?.bitWidth + val lhsWidth = (lhs.type as? NumericType)?.bitWidth + if (src == rhs && lhsWidth != null && srcWidth != null && lhsWidth < srcWidth) { // Do not propagate anything if the new type is too big for the current type. return } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/CallExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/CallExpression.kt index 37fdd0abb6..d31602de2d 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/CallExpression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/CallExpression.kt @@ -200,9 +200,9 @@ open class CallExpression : Expression(), HasType.TypeListener, SecondaryTypeEdg private fun replaceTypeTemplateParameter(oldType: Type?, newType: Type) { for (i in templateParameterEdges?.indices ?: listOf()) { - val propertyEdge = templateParameterEdges!![i] - if (propertyEdge.end == oldType) { - propertyEdge.end = newType + val propertyEdge = templateParameterEdges?.get(i) + if (propertyEdge?.end == oldType) { + propertyEdge?.end = newType } } } @@ -224,7 +224,7 @@ open class CallExpression : Expression(), HasType.TypeListener, SecondaryTypeEdg val propertyEdge = PropertyEdge(this, templateParam) propertyEdge.addProperty(Properties.INDEX, templateParameters.size) propertyEdge.addProperty(Properties.INSTANTIATION, templateInitialization) - templateParameterEdges!!.add(propertyEdge) + templateParameterEdges?.add(propertyEdge) template = true } } @@ -237,7 +237,7 @@ open class CallExpression : Expression(), HasType.TypeListener, SecondaryTypeEdg templateParameterEdges = mutableListOf() } - for (edge in templateParameterEdges!!) { + for (edge in templateParameterEdges ?: listOf()) { if ( edge.getProperty(Properties.INSTANTIATION) != null && (edge.getProperty(Properties.INSTANTIATION) == @@ -248,9 +248,9 @@ open class CallExpression : Expression(), HasType.TypeListener, SecondaryTypeEdg } } - for (i in templateParameterEdges!!.size until orderedInitializationSignature.size) { + for (i in (templateParameterEdges?.size ?: 0) until orderedInitializationSignature.size) { val propertyEdge = PropertyEdge(this, orderedInitializationSignature[i]) - propertyEdge.addProperty(Properties.INDEX, templateParameterEdges!!.size) + propertyEdge.addProperty(Properties.INDEX, templateParameterEdges?.size) propertyEdge.addProperty( Properties.INSTANTIATION, initializationType.getOrDefault( @@ -258,7 +258,7 @@ open class CallExpression : Expression(), HasType.TypeListener, SecondaryTypeEdg TemplateInitialization.UNKNOWN ) ) - templateParameterEdges!!.add(propertyEdge) + templateParameterEdges?.add(propertyEdge) } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/DeclaredReferenceExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/DeclaredReferenceExpression.kt index 08a0006d5e..3c9b839fc8 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/DeclaredReferenceExpression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/DeclaredReferenceExpression.kt @@ -58,7 +58,7 @@ open class DeclaredReferenceExpression : Expression(), HasType.TypeListener { current.unregisterTypeListener(this) } if (current is HasType.TypeListener) { - unregisterTypeListener((current as HasType.TypeListener?)!!) + unregisterTypeListener(current as HasType.TypeListener) } } @@ -94,10 +94,9 @@ open class DeclaredReferenceExpression : Expression(), HasType.TypeListener { * */ fun getRefersToAs(clazz: Class): T? { - if (refersTo == null) { - return null - } - return if (clazz.isAssignableFrom(refersTo!!.javaClass)) clazz.cast(refersTo) else null + return if (refersTo?.javaClass?.let { clazz.isAssignableFrom(it) } == true) + clazz.cast(refersTo) + else null } override fun typeChanged(src: HasType, root: MutableList, oldType: Type) { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/UnaryOperator.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/UnaryOperator.kt index 5e2829acd3..bed0cc6fe5 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/UnaryOperator.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/UnaryOperator.kt @@ -127,7 +127,7 @@ class UnaryOperator : Expression(), HasType.TypeListener { newType = src.propagationType.dereference() } - input.setType(newType!!, mutableListOf(this)) + newType?.let { input.setType(it, mutableListOf(this)) } } if (previous != type) { type.typeOrigin = Type.Origin.DATAFLOW diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/IncompleteType.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/IncompleteType.kt index b9b9772dd4..7c0b543afa 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/IncompleteType.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/IncompleteType.kt @@ -38,7 +38,7 @@ import java.util.* */ class IncompleteType : Type { constructor() : super("void", null) - constructor(type: Type?) : super(type!!) + constructor(type: Type?) : super(type) /** @return PointerType to a IncompleteType, e.g. void* */ override fun reference(pointer: PointerOrigin?): Type { 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 4d608d6b0c..e59a39043a 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 @@ -58,7 +58,7 @@ class PointerType : Type, SecondOrderType { this.elementType = elementType } - constructor(type: Type?, elementType: Type, pointerOrigin: PointerOrigin?) : super(type!!) { + constructor(type: Type?, elementType: Type, pointerOrigin: PointerOrigin?) : super(type) { language = elementType.language if (pointerOrigin == PointerOrigin.ARRAY) { name = elementType.name.append("[]") diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/Type.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/Type.kt index 7bf5067224..971723c2f3 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/Type.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/Type.kt @@ -57,7 +57,7 @@ abstract class Type : Node { } constructor(typeName: String?) { - name = language.parseName(typeName!!) + name = language.parseName(typeName ?: UNKNOWN_TYPE_STRING) typeOrigin = Origin.UNRESOLVED } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CXXCallResolverHelper.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CXXCallResolverHelper.kt index 52a1fea81e..9d9b1c5bad 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CXXCallResolverHelper.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CXXCallResolverHelper.kt @@ -370,8 +370,10 @@ fun applyTemplateInstantiation( // Template. for ((declaration) in initializationSignature) { if (declaration is ParamVariableDeclaration) { - declaration.addPrevDFG(initializationSignature[declaration]!!) - initializationSignature[declaration]!!.addNextDFG(declaration) + initializationSignature[declaration]?.let { + declaration.addPrevDFG(it) + it.addNextDFG(declaration) // TODO: This should be unnecessary + } } } 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 88fa875a79..7c8cfc3893 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 @@ -94,7 +94,7 @@ open class TypeResolver : Pass() { // ReferencesTypes if (type.root in typeState) { - if (type !in typeState[type.root]!!) { + if (type !in (typeState[type.root] ?: listOf())) { typeState[type.root]?.add(type) addType((type as SecondOrderType).elementType) } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/VariableUsageResolver.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/VariableUsageResolver.kt index e94f81b0dc..a304060c91 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/VariableUsageResolver.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/VariableUsageResolver.kt @@ -323,7 +323,7 @@ open class VariableUsageResolver : SymbolResolverPass() { var member: FieldDeclaration? = null if (containingClass !is UnknownType && containingClass.name in recordMap) { member = - recordMap[containingClass.name]!! + recordMap[containingClass.name] .fields .filter { it.name.lastPartsMatch(reference.name) } .map { it.definition } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/Inference.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/Inference.kt index 19f9b33514..ec97a07ed3 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/Inference.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/Inference.kt @@ -158,8 +158,8 @@ class Inference(val start: Node, val scopeManager: ScopeManager) : scopeManager.enterScope(function) for (i in signature.indices) { - val targetType = signature[i] - val paramName = generateParamName(i, targetType!!) + val targetType = signature[i] ?: UnknownType.getUnknownType(function.language) + val paramName = generateParamName(i, targetType) val param = newParamVariableDeclaration(paramName, targetType, false, "") param.argumentIndex = i diff --git a/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/DeclarationHandler.kt b/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/DeclarationHandler.kt index 4bdd678251..8241eeca80 100644 --- a/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/DeclarationHandler.kt +++ b/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/DeclarationHandler.kt @@ -25,7 +25,6 @@ */ package de.fraunhofer.aisec.cpg.frontends.java -import com.github.javaparser.ast.Modifier import com.github.javaparser.ast.body.* import com.github.javaparser.ast.body.ConstructorDeclaration import com.github.javaparser.ast.body.MethodDeclaration @@ -33,9 +32,7 @@ import com.github.javaparser.ast.expr.Expression import com.github.javaparser.ast.stmt.BlockStmt import com.github.javaparser.ast.stmt.ReturnStmt import com.github.javaparser.ast.stmt.Statement -import com.github.javaparser.ast.type.ClassOrInterfaceType import com.github.javaparser.ast.type.ReferenceType -import com.github.javaparser.ast.type.TypeParameter import com.github.javaparser.resolution.UnsolvedSymbolException import de.fraunhofer.aisec.cpg.frontends.Handler import de.fraunhofer.aisec.cpg.frontends.HandlerInterface @@ -49,6 +46,7 @@ import de.fraunhofer.aisec.cpg.graph.scopes.RecordScope 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.Type +import de.fraunhofer.aisec.cpg.graph.types.UnknownType import java.util.function.Supplier import java.util.stream.Collectors import kotlin.collections.set @@ -197,21 +195,15 @@ open class DeclarationHandler(lang: JavaLanguageFrontend) : val recordDeclaration = this.newRecordDeclaration(fqn, "class", null, classInterDecl) recordDeclaration.superClasses = classInterDecl.extendedTypes - .stream() - .map { type: ClassOrInterfaceType? -> frontend.getTypeAsGoodAsPossible(type!!) } - .collect(Collectors.toList()) + .map { type -> frontend.getTypeAsGoodAsPossible(type) } + .toMutableList() recordDeclaration.implementedInterfaces = - classInterDecl.implementedTypes - .stream() - .map { type: ClassOrInterfaceType? -> frontend.getTypeAsGoodAsPossible(type!!) } - .collect(Collectors.toList()) + classInterDecl.implementedTypes.map { type -> frontend.getTypeAsGoodAsPossible(type) } + TypeManager.getInstance() .addTypeParameter( recordDeclaration, - classInterDecl.typeParameters - .stream() - .map { t: TypeParameter -> ParameterizedType(t.nameAsString, language) } - .collect(Collectors.toList()) + classInterDecl.typeParameters.map { ParameterizedType(it.nameAsString, language) } ) // TODO: I cannot replicate the old partionedBy logic @@ -299,16 +291,17 @@ open class DeclarationHandler(lang: JavaLanguageFrontend) : // need this // to generate the field. val scope = frontend.scopeManager.currentScope as RecordScope? - if (scope!!.name != null) { - val fieldType = this.parseType(scope.name!!) + if (scope?.name != null) { + val fieldType = + scope.name?.let { this.parseType(it) } ?: UnknownType.getUnknownType(language) // 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, + "this$" + scope.name?.localName, fieldType, - listOf(), + listOf(), null, null, null @@ -327,11 +320,7 @@ open class DeclarationHandler(lang: JavaLanguageFrontend) : // TODO: can field have more than one variable? val variable = fieldDecl.getVariable(0) - val modifiers = - fieldDecl.modifiers - .stream() - .map { modifier: Modifier -> modifier.keyword.asString() } - .collect(Collectors.toList()) + val modifiers = fieldDecl.modifiers.map { modifier -> modifier.keyword.asString() } val joinedModifiers = java.lang.String.join(" ", modifiers) + " " val location = frontend.getLocationFromRawNode(fieldDecl) val initializer = 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 338462f5de..c41e9af33a 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 @@ -35,15 +35,11 @@ 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.Name import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration import de.fraunhofer.aisec.cpg.graph.statements.* import de.fraunhofer.aisec.cpg.graph.statements.expressions.* -import de.fraunhofer.aisec.cpg.graph.types.FunctionType -import de.fraunhofer.aisec.cpg.graph.types.PointerType -import de.fraunhofer.aisec.cpg.graph.types.Type -import de.fraunhofer.aisec.cpg.graph.types.TypeParser +import de.fraunhofer.aisec.cpg.graph.types.* import java.util.function.Supplier import kotlin.collections.set import org.slf4j.LoggerFactory @@ -125,10 +121,8 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : // dimensions are only present if you specify them explicitly, such as new int[1] for (lvl in arrayCreationExpr.levels) { lvl.dimension.ifPresent { - creationExpression.addDimension( - (handle(it) - as? de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression?)!! - ) + (handle(it) as? de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression?) + ?.let { creationExpression.addDimension(it) } } } return creationExpression @@ -154,12 +148,14 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : private fun handleArrayAccessExpr(expr: Expression): ArraySubscriptionExpression { val arrayAccessExpr = expr as ArrayAccessExpr val arraySubsExpression = this.newArraySubscriptionExpression(expr.toString()) - arraySubsExpression.arrayExpression = - (handle(arrayAccessExpr.name) - as de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression?)!! - arraySubsExpression.subscriptExpression = - (handle(arrayAccessExpr.index) - as de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression?)!! + (handle(arrayAccessExpr.name) + as de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression?) + ?.let { arraySubsExpression.arrayExpression = it } + + (handle(arrayAccessExpr.index) + as de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression?) + ?.let { arraySubsExpression.subscriptExpression = it } + return arraySubsExpression } @@ -190,13 +186,14 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : val condition = handle(conditionalExpr.condition) as de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression? + ?: newProblemExpression("Could not parse condition") val thenExpr = handle(conditionalExpr.thenExpr) as de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression? val elseExpr = handle(conditionalExpr.elseExpr) as de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression? - return this.newConditionalExpression(condition!!, thenExpr, elseExpr, superType) + return this.newConditionalExpression(condition, thenExpr, elseExpr, superType) } private fun handleAssignmentExpression(expr: Expression): BinaryOperator { @@ -260,7 +257,7 @@ 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? + 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 @@ -336,7 +333,9 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : fieldAccessExpr.scope ) } else if (scope.isFieldAccessExpr) { - base = handle(scope) as de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression? + 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 @@ -373,7 +372,9 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : } frontend.setCodeAndLocation(base, fieldAccessExpr.scope) } else { - base = handle(scope) as de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression? + base = + handle(scope) as de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression? + ?: newProblemExpression("Could not parse base") } var fieldType: Type? try { @@ -401,7 +402,7 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : val memberExpression = this.newMemberExpression( fieldAccessExpr.name.identifier, - base!!, + base, fieldType, "." // there is only "." in java ) @@ -419,11 +420,11 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : newUnknownType() } val memberExpression = - this.newMemberExpression(fieldAccessExpr.name.identifier, base!!, fieldType, ".") + this.newMemberExpression(fieldAccessExpr.name.identifier, base, fieldType, ".") memberExpression.isStaticAccess = true return memberExpression } - if (base!!.location == null) { + if (base.location == null) { base.location = frontend.getLocationFromRawNode(fieldAccessExpr) } return this.newMemberExpression(fieldAccessExpr.name.identifier, base, fieldType, ".") @@ -728,12 +729,14 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : if (frontend.getQualifiedNameFromImports(qualifiedName) != null) { isStatic = true } - val base: de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression? + val base: de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression // the scope could either be a variable or also the class name (static call!) // thus, only because the scope is present, this is not automatically a member call if (o.isPresent) { val scope = o.get() - base = handle(scope) as de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression? + base = + handle(scope) as de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression? + ?: newProblemExpression("Could not parse base") // If the base directly refers to a record, then this is a static call if (base is DeclaredReferenceExpression && base.refersTo is RecordDeclaration) { @@ -751,14 +754,13 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : // resolved // method val baseType: Type - val baseName: Name? - baseName = + val baseName = if (resolved != null) { this.parseName(resolved.declaringType().qualifiedName) } else { this.parseName(qualifiedName).parent } - baseType = this.parseType(baseName!!) + baseType = this.parseType(baseName ?: Type.UNKNOWN_TYPE_STRING) base = this.newDeclaredReferenceExpression(baseName, baseType) } else { // Since it is possible to omit the "this" keyword, some methods in java do not have @@ -769,7 +771,7 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : base = createImplicitThis() } } - val member = this.newMemberExpression(name, base!!, newUnknownType(), ".") + val member = this.newMemberExpression(name, base, newUnknownType(), ".") frontend.setCodeAndLocation( member, methodCallExpr.name @@ -784,8 +786,10 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : val argument = handle(arguments[i]) as de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression? - argument!!.argumentIndex = i - callExpression.addArgument(argument) + argument?.argumentIndex = i + callExpression.addArgument( + argument ?: newProblemExpression("Could not parse the argument") + ) } return callExpression } diff --git a/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/JavaLanguage.kt b/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/JavaLanguage.kt index cabd99d5bc..0e4acada5d 100644 --- a/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/JavaLanguage.kt +++ b/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/JavaLanguage.kt @@ -103,7 +103,7 @@ open class JavaLanguage : (operation.lhs.type as? IntegerType)?.name?.localName?.equals("char") == true && (operation.rhs.type as? IntegerType)?.name?.localName?.equals("char") == true ) { - return getSimpleTypeOf("int")!! + getSimpleTypeOf("int") ?: UnknownType.getUnknownType(this) } else super.propagateTypeOfBinaryOperation(operation) } diff --git a/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/StatementHandler.kt b/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/StatementHandler.kt index 2ff8f50882..b064812a6e 100644 --- a/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/StatementHandler.kt +++ b/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/StatementHandler.kt @@ -159,7 +159,7 @@ class StatementHandler(lang: JavaLanguageFrontend?) : val variable = frontend.expressionHandler.handle(forEachStmt.variable) val iterable = frontend.expressionHandler.handle(forEachStmt.iterable) if (variable !is DeclarationStatement) { - log.error("Expected a DeclarationStatement but received: {}", variable!!.name) + log.error("Expected a DeclarationStatement but received: {}", variable?.name) } else { statement.variable = variable } @@ -195,25 +195,27 @@ class StatementHandler(lang: JavaLanguageFrontend?) : s?.let { initExprList.addExpression(it) } // can not update location - if (s!!.location == null) { + if (s?.location == null) { continue } if (ofExprList == null) { ofExprList = s.location } - ofExprList!!.region = frontend.mergeRegions(ofExprList.region, s.location!!.region) + ofExprList?.region?.let { ofRegion -> + s.location?.region?.let { + ofExprList?.region = frontend.mergeRegions(ofRegion, it) + } + } } // set code and location of init list - if (statement.location != null && ofExprList != null) { - val initCode = - frontend.getCodeOfSubregion( - statement, - statement.location!!.region, - ofExprList.region - ) - initExprList.location = ofExprList - initExprList.code = initCode + statement.location?.let { location -> + ofExprList?.let { + val initCode = + frontend.getCodeOfSubregion(statement, location.region, it.region) + initExprList.location = ofExprList + initExprList.code = initCode + } } statement.initializerStatement = initExprList } else if (forStmt.initialization.size == 1) { @@ -246,25 +248,27 @@ class StatementHandler(lang: JavaLanguageFrontend?) : s?.let { iterationExprList.addExpression(it) } // can not update location - if (s!!.location == null) { + if (s?.location == null) { continue } if (ofExprList == null) { ofExprList = s.location } - ofExprList!!.region = frontend.mergeRegions(ofExprList.region, s.location!!.region) + ofExprList?.region?.let { ofRegion -> + s.location?.region?.let { + ofExprList.region = frontend.mergeRegions(ofRegion, it) + } + } } // set code and location of init list - if (statement.location != null && ofExprList != null) { - val updateCode = - frontend.getCodeOfSubregion( - statement, - statement.location!!.region, - ofExprList.region - ) - iterationExprList.location = ofExprList - iterationExprList.code = updateCode + statement.location?.let { location -> + ofExprList?.let { + val updateCode = + frontend.getCodeOfSubregion(statement, location.region, it.region) + iterationExprList.location = ofExprList + iterationExprList.code = updateCode + } } statement.iterationStatement = iterationExprList } else if (forStmt.update.size == 1) { @@ -338,7 +342,7 @@ class StatementHandler(lang: JavaLanguageFrontend?) : frontend.scopeManager.enterScope(compoundStatement) for (child in blockStmt.statements) { val statement = handle(child) - compoundStatement.addStatement(statement!!) + statement?.let { compoundStatement.addStatement(it) } } frontend.setCodeAndLocation(compoundStatement, stmt) frontend.scopeManager.leaveScope(compoundStatement) @@ -448,7 +452,7 @@ class StatementHandler(lang: JavaLanguageFrontend?) : val newCode = StringBuilder(startToken.text) var current = startToken do { - current = current!!.nextToken.orElse(null) + current = current?.nextToken?.orElse(null) if (current == null) { break } @@ -487,7 +491,9 @@ class StatementHandler(lang: JavaLanguageFrontend?) : compoundStatement.addStatement(handleCaseDefaultStatement(caseExp, sentry)) } for (subStmt in sentry.statements) { - compoundStatement.addStatement(handle(subStmt)!!) + compoundStatement.addStatement( + handle(subStmt) ?: ProblemExpression("Could not parse statement") + ) } } switchStatement.statement = compoundStatement diff --git a/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/DeclarationHandler.kt b/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/DeclarationHandler.kt index 6aba2cf483..177c6189fd 100644 --- a/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/DeclarationHandler.kt +++ b/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/DeclarationHandler.kt @@ -260,8 +260,9 @@ class DeclarationHandler(lang: LLVMIRLanguageFrontend) : alreadyVisited: MutableMap ): String { val typeStr = LLVMPrintTypeToString(typeRef).string - if (typeStr in frontend.typeCache && frontend.typeCache[typeStr] != null) { - return frontend.typeCache[typeStr]!!.name.localName + if (typeStr in frontend.typeCache) { + val localName = frontend.typeCache[typeStr]?.name?.localName + if (localName != null) return localName } var name = "literal" diff --git a/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/LLVMIRLanguageFrontend.kt b/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/LLVMIRLanguageFrontend.kt index 5066e43252..72b09e660f 100644 --- a/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/LLVMIRLanguageFrontend.kt +++ b/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/LLVMIRLanguageFrontend.kt @@ -188,14 +188,12 @@ class LLVMIRLanguageFrontend( alreadyVisited: MutableMap = mutableMapOf() ): Type { val typeStr = LLVMPrintTypeToString(typeRef).string - if (typeStr in typeCache && typeCache[typeStr] != null) { - return typeCache[typeStr]!! + if (typeStr in typeCache) { + val result = typeCache[typeStr] + if (result != null) return result } - if (typeRef in alreadyVisited && alreadyVisited[typeRef] != null) { - return alreadyVisited[typeRef]!! - } else if (typeRef in alreadyVisited) { - // Recursive call but we can't resolve it. - return newUnknownType() + if (typeRef in alreadyVisited) { + return alreadyVisited[typeRef] ?: newUnknownType() } alreadyVisited[typeRef] = null val res: Type = diff --git a/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/StatementHandler.kt b/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/StatementHandler.kt index dfca2cc049..15dcc8c8dd 100644 --- a/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/StatementHandler.kt +++ b/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/StatementHandler.kt @@ -1390,9 +1390,9 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : } if (labelMap.keys.size == 1) { // We only have a single pair, so we insert a declaration in that one BB. - val key = labelMap.keys.elementAt(0) + val (key, value) = labelMap.entries.elementAt(0) val basicBlock = key.subStatement as? CompoundStatement - val decl = declarationOrNot(labelMap[key]!!, instr) + val decl = declarationOrNot(value, instr) flatAST.addAll(SubgraphWalker.flattenAST(decl)) val mutableStatements = basicBlock?.statements?.toMutableList() mutableStatements?.add(basicBlock.statements.size - 1, decl) @@ -1436,10 +1436,10 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : mutableFunctionStatements.add(0, declStatement) firstBB.statements = mutableFunctionStatements - for (l in labelMap.keys) { + for ((l, r) in labelMap) { // Now, we iterate over all the basic blocks and add an assign statement. val assignment = newBinaryOperator("=", code) - assignment.rhs = labelMap[l]!! + assignment.rhs = r assignment.lhs = newDeclaredReferenceExpression(varName, type, code) (assignment.lhs as DeclaredReferenceExpression).type = type (assignment.lhs as DeclaredReferenceExpression).unregisterTypeListener(assignment) diff --git a/cpg-neo4j/src/main/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/Application.kt b/cpg-neo4j/src/main/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/Application.kt index 01931ee4d8..32f775c6d9 100644 --- a/cpg-neo4j/src/main/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/Application.kt +++ b/cpg-neo4j/src/main/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/Application.kt @@ -236,7 +236,7 @@ class Application : Callable { private var passClassMap = passClassList.map { Pair(it.simpleName, it) }.toMap() /** The list of available passes that can be registered. */ private val passList: List - get() = passClassList.map { it.simpleName!! } + get() = passClassList.mapNotNull { it.simpleName } /** * Pushes the whole translationResult to the neo4j db. @@ -387,13 +387,15 @@ class Application : Callable { if (pass !in passClassMap) { throw ConfigurationException("Asked to produce unknown pass") } - translationConfiguration.registerPass(passClassMap[pass]!!.createInstance()) + passClassMap[pass]?.let { + translationConfiguration.registerPass(it.createInstance()) + } } } } - if (mutuallyExclusiveParameters.jsonCompilationDatabase != null) { - val db = fromFile(mutuallyExclusiveParameters.jsonCompilationDatabase!!) + mutuallyExclusiveParameters.jsonCompilationDatabase?.let { + val db = fromFile(it) if (db.isNotEmpty()) { translationConfiguration.useCompilationDatabase(db) translationConfiguration.sourceLocations(db.sourceFiles) From 2767a235481b52c3cf759a9ba8733ab8f80bc6ff Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 26 May 2023 08:32:09 +0000 Subject: [PATCH 049/143] Update dependency webpack to v5.84.1 (#1179) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Christian Banse --- .../src/main/nodejs/package.json | 2 +- .../src/main/nodejs/yarn.lock | 28 +++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/cpg-language-typescript/src/main/nodejs/package.json b/cpg-language-typescript/src/main/nodejs/package.json index b3f4856330..339f988c4e 100644 --- a/cpg-language-typescript/src/main/nodejs/package.json +++ b/cpg-language-typescript/src/main/nodejs/package.json @@ -11,7 +11,7 @@ }, "license": "Apache-2.0", "devDependencies": { - "webpack": "5.83.1", + "webpack": "5.84.1", "webpack-cli": "5.1.0" } } \ No newline at end of file diff --git a/cpg-language-typescript/src/main/nodejs/yarn.lock b/cpg-language-typescript/src/main/nodejs/yarn.lock index ff43cd090e..b73ddc457d 100644 --- a/cpg-language-typescript/src/main/nodejs/yarn.lock +++ b/cpg-language-typescript/src/main/nodejs/yarn.lock @@ -242,10 +242,10 @@ resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== -acorn-import-assertions@^1.7.6: - version "1.7.6" - resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.7.6.tgz#580e3ffcae6770eebeec76c3b9723201e9d01f78" - integrity sha512-FlVvVFA1TX6l3lp8VjDnYYq7R1nyW6x3svAt4nDgrWQ9SBaSh9CnbwgSUTasgfNfOG5HlM1ehugCvM+hjo56LA== +acorn-import-assertions@^1.9.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz#507276249d684797c84e0734ef84860334cfb1ac" + integrity sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA== acorn@^8.5.0, acorn@^8.7.1: version "8.8.0" @@ -336,10 +336,10 @@ electron-to-chromium@^1.3.723: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.772.tgz#fd1ed39f9f3149f62f581734e4f026e600369479" integrity sha512-X/6VRCXWALzdX+RjCtBU6cyg8WZgoxm9YA02COmDOiNJEZ59WkQggDbWZ4t/giHi/3GS+cvdrP6gbLISANAGYA== -enhanced-resolve@^5.14.0: - version "5.14.0" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.14.0.tgz#0b6c676c8a3266c99fa281e4433a706f5c0c61c4" - integrity sha512-+DCows0XNwLDcUhbFJPdlQEVnT2zXlCv7hPxemTz86/O+B/hCQ+mb7ydkPKiflpVraqLPCAfu7lDy+hBXueojw== +enhanced-resolve@^5.14.1: + version "5.14.1" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.14.1.tgz#de684b6803724477a4af5d74ccae5de52c25f6b3" + integrity sha512-Vklwq2vDKtl0y/vtwjSesgJ5MYS7Etuk5txS8VdKL4AOS1aUlD96zqIfsOSLQsdv3xgMRbtkWM8eG9XDfKUPow== dependencies: graceful-fs "^4.2.4" tapable "^2.2.0" @@ -772,10 +772,10 @@ webpack-sources@^3.2.3: resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== -webpack@5.83.1: - version "5.83.1" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.83.1.tgz#fcb69864a0669ac3539a471081952c45b15d1c40" - integrity sha512-TNsG9jDScbNuB+Lb/3+vYolPplCS3bbEaJf+Bj0Gw4DhP3ioAflBb1flcRt9zsWITyvOhM96wMQNRWlSX52DgA== +webpack@5.84.1: + version "5.84.1" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.84.1.tgz#d4493acdeca46b26ffc99d86d784cabfeb925a15" + integrity sha512-ZP4qaZ7vVn/K8WN/p990SGATmrL1qg4heP/MrVneczYtpDGJWlrgZv55vxaV2ul885Kz+25MP2kSXkPe3LZfmg== dependencies: "@types/eslint-scope" "^3.7.3" "@types/estree" "^1.0.0" @@ -783,10 +783,10 @@ webpack@5.83.1: "@webassemblyjs/wasm-edit" "^1.11.5" "@webassemblyjs/wasm-parser" "^1.11.5" acorn "^8.7.1" - acorn-import-assertions "^1.7.6" + acorn-import-assertions "^1.9.0" browserslist "^4.14.5" chrome-trace-event "^1.0.2" - enhanced-resolve "^5.14.0" + enhanced-resolve "^5.14.1" es-module-lexer "^1.2.1" eslint-scope "5.1.1" events "^3.2.0" From c25472476b42e673ba54b5b4a99df477180bf3e0 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 26 May 2023 13:21:12 +0000 Subject: [PATCH 050/143] Update spotless to v6.19.0 (#1180) * Update spotless to v6.19.0 * Formatted with new spotless version --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Christian Banse --- .../de/fraunhofer/aisec/cpg/analysis/NumberSet.kt | 12 ++++++++++++ .../de/fraunhofer/aisec/cpg/analysis/fsm/DFA.kt | 1 + .../de/fraunhofer/aisec/cpg/analysis/fsm/FSM.kt | 1 + .../fraunhofer/aisec/cpg/ConfigurationException.kt | 1 + .../fraunhofer/aisec/cpg/InferenceConfiguration.kt | 3 +++ .../fraunhofer/aisec/cpg/TranslationConfiguration.kt | 2 ++ .../de/fraunhofer/aisec/cpg/TranslationResult.kt | 1 + .../de/fraunhofer/aisec/cpg/frontends/Handler.kt | 1 + .../aisec/cpg/frontends/LanguageFrontend.kt | 1 + .../aisec/cpg/frontends/TranslationException.kt | 1 + .../fraunhofer/aisec/cpg/graph/DeclarationHolder.kt | 1 + .../de/fraunhofer/aisec/cpg/graph/Extensions.kt | 1 + .../cpg/graph/declarations/RecordDeclaration.kt | 3 +++ .../fraunhofer/aisec/cpg/graph/edge/PropertyEdge.kt | 1 + .../fraunhofer/aisec/cpg/graph/scopes/LoopScope.kt | 1 + .../fraunhofer/aisec/cpg/graph/scopes/ScopeTraits.kt | 1 + .../fraunhofer/aisec/cpg/graph/scopes/SwitchScope.kt | 1 + .../graph/statements/expressions/MemberExpression.kt | 1 + .../graph/statements/expressions/NewExpression.kt | 1 + .../de/fraunhofer/aisec/cpg/graph/types/HasType.kt | 8 ++++++++ .../aisec/cpg/graph/types/IncompleteType.kt | 1 + .../de/fraunhofer/aisec/cpg/graph/types/Type.kt | 6 ++++++ .../fraunhofer/aisec/cpg/helpers/SubgraphWalker.kt | 1 + .../de/fraunhofer/aisec/cpg/processing/IVisitor.kt | 1 + .../de/fraunhofer/aisec/cpg/graph/types/TypeTests.kt | 1 + .../fraunhofer/aisec/cpg/processing/VisitorTest.kt | 1 + .../fraunhofer/aisec/cpg/frontends/TestLanguage.kt | 1 + .../aisec/cpg/frontends/java/JavaLanguage.kt | 1 + .../cpg/frontends/java/JavaLanguageFrontendTest.kt | 1 + .../aisec/cpg/frontends/python/PythonFrontendTest.kt | 1 + gradle/libs.versions.toml | 2 +- 31 files changed, 59 insertions(+), 1 deletion(-) diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/NumberSet.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/NumberSet.kt index b9cd312214..43606cc8a4 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/NumberSet.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/NumberSet.kt @@ -27,9 +27,13 @@ package de.fraunhofer.aisec.cpg.analysis abstract class NumberSet { abstract fun min(): Long + abstract fun max(): Long + abstract fun addValue(value: Long) + abstract fun maybe(value: Long): Boolean + abstract fun clear() } @@ -45,15 +49,19 @@ class Interval : NumberSet() { max = value } } + override fun min(): Long { return min } + override fun max(): Long { return max } + override fun maybe(value: Long): Boolean { return value in min..max } + override fun clear() { min = Long.MAX_VALUE max = Long.MIN_VALUE @@ -64,15 +72,19 @@ class ConcreteNumberSet(var values: MutableSet = mutableSetOf()) : NumberS override fun addValue(value: Long) { values.add(value) } + override fun min(): Long { return values.minOrNull() ?: Long.MAX_VALUE } + override fun max(): Long { return values.maxOrNull() ?: Long.MIN_VALUE } + override fun maybe(value: Long): Boolean { return value in values } + override fun clear() { values.clear() } diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/fsm/DFA.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/fsm/DFA.kt index 421c20392e..039269654d 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/fsm/DFA.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/fsm/DFA.kt @@ -38,6 +38,7 @@ class DFA(states: Set = setOf()) : FSM(states) { private val _executionTrace = mutableListOf() val executionTrace: List get() = _executionTrace + val currentState: State? get() = executionTrace.lastOrNull()?.edge?.nextState ?: states.singleOrNull { it.isStart } diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/fsm/FSM.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/fsm/FSM.kt index 74f6ebcf78..3e2d4a78e0 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/fsm/FSM.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/fsm/FSM.kt @@ -31,6 +31,7 @@ sealed class FSM(states: Set) { private val _states: MutableSet = mutableSetOf() val states: Set get() = _states + private val nextStateName get() = if (states.isEmpty()) 1 else states.maxOf { it.name } + 1 diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/ConfigurationException.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/ConfigurationException.kt index 32d2517dd3..8ced7325ff 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/ConfigurationException.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/ConfigurationException.kt @@ -31,5 +31,6 @@ package de.fraunhofer.aisec.cpg */ class ConfigurationException : Exception { constructor(message: String) : super(message) + constructor(ex: Exception) : super(ex) } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/InferenceConfiguration.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/InferenceConfiguration.kt index 519f69000d..1773cfdb45 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/InferenceConfiguration.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/InferenceConfiguration.kt @@ -52,10 +52,13 @@ private constructor( var inferDfgForUnresolvedCalls: Boolean = true ) { fun guessCastExpressions(guess: Boolean) = apply { this.guessCastExpressions = guess } + fun inferRecords(infer: Boolean) = apply { this.inferRecords = infer } + fun inferDfgForUnresolvedCalls(infer: Boolean) = apply { this.inferDfgForUnresolvedCalls = infer } + fun build() = InferenceConfiguration(guessCastExpressions, inferRecords, inferDfgForUnresolvedCalls) } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationConfiguration.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationConfiguration.kt index 437da671e9..17fc3b1d83 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationConfiguration.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationConfiguration.kt @@ -228,6 +228,7 @@ private constructor( private var compilationDatabase: CompilationDatabase? = null private var matchCommentsToNodes = false private var addIncludesToGraph = true + fun symbols(symbols: Map): Builder { this.symbols = symbols return this @@ -702,6 +703,7 @@ private constructor( companion object { private val log = LoggerFactory.getLogger(TranslationConfiguration::class.java) + fun builder(): Builder { return Builder() } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationResult.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationResult.kt index 6866f515bd..3cccc9cd38 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationResult.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationResult.kt @@ -161,6 +161,7 @@ class TranslationResult( ) return result } + override val config: TranslationConfiguration get() = translationManager.config diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/Handler.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/Handler.kt index 2a6d582284..fefe5b5cd4 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/Handler.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/Handler.kt @@ -177,6 +177,7 @@ abstract class Handler( override val scope: Scope? get() = frontend.scope + override val namespace: Name? get() = frontend.namespace diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/LanguageFrontend.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/LanguageFrontend.kt index 7978162ba7..71910958d6 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/LanguageFrontend.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/LanguageFrontend.kt @@ -108,6 +108,7 @@ abstract class LanguageFrontend( * @return the location */ abstract fun getLocationFromRawNode(astNode: T): PhysicalLocation? + override fun setCodeAndLocation(cpgNode: N, astNode: S?) { if (cpgNode is Node && astNode != null) { if (config.codeInNodes) { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/TranslationException.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/TranslationException.kt index e589ff514c..2e27cee290 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/TranslationException.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/TranslationException.kt @@ -34,5 +34,6 @@ package de.fraunhofer.aisec.cpg.frontends */ class TranslationException : Exception { constructor(ex: Exception) : super(ex) + constructor(message: String) : super(message) } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/DeclarationHolder.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/DeclarationHolder.kt index 71a940b561..586681978d 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/DeclarationHolder.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/DeclarationHolder.kt @@ -37,6 +37,7 @@ interface DeclarationHolder { * @param declaration the declaration */ fun addDeclaration(declaration: Declaration) + fun addIfNotContains(collection: MutableCollection, declaration: T) { if (!collection.contains(declaration)) { collection.add(declaration) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt index 8262adf455..92c5fcec54 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt @@ -196,6 +196,7 @@ class DeclarationNotFound(message: String) : Exception(message) class FulfilledAndFailedPaths(val fulfilled: List>, val failed: List>) { operator fun component1(): List> = fulfilled + operator fun component2(): List> = failed } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/RecordDeclaration.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/RecordDeclaration.kt index 37bf64944d..0b997e3357 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/RecordDeclaration.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/RecordDeclaration.kt @@ -118,6 +118,7 @@ class RecordDeclaration : Declaration(), DeclarationHolder, StatementHolder { fun removeMethod(methodDeclaration: MethodDeclaration?) { methodEdges.removeIf { it.end == methodDeclaration } } + fun addConstructor(constructorDeclaration: ConstructorDeclaration) { addIfNotContains(constructorEdges, constructorDeclaration) } @@ -129,6 +130,7 @@ class RecordDeclaration : Declaration(), DeclarationHolder, StatementHolder { fun removeRecord(recordDeclaration: RecordDeclaration) { recordEdges.removeIf { it.end == recordDeclaration } } + fun removeTemplate(templateDeclaration: TemplateDeclaration?) { templateEdges.removeIf { it.end == templateDeclaration } } @@ -143,6 +145,7 @@ class RecordDeclaration : Declaration(), DeclarationHolder, StatementHolder { list.addAll(templates) return list } + val superTypes: List /** * Combines both implemented interfaces and extended classes. This is most commonly what you diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edge/PropertyEdge.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edge/PropertyEdge.kt index 153a6694f5..7db83bbf7b 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edge/PropertyEdge.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edge/PropertyEdge.kt @@ -84,6 +84,7 @@ open class PropertyEdge : Persistable { /** Map containing all properties of an edge */ @Convert(PropertyEdgeConverter::class) private var properties: MutableMap + fun getProperty(property: Properties): Any? { return properties.getOrDefault(property, null) } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/scopes/LoopScope.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/scopes/LoopScope.kt index 8fa03d867c..24c1a374b9 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/scopes/LoopScope.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/scopes/LoopScope.kt @@ -89,6 +89,7 @@ class LoopScope(loopStatement: Statement) : mutableListOf() } } + private val breaks = mutableListOf() private val continues = mutableListOf() diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/scopes/ScopeTraits.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/scopes/ScopeTraits.kt index 9dbf43d899..3aec62b34d 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/scopes/ScopeTraits.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/scopes/ScopeTraits.kt @@ -31,6 +31,7 @@ import de.fraunhofer.aisec.cpg.graph.statements.ContinueStatement /** Represents scopes that can be interrupted by a [BreakStatement]. */ interface Breakable { fun addBreakStatement(breakStatement: BreakStatement) + val breakStatements: List } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/scopes/SwitchScope.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/scopes/SwitchScope.kt index 2b32d390d8..c57c9de64b 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/scopes/SwitchScope.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/scopes/SwitchScope.kt @@ -31,6 +31,7 @@ import de.fraunhofer.aisec.cpg.graph.statements.SwitchStatement class SwitchScope(switchStatement: SwitchStatement) : ValueDeclarationScope(switchStatement), Breakable { private val breaks = mutableListOf() + override fun addBreakStatement(breakStatement: BreakStatement) { breaks.add(breakStatement) } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/MemberExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/MemberExpression.kt index 495accef77..5753976c99 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/MemberExpression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/MemberExpression.kt @@ -50,6 +50,7 @@ class MemberExpression : DeclaredReferenceExpression(), HasBase { } override var operatorCode: String? = null + override fun toString(): String { return ToStringBuilder(this, TO_STRING_STYLE) .appendSuper(super.toString()) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/NewExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/NewExpression.kt index b44c6c6a95..8d6cd0c4de 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/NewExpression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/NewExpression.kt @@ -44,6 +44,7 @@ class NewExpression : Expression(), HasInitializer { @Relationship(value = "TEMPLATE_PARAMETERS", direction = Relationship.Direction.OUTGOING) @AST var templateParameters: List? = null + override fun toString(): String { return ToStringBuilder(this, TO_STRING_STYLE) .appendSuper(super.toString()) 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 bb9aca0955..4d28388065 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 @@ -42,6 +42,7 @@ interface HasType { * @param type new type */ fun updateType(type: Type) + fun updatePossibleSubtypes(types: List) /** @@ -54,6 +55,7 @@ interface HasType { * event and subsequent type listeners receive the current node as their root. */ fun setType(type: Type, root: MutableList?) + var possibleSubTypes: List /** @@ -64,9 +66,13 @@ interface HasType { * @param root A list of already seen nodes which is used for detecting loops. */ fun setPossibleSubTypes(possibleSubTypes: List, root: MutableList) + fun registerTypeListener(listener: TypeListener) + fun unregisterTypeListener(listener: TypeListener) + val typeListeners: Set + fun refreshType() /** @@ -76,8 +82,10 @@ interface HasType { * @param type the more precise type */ fun resetTypes(type: Type) + interface TypeListener { fun typeChanged(src: HasType, root: MutableList, oldType: Type) + fun possibleSubTypesChanged(src: HasType, root: MutableList) } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/IncompleteType.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/IncompleteType.kt index 7c0b543afa..59b440e376 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/IncompleteType.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/IncompleteType.kt @@ -38,6 +38,7 @@ import java.util.* */ class IncompleteType : Type { constructor() : super("void", null) + constructor(type: Type?) : super(type) /** @return PointerType to a IncompleteType, e.g. void* */ diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/Type.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/Type.kt index 971723c2f3..e32b54b51f 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/Type.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/Type.kt @@ -48,8 +48,10 @@ abstract class Type : Node { @Relationship(value = "SUPER_TYPE", direction = Relationship.Direction.OUTGOING) var superTypes = mutableSetOf() protected set + var isPrimitive = false protected set + open var typeOrigin: Origin? = null constructor() { @@ -102,7 +104,9 @@ abstract class Type : Node { * pointer type we obtain the type the pointer is pointing towards */ abstract fun dereference(): Type + open fun refreshNames() {} + var root: Type /** * Obtain the root Type Element for a Type Chain (follows Pointer and ReferenceTypes until a @@ -131,12 +135,14 @@ abstract class Type : Node { val typeName: String get() = name.toString() + open val referenceDepth: Int /** * @return number of steps that are required in order to traverse the type chain until the * root is reached */ get() = 0 + val isFirstOrderType: Boolean /** * @return True if the Type parameter t is a FirstOrderType (Root of a chain) and not a 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 a7015f0c6f..c8b08bf36f 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 @@ -406,6 +406,7 @@ object SubgraphWalker { * the root node of the current declaration scope, the currently visited node. */ private val handlers = mutableListOf>() + fun clearCallbacks() { handlers.clear() } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/processing/IVisitor.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/processing/IVisitor.kt index 2842c6f3fb..7739aed58f 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/processing/IVisitor.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/processing/IVisitor.kt @@ -35,6 +35,7 @@ import java.lang.reflect.InvocationTargetException */ abstract class IVisitor> { @JvmField val visited = IdentitySet() + open fun visit(t: V) { try { val mostSpecificVisit = this.javaClass.getMethod("visit", t::class.java) diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/types/TypeTests.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/types/TypeTests.kt index dbe059af6d..96cb16f5e3 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/types/TypeTests.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/types/TypeTests.kt @@ -425,6 +425,7 @@ internal class TypeTests : BaseTest() { TypeManager.getInstance().getCommonType(listOf(level2, level2b), provider) ) } + @Test @Throws(Exception::class) fun graphTest() { diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/processing/VisitorTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/processing/VisitorTest.kt index 0ef3ea1f28..3ea2a6942a 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/processing/VisitorTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/processing/VisitorTest.kt @@ -134,6 +134,7 @@ class VisitorTest : BaseTest() { companion object { private var recordDecl: RecordDeclaration? = null + @BeforeAll @JvmStatic @Throws( diff --git a/cpg-core/src/testFixtures/kotlin/de/fraunhofer/aisec/cpg/frontends/TestLanguage.kt b/cpg-core/src/testFixtures/kotlin/de/fraunhofer/aisec/cpg/frontends/TestLanguage.kt index a618819497..09ad8082ea 100644 --- a/cpg-core/src/testFixtures/kotlin/de/fraunhofer/aisec/cpg/frontends/TestLanguage.kt +++ b/cpg-core/src/testFixtures/kotlin/de/fraunhofer/aisec/cpg/frontends/TestLanguage.kt @@ -61,6 +61,7 @@ open class TestLanguage(namespaceDelimiter: String = "::") : Language Date: Sat, 27 May 2023 20:57:42 +0200 Subject: [PATCH 051/143] Remove more !! operators (#1182) * Remove more !! operators * Fix error * Make sonar more happy * Try to fix format * Reduce intellij warnings --- .../aisec/cpg/analysis/MultiValueEvaluator.kt | 1 - .../aisec/cpg/analysis/SizeEvaluator.kt | 3 +- .../aisec/cpg/analysis/ValueEvaluator.kt | 1 - .../fraunhofer/aisec/cpg/analysis/fsm/FSM.kt | 13 +- .../fraunhofer/aisec/cpg/analysis/fsm/NFA.kt | 2 +- .../de/fraunhofer/aisec/cpg/query/Query.kt | 12 +- .../aisec/cpg/analysis/NullPointerCheck.kt | 2 +- .../aisec/cpg/console/CompilationDatabase.kt | 4 +- .../aisec/cpg/graph/TypeManager.java | 6 +- .../de/fraunhofer/aisec/cpg/ScopeManager.kt | 38 +++--- .../aisec/cpg/TranslationManager.kt | 2 +- .../fraunhofer/aisec/cpg/TranslationResult.kt | 2 +- .../aisec/cpg/frontends/FrontendUtils.kt | 9 +- .../aisec/cpg/frontends/Language.kt | 32 ++--- .../cpg/frontends/cpp/DeclarationHandler.kt | 8 +- .../cpg/frontends/cpp/ExpressionHandler.kt | 25 ++-- .../cpg/frontends/cpp/InitializerHandler.kt | 6 +- .../cpg/frontends/cpp/StatementHandler.kt | 2 +- .../fraunhofer/aisec/cpg/graph/Extensions.kt | 9 +- .../aisec/cpg/graph/edge/PropertyEdge.kt | 6 +- .../expressions/AssignExpression.kt | 32 +++-- .../statements/expressions/ExpressionList.kt | 2 +- .../statements/expressions/UnaryOperator.kt | 17 +-- .../aisec/cpg/graph/types/ObjectType.kt | 2 +- .../aisec/cpg/graph/types/PointerType.kt | 22 +-- .../fraunhofer/aisec/cpg/graph/types/Type.kt | 13 +- .../aisec/cpg/helpers/CommentMatcher.kt | 40 +++--- .../aisec/cpg/helpers/CommonPath.kt | 4 +- .../aisec/cpg/helpers/SubgraphWalker.kt | 24 ++-- .../de/fraunhofer/aisec/cpg/helpers/Util.kt | 2 +- .../aisec/cpg/passes/CXXCallResolverHelper.kt | 6 +- .../aisec/cpg/passes/CallResolver.kt | 28 ++-- .../de/fraunhofer/aisec/cpg/passes/DFGPass.kt | 6 +- .../cpg/passes/EvaluationOrderGraphPass.kt | 39 +++--- .../cpg/passes/FunctionPointerCallResolver.kt | 2 +- .../cpg/passes/TemplateCallResolverHelper.kt | 34 ++--- .../aisec/cpg/passes/TypeResolver.kt | 1 - .../aisec/cpg/passes/inference/Inference.kt | 10 +- .../de/fraunhofer/aisec/cpg/GraphExamples.kt | 6 +- .../enhancements/calls/ConstructorsTest.kt | 2 +- .../aisec/cpg/passes/UnresolvedDFGPassTest.kt | 2 +- .../cpg/frontends/java/DeclarationHandler.kt | 63 +++++---- .../cpg/frontends/java/ExpressionHandler.kt | 80 +++++------ .../frontends/java/JavaLanguageFrontend.kt | 24 ++-- .../cpg/frontends/java/StatementHandler.kt | 128 ++++++------------ .../enhancements/calls/ConstructorsTest.kt | 2 +- .../java/JavaLanguageFrontendTest.kt | 11 +- .../cpg/frontends/llvm/ExpressionHandler.kt | 34 +++-- .../frontends/llvm/LLVMIRLanguageFrontend.kt | 10 +- .../cpg/frontends/llvm/StatementHandler.kt | 29 ++-- .../aisec/cpg/passes/CompressLLVMPass.kt | 2 +- .../cpg/frontends/python/JepSingleton.kt | 2 +- .../cpg/frontends/python/PythonLanguage.kt | 9 +- .../typescript/DeclarationHandler.kt | 10 +- .../frontends/typescript/ExpressionHandler.kt | 38 ++---- .../typescript/TypeScriptLanguageFrontend.kt | 55 ++++---- .../aisec/cpg_vis_neo4j/Application.kt | 5 +- 57 files changed, 469 insertions(+), 510 deletions(-) diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluator.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluator.kt index 2c7e14a9d2..8760d1e5b2 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluator.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluator.kt @@ -354,7 +354,6 @@ class MultiValueEvaluator : ValueEvaluator() { if (loopVar == null) { return result } - // result.add(loopVar) if ((cond.lhs as? DeclaredReferenceExpression)?.refersTo == expr.refersTo) { lhs = loopVar diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/SizeEvaluator.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/SizeEvaluator.kt index 0d86d3c7c3..b64fef2989 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/SizeEvaluator.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/SizeEvaluator.kt @@ -43,8 +43,7 @@ class SizeEvaluator : ValueEvaluator() { if (node is String) { return node.length } - val result = evaluateInternal(node as? Node, 0) - return result + return evaluateInternal(node as? Node, 0) } override fun evaluateInternal(node: Node?, depth: Int): Any? { diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluator.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluator.kt index 394b79a1e0..5836fb7fc0 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluator.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluator.kt @@ -30,7 +30,6 @@ import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration import de.fraunhofer.aisec.cpg.graph.statements.expressions.* import de.fraunhofer.aisec.cpg.graph.statements.expressions.AssignExpression -import kotlin.UnsupportedOperationException import org.slf4j.Logger import org.slf4j.LoggerFactory diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/fsm/FSM.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/fsm/FSM.kt index 3e2d4a78e0..1e5c5532f2 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/fsm/FSM.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/fsm/FSM.kt @@ -46,13 +46,15 @@ sealed class FSM(states: Set) { * Checks whether the given object is an [FSM] and whether it accepts the same language as this * [FSM] */ - override fun equals(other: Any?) = if (other is FSM) acceptsSameLanguage(this, other) else false + override fun equals(other: Any?) = other is FSM && acceptsSameLanguage(this, other) /** * This function is set as [State.edgeCheck] inside [addState]. In case the [edge] must not be * added to the [state], this function must throw an exception. */ - open fun checkEdge(state: State, edge: Edge) {} + open fun checkEdge(state: State, edge: Edge) { + // Nothing to do here because every edge is allowed for an NFA. + } private fun checkState(state: State) { for (edge in state.outgoingEdges) checkEdge(state, edge) @@ -145,10 +147,11 @@ sealed class FSM(states: Set) { ) fun renameStatesToBeDifferentFrom(otherFsm: FSM) { - otherFsm.states.forEach { + otherFsm.states.forEach { state -> otherFsm.checkedChangeStateProperty( - it, - name = it.name + maxOf(states.maxOf { it.name }, otherFsm.states.maxOf { it.name }) + state, + name = + state.name + maxOf(states.maxOf { it.name }, otherFsm.states.maxOf { it.name }) ) } } diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/fsm/NFA.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/fsm/NFA.kt index 2013699c29..4310f955ff 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/fsm/NFA.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/fsm/NFA.kt @@ -86,7 +86,7 @@ class NFA(states: Set = setOf()) : FSM(states) { // by walking through the NFA starting with the start state, this algorithm only converts // the // reachable part of the NFA - while (statesToExplore.size > 0) { + while (statesToExplore.isNotEmpty()) { // get the state to explore next (starts with the new start state created above) val (currentDfaState, epsilonClosure) = statesToExplore.removeFirst() // for each state in the epsilonClosure of the currently explored state, we have to get diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt index f8090e7f60..2b89f23d97 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt @@ -69,10 +69,10 @@ inline fun Node.allExtended( inline fun Node.all( noinline sel: ((T) -> Boolean)? = null, noinline mustSatisfy: (T) -> Boolean -): Pair> { +): Pair> { val nodes = this.allChildren(sel) - val failedNodes = nodes.filterNot(mustSatisfy) as List + val failedNodes = nodes.filterNot(mustSatisfy) return Pair(failedNodes.isEmpty(), failedNodes) } @@ -83,7 +83,7 @@ inline fun Node.all( * evaluation. This filter should be rather simple in most cases since its evaluation is not part of * the resulting reasoning chain. */ -inline fun Node.existsExtended( +inline fun Node.existsExtended( noinline sel: ((T) -> Boolean)? = null, noinline mustSatisfy: (T) -> QueryTree ): QueryTree { @@ -103,13 +103,13 @@ inline fun Node.existsExtended( * The optional argument [sel] can be used to filter nodes which are considered during the * evaluation. */ -inline fun Node.exists( +inline fun Node.exists( noinline sel: ((T) -> Boolean)? = null, noinline mustSatisfy: (T) -> Boolean -): Pair> { +): Pair> { val nodes = this.allChildren(sel) - val queryChildren = nodes.filter(mustSatisfy) as List + val queryChildren = nodes.filter(mustSatisfy) return Pair(queryChildren.isNotEmpty(), queryChildren) } diff --git a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/NullPointerCheck.kt b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/NullPointerCheck.kt index 5753bbafe1..dc376e8343 100644 --- a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/NullPointerCheck.kt +++ b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/NullPointerCheck.kt @@ -131,7 +131,7 @@ class NullPointerCheck { } } } catch (ex: Throwable) { - log.error("Exception while running check: {}", ex) + log.error("Exception while running check", ex) } } } diff --git a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/console/CompilationDatabase.kt b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/console/CompilationDatabase.kt index e7ec6509c2..3c998db85c 100644 --- a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/console/CompilationDatabase.kt +++ b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/console/CompilationDatabase.kt @@ -99,5 +99,7 @@ class CompilationDatabase : Plugin { repl.registerCommand(Load(config)) } - override fun cleanUp() {} + override fun cleanUp() { + // Nothing to do + } } diff --git a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/TypeManager.java b/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/TypeManager.java index 54bea206d7..553c0284b2 100644 --- a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/TypeManager.java +++ b/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/TypeManager.java @@ -221,13 +221,11 @@ public ParameterizedType createOrGetTypeParameter( String typeName, Language language) { ParameterizedType parameterizedType = getTypeParameter(templateDeclaration, typeName); - if (parameterizedType != null) { - return parameterizedType; - } else { + if (parameterizedType == null) { parameterizedType = new ParameterizedType(typeName, language); addTypeParameter(templateDeclaration, parameterizedType); - return parameterizedType; } + return parameterizedType; } @NotNull 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 29da6fb328..1ca4756958 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 @@ -40,7 +40,6 @@ import de.fraunhofer.aisec.cpg.processing.IVisitor import de.fraunhofer.aisec.cpg.processing.strategy.Strategy import java.util.* import java.util.concurrent.atomic.AtomicInteger -import java.util.function.Consumer import java.util.function.Predicate import org.slf4j.LoggerFactory @@ -198,7 +197,7 @@ class ScopeManager : ScopeProvider { if (scope is NameScope) { // for this to work, it is essential that RecordDeclaration and NamespaceDeclaration // nodes have a FQN as their name. - fqnScopeMap[scope.astNode!!.name.toString()] = scope + fqnScopeMap[scope.astNode?.name.toString()] = scope } currentScope?.let { it.children.add(scope) @@ -468,9 +467,9 @@ class ScopeManager : ScopeProvider { } (scope as Breakable).addBreakStatement(breakStatement) } else { - val labelStatement = getLabelStatement(breakStatement.label!!) - if (labelStatement?.subStatement != null) { - val scope = lookupScope(labelStatement.subStatement!!) + val labelStatement = getLabelStatement(breakStatement.label) + labelStatement?.subStatement?.let { + val scope = lookupScope(it) (scope as Breakable?)?.addBreakStatement(breakStatement) } } @@ -493,9 +492,9 @@ class ScopeManager : ScopeProvider { } (scope as Continuable).addContinueStatement(continueStatement) } else { - val labelStatement = getLabelStatement(continueStatement.label!!) - if (labelStatement?.subStatement != null) { - val scope = lookupScope(labelStatement.subStatement!!) + val labelStatement = getLabelStatement(continueStatement.label) + labelStatement?.subStatement?.let { + val scope = lookupScope(it) (scope as Continuable?)?.addContinueStatement(continueStatement) } } @@ -514,7 +513,8 @@ class ScopeManager : ScopeProvider { * This function is internal to the scope manager and primarily used by [addBreakStatement] and * [addContinueStatement]. It retrieves the [LabelStatement] associated with the [labelString]. */ - private fun getLabelStatement(labelString: String): LabelStatement? { + private fun getLabelStatement(labelString: String?): LabelStatement? { + if (labelString == null) return null var labelStatement: LabelStatement? var searchScope = currentScope while (searchScope != null) { @@ -701,9 +701,7 @@ class ScopeManager : ScopeProvider { fun resolveFunctionStopScopeTraversalOnDefinition( call: CallExpression ): List { - return resolve(currentScope, true) { f: FunctionDeclaration -> - f.name.lastPartsMatch(call.name) - } + return resolve(currentScope, true) { f -> f.name.lastPartsMatch(call.name) } } /** @@ -774,9 +772,7 @@ class ScopeManager : ScopeProvider { call: CallExpression, scope: Scope? = currentScope ): List { - return resolve(scope, true) { c: FunctionTemplateDeclaration -> - c.name.lastPartsMatch(call.name) - } + return resolve(scope, true) { c -> c.name.lastPartsMatch(call.name) } } /** @@ -804,7 +800,7 @@ class ScopeManager : ScopeProvider { override fun visit(t: Node) { if (t is HasType) { val typeNode = t as HasType - typeCache.getOrDefault(typeNode, emptyList()).forEach { it: Type? -> + typeCache.getOrDefault(typeNode, emptyList()).forEach { (t as HasType).type = TypeManager.getInstance() .resolvePossibleTypedef(it, this@ScopeManager) @@ -819,12 +815,10 @@ class ScopeManager : ScopeProvider { // For some nodes it may happen that they are not reachable via AST, but we still need to // set their type to the requested value - typeCache.forEach { (n: HasType, types: List) -> - types.forEach( - Consumer { t: Type? -> - n.type = TypeManager.getInstance().resolvePossibleTypedef(t, this) - } - ) + typeCache.forEach { (n, types) -> + types.forEach { t -> + n.type = TypeManager.getInstance().resolvePossibleTypedef(t, this) + } } } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationManager.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationManager.kt index d619f1be14..acd857c6c0 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationManager.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationManager.kt @@ -339,7 +339,7 @@ private constructor( result.scratch.computeIfAbsent(TranslationResult.SOURCE_LOCATIONS_TO_FRONTEND) { mutableMapOf() } as MutableMap - sfToFe[sourceLocation!!.name] = f.javaClass.simpleName + sourceLocation?.name?.let { sfToFe[it] = f.javaClass.simpleName } } @Throws(TranslationException::class) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationResult.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationResult.kt index 3cccc9cd38..2030ef3a33 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationResult.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationResult.kt @@ -128,7 +128,7 @@ class TranslationResult( components.add(swc) } } - swc.translationUnits.add(tu!!) + tu?.let { swc.translationUnits.add(it) } } /** diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/FrontendUtils.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/FrontendUtils.kt index 44c46d96c9..c262db3654 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/FrontendUtils.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/FrontendUtils.kt @@ -75,8 +75,7 @@ class FrontendUtils { } val endColumn = getEndColumnIndex(fileContent, nodeOffset + nodeLength) - val region = Region(startingLineNumber, startColumn, endingLineNumber, endColumn) - return region + return Region(startingLineNumber, startColumn, endingLineNumber, endColumn) } /** @@ -122,7 +121,7 @@ class FrontendUtils { // Get a List of all Nodes that enclose the comment var enclosingNodes = nodes.filter { - val nodeRegion: Region = it.location?.let { it.region } ?: Region() + val nodeRegion: Region = it.location?.region ?: Region() nodeRegion.startLine <= location.startLine && nodeRegion.endLine >= location.endLine && (nodeRegion.startLine != location.startLine || @@ -143,8 +142,8 @@ class FrontendUtils { // Because in GO we wrap all elements into a NamespaceDeclaration we have to extract the // natural children children.addAll( - children.filterIsInstance().flatMap { - SubgraphWalker.getAstChildren(it).filter { !children.contains(it) } + children.filterIsInstance().flatMap { namespace -> + SubgraphWalker.getAstChildren(namespace).filter { it !in children } } ) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/Language.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/Language.kt index b663d1e307..d9a4ad0661 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/Language.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/Language.kt @@ -115,26 +115,18 @@ abstract class Language : Node() { } private fun arithmeticOpTypePropagation(lhs: Type, rhs: Type): Type { - return if (lhs is FloatingPointType && rhs !is FloatingPointType) { - lhs - } else if (lhs !is FloatingPointType && rhs is FloatingPointType) { - rhs - } else if (lhs is FloatingPointType && rhs is FloatingPointType) { - // We take the one with the bigger bitwidth - if ((lhs.bitWidth ?: 0) >= (rhs.bitWidth ?: 0)) { - lhs - } else { - rhs - } - } else if (lhs is IntegerType && rhs is IntegerType) { - // We take the one with the bigger bitwidth - if ((lhs.bitWidth ?: 0) >= (rhs.bitWidth ?: 0)) { - lhs - } else { - rhs - } - } else { - newUnknownType() + return when { + lhs is FloatingPointType && rhs !is FloatingPointType && rhs is NumericType -> lhs + lhs !is FloatingPointType && lhs is NumericType && rhs is FloatingPointType -> rhs + lhs is FloatingPointType && rhs is FloatingPointType || + lhs is IntegerType && rhs is IntegerType -> + // We take the one with the bigger bitwidth + if (((lhs as NumericType).bitWidth ?: 0) >= ((rhs as NumericType).bitWidth ?: 0)) { + lhs + } else { + rhs + } + else -> newUnknownType() } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/DeclarationHandler.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/DeclarationHandler.kt index 563e886a20..c7f5b82151 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/DeclarationHandler.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/DeclarationHandler.kt @@ -342,8 +342,10 @@ class DeclarationHandler(lang: CXXLanguageFrontend) : templateParameter.declarator.initializer ) nonTypeTemplateParamDeclaration.default = defaultExpression - nonTypeTemplateParamDeclaration.addPrevDFG(defaultExpression!!) - defaultExpression.addNextDFG(nonTypeTemplateParamDeclaration) + defaultExpression?.let { + nonTypeTemplateParamDeclaration.addPrevDFG(it) + it.addNextDFG(nonTypeTemplateParamDeclaration) + } } templateDeclaration.addParameter(nonTypeTemplateParamDeclaration) frontend.scopeManager.addDeclaration(nonTypeTemplateParamDeclaration) @@ -726,7 +728,7 @@ class DeclarationHandler(lang: CXXLanguageFrontend) : val allIncludes = HashMap>() parseInclusions(dependencyTree.inclusions, allIncludes) - if (allIncludes.size == 0) { + if (allIncludes.isEmpty()) { return } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/ExpressionHandler.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/ExpressionHandler.kt index 85adbc9cbe..a16882a344 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/ExpressionHandler.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/ExpressionHandler.kt @@ -223,18 +223,17 @@ class ExpressionHandler(lang: CXXLanguageFrontend) : // we also need to "forward" our template parameters (if we have any) to the construct // expression since the construct expression will do the actual template instantiation - if ( - newExpression.templateParameters != null && - newExpression.templateParameters!!.isNotEmpty() - ) { - CallResolver.addImplicitTemplateParametersToCall( - newExpression.templateParameters!!, - initializer as ConstructExpression - ) + if (newExpression.templateParameters?.isNotEmpty() == true) { + newExpression.templateParameters?.let { + CallResolver.addImplicitTemplateParametersToCall( + it, + initializer as ConstructExpression + ) + } } // our initializer, such as a construct expression, will have the non-pointer type - initializer!!.type = t + initializer?.type = t newExpression.initializer = initializer newExpression } @@ -448,13 +447,15 @@ class ExpressionHandler(lang: CXXLanguageFrontend) : for ((i, argument) in ctx.arguments.withIndex()) { val arg = handle(argument) - arg!!.argumentIndex = i - callExpression.addArgument(arg) + arg?.let { + it.argumentIndex = i + callExpression.addArgument(it) + } } // Important: we don't really need the reference node, but even its temporary creation might // leave unwanted artifacts behind in the final graph! - reference!!.disconnectFromGraph() + reference?.disconnectFromGraph() return callExpression } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/InitializerHandler.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/InitializerHandler.kt index 7ba28fe46e..82079faf4f 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/InitializerHandler.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/InitializerHandler.kt @@ -57,8 +57,10 @@ class InitializerHandler(lang: CXXLanguageFrontend) : for ((i, argument) in ctx.arguments.withIndex()) { val arg = frontend.expressionHandler.handle(argument) - arg!!.argumentIndex = i - constructExpression.addArgument(arg) + arg?.let { + it.argumentIndex = i + constructExpression.addArgument(it) + } } return constructExpression diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/StatementHandler.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/StatementHandler.kt index 6c49cdb7b8..63a398818d 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/StatementHandler.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/StatementHandler.kt @@ -253,7 +253,7 @@ class StatementHandler(lang: CXXLanguageFrontend) : val statement = newForEachStatement(ctx.rawSignature) frontend.scopeManager.enterScope(statement) val decl = frontend.declarationHandler.handle(ctx.declaration) - val `var` = newDeclarationStatement(decl!!.code) + val `var` = newDeclarationStatement(decl?.code) `var`.singleDeclaration = decl val iterable: Statement? = frontend.expressionHandler.handle(ctx.initializerClause) statement.variable = `var` diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt index 92c5fcec54..a304dbff6e 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt @@ -579,7 +579,7 @@ operator fun Expression.invoke(): N? { /** Returns all [CallExpression]s in this graph which call a method with the given [name]. */ fun TranslationResult.callsByName(name: String): List { return SubgraphWalker.flattenAST(this).filter { node -> - (node as? CallExpression)?.invokes?.any { it.name.lastPartsMatch(name) } == true + node is CallExpression && node.invokes.any { it.name.lastPartsMatch(name) } } as List } @@ -624,9 +624,12 @@ fun IfStatement.controls(): List { /** All nodes which depend on this if statement */ fun Node.controlledBy(): List { val result = mutableListOf() - var checkedNode: Node = this + var checkedNode: Node? = this while (checkedNode !is FunctionDeclaration) { - checkedNode = checkedNode.astParent!! + checkedNode = checkedNode?.astParent + if (checkedNode == null) { + break + } if (checkedNode is IfStatement || checkedNode is SwitchStatement) { result.add(checkedNode) } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edge/PropertyEdge.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edge/PropertyEdge.kt index 7db83bbf7b..1b5e59d4f2 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edge/PropertyEdge.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edge/PropertyEdge.kt @@ -247,7 +247,7 @@ open class PropertyEdge : Persistable { } else { obj.start } - } else if (obj is Collection<*> && !obj.isEmpty()) { + } else if (obj is Collection<*> && obj.isNotEmpty()) { return unwrapPropertyEdgeCollection(obj, outgoing) } return obj @@ -297,10 +297,10 @@ open class PropertyEdge : Persistable { ): List> { val newPropertyEdges: MutableList> = ArrayList() for (propertyEdge in propertyEdges) { - if (end && !propertyEdge.end.equals(element)) { + if (end && propertyEdge.end != element) { newPropertyEdges.add(propertyEdge) } - if (!end && !propertyEdge.start.equals(element)) { + if (!end && propertyEdge.start != element) { newPropertyEdges.add(propertyEdge) } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/AssignExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/AssignExpression.kt index b64202cbe8..18a1867162 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/AssignExpression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/AssignExpression.kt @@ -29,6 +29,8 @@ import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration import de.fraunhofer.aisec.cpg.graph.types.TupleType import de.fraunhofer.aisec.cpg.graph.types.Type +import org.slf4j.Logger +import org.slf4j.LoggerFactory /** * Represents an assignment of a group of expressions (in the simplest case: one) from the right @@ -153,17 +155,17 @@ class AssignExpression : Expression(), AssignmentHolder, HasType.TypeListener { /** Finds the value (of [rhs]) that is assigned to the particular [lhs] expression. */ fun findValue(lhsExpr: HasType): Expression? { - if (lhs.size > 1) { - return rhs.singleOrNull() + return if (lhs.size > 1) { + rhs.singleOrNull() } else { // Basically, we need to find out which index on the lhs this variable belongs to and // find the corresponding index on the rhs. val idx = lhs.indexOf(lhsExpr) if (idx == -1) { - return null + null + } else { + rhs.getOrNull(idx) } - - return rhs.getOrNull(idx) } } @@ -173,25 +175,25 @@ class AssignExpression : Expression(), AssignmentHolder, HasType.TypeListener { // There are now two possibilities: Either, we have a tuple type, that we need to // deconstruct, or we have a singular type - if (type is TupleType) { + return if (type is TupleType) { // We need to see if there is enough room on the left side. Currently, we only support // languages that do not allow to mix tuple and non-tuple types luckily, so we can just // assume that all arguments on the left side are assignment targets if (lhs.size != type.types.size) { - println("Tuple type size on RHS does not match number of LHS expressions") - return listOf() + log.info("Tuple type size on RHS does not match number of LHS expressions") + listOf() + } else { + lhs } - - return lhs } else { // Basically, we need to find out which index on the rhs this variable belongs to and // find the corresponding index on the rhs. val idx = rhs.indexOf(rhsExpr) if (idx == -1) { - return listOf() + listOf() + } else { + listOfNotNull(lhs.getOrNull(idx)) } - - return listOfNotNull(lhs.getOrNull(idx)) } } @@ -205,4 +207,8 @@ class AssignExpression : Expression(), AssignmentHolder, HasType.TypeListener { return list } + + companion object { + private val log: Logger = LoggerFactory.getLogger(Node::class.java) + } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ExpressionList.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ExpressionList.kt index 29eb3f137a..a943e759b0 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ExpressionList.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ExpressionList.kt @@ -63,7 +63,7 @@ class ExpressionList : Expression(), HasType.TypeListener { } fun addExpression(expression: Statement) { - if (!expressionEdges.isEmpty()) { + if (expressionEdges.isNotEmpty()) { val lastExpression = expressionEdges[expressionEdges.size - 1].end if (lastExpression is HasType) (lastExpression as HasType).unregisterTypeListener(this) } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/UnaryOperator.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/UnaryOperator.kt index bed0cc6fe5..3c1c44db28 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/UnaryOperator.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/UnaryOperator.kt @@ -79,7 +79,7 @@ class UnaryOperator : Expression(), HasType.TypeListener { ): Boolean { val worklist: MutableList = ArrayList() worklist.add(curr) - while (!worklist.isEmpty()) { + while (worklist.isNotEmpty()) { val tl = worklist.removeAt(0) if (!checked.contains(tl)) { checked.add(tl) @@ -179,17 +179,14 @@ class UnaryOperator : Expression(), HasType.TypeListener { if (other !is UnaryOperator) { return false } - val that = other - return super.equals(that) && - isPostfix == that.isPostfix && - isPrefix == that.isPrefix && - input == that.input && - operatorCode == that.operatorCode + return super.equals(other) && + isPostfix == other.isPostfix && + isPrefix == other.isPrefix && + input == other.input && + operatorCode == other.operatorCode } - override fun hashCode(): Int { - return super.hashCode() - } + override fun hashCode() = super.hashCode() companion object { const val OPERATOR_POSTFIX_INCREMENT = "++" diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/ObjectType.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/ObjectType.kt index 9876ba71d5..d453007679 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/ObjectType.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/ObjectType.kt @@ -95,7 +95,7 @@ open class ObjectType : Type, SecondaryTypeEdge { fun replaceGenerics(oldType: Type?, newType: Type) { for (i in genericsPropertyEdges.indices) { val propertyEdge = genericsPropertyEdges[i] - if (propertyEdge.end.equals(oldType)) { + if (propertyEdge.end == oldType) { propertyEdge.end = newType } } 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 e59a39043a..39af389997 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 @@ -49,22 +49,24 @@ class PointerType : Type, SecondOrderType { constructor(elementType: Type, pointerOrigin: PointerOrigin?) : super() { language = elementType.language - if (pointerOrigin == PointerOrigin.ARRAY) { - name = elementType.name.append("[]") - } else { - name = elementType.name.append("*") - } + name = + if (pointerOrigin == PointerOrigin.ARRAY) { + elementType.name.append("[]") + } else { + elementType.name.append("*") + } this.pointerOrigin = pointerOrigin this.elementType = elementType } constructor(type: Type?, elementType: Type, pointerOrigin: PointerOrigin?) : super(type) { language = elementType.language - if (pointerOrigin == PointerOrigin.ARRAY) { - name = elementType.name.append("[]") - } else { - name = elementType.name.append("*") - } + name = + if (pointerOrigin == PointerOrigin.ARRAY) { + elementType.name.append("[]") + } else { + elementType.name.append("*") + } this.pointerOrigin = pointerOrigin this.elementType = elementType } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/Type.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/Type.kt index e32b54b51f..a666695879 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/Type.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/Type.kt @@ -69,11 +69,12 @@ abstract class Type : Node { } constructor(typeName: CharSequence, language: Language?) { - if (this is FunctionType) { - name = Name(typeName.toString(), null, language) - } else { - name = language.parseName(typeName) - } + name = + if (this is FunctionType) { + Name(typeName.toString(), null, language) + } else { + language.parseName(typeName) + } this.language = language typeOrigin = Origin.UNRESOLVED } @@ -172,7 +173,7 @@ abstract class Type : Node { override fun equals(other: Any?): Boolean { if (this === other) return true - return if (other !is Type) false else name == other.name && language == other.language + return other is Type && name == other.name && language == other.language } override fun hashCode() = Objects.hash(name, language) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/CommentMatcher.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/CommentMatcher.kt index 86564b1111..6a49444e33 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/CommentMatcher.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/CommentMatcher.kt @@ -46,22 +46,20 @@ class CommentMatcher { // As some frontends add regional implicit namespaces we have to search amongst its children // instead. children.addAll( - children.filterIsInstance().flatMap { - SubgraphWalker.getAstChildren(it).filter { !children.contains(it) } + children.filterIsInstance().flatMap { namespace -> + SubgraphWalker.getAstChildren(namespace).filter { it !in children } } ) - var enclosing = - children - .filter { - val nodeRegion: Region = it.location?.let { it.region } ?: Region() - nodeRegion.startLine <= location.startLine && - nodeRegion.endLine >= location.endLine && - (nodeRegion.startLine != location.startLine || - nodeRegion.startColumn <= location.startColumn) && - (nodeRegion.endLine != location.endLine || - nodeRegion.endColumn >= location.endColumn) - } - .firstOrNull() + val enclosing = + children.firstOrNull { + val nodeRegion: Region = it.location?.region ?: Region() + nodeRegion.startLine <= location.startLine && + nodeRegion.endLine >= location.endLine && + (nodeRegion.startLine != location.startLine || + nodeRegion.startColumn <= location.startColumn) && + (nodeRegion.endLine != location.endLine || + nodeRegion.endColumn >= location.endColumn) + } return enclosing ?: node } @@ -84,16 +82,16 @@ class CommentMatcher { // Because we sometimes wrap all elements into a NamespaceDeclaration we have to extract the // children with a location children.addAll( - children.filterIsInstance().flatMap { - SubgraphWalker.getAstChildren(it).filter { !children.contains(it) } + children.filterIsInstance().flatMap { namespace -> + SubgraphWalker.getAstChildren(namespace).filter { it !in children } } ) // Searching for the closest successor to our comment amongst the children of the smallest // enclosing nodes - var successors = + val successors = children.filter { - val nodeRegion: Region = it.location?.region?.let { it } ?: Region() + val nodeRegion: Region = it.location?.region ?: Region() nodeRegion.startLine >= location.endLine && (nodeRegion.startLine > location.endLine || nodeRegion.startColumn >= location.endColumn) @@ -110,11 +108,11 @@ class CommentMatcher { val closestLine = closest?.location?.region?.startLine ?: location.endLine + 1 // If the closest successor is not in the same line there may be a more adequate predecessor - // to associated the comment to (Has to be in the same line) + // to associate the comment to (Has to be in the same line) if (closest == null || closestLine > location.endLine) { - var predecessor = + val predecessor = children.filter { - val nodeRegion: Region = it.location?.region?.let { it } ?: Region() + val nodeRegion: Region = it.location?.region ?: Region() nodeRegion.endLine <= location.startLine && (nodeRegion.endLine < location.startLine || nodeRegion.endColumn <= location.startColumn) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/CommonPath.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/CommonPath.kt index fab1f5fc00..0b025bf368 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/CommonPath.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/CommonPath.kt @@ -37,8 +37,8 @@ object CommonPath { val longestPrefix = StringBuilder() val splitPaths = paths - .map { - it.absolutePath + .map { file -> + file.absolutePath .split(Pattern.quote(File.separator).toRegex()) .dropLastWhile { it.isEmpty() } .toTypedArray() 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 c8b08bf36f..fd13835ba4 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 @@ -70,7 +70,7 @@ object SubgraphWalker { // Note: we cannot use computeIfAbsent here, because we are calling our function // recursively and this would result in a ConcurrentModificationException if (fieldCache.containsKey(cacheKey)) { - return fieldCache[cacheKey]!! + return fieldCache[cacheKey] ?: ArrayList() } val fields = ArrayList() fields.addAll(getAllFields(classType.superclass)) @@ -322,11 +322,11 @@ object SubgraphWalker { backlog = ArrayDeque() val seen: MutableSet = LinkedHashSet() todo?.push(Pair(root, null)) - while (!(todo as ArrayDeque>).isEmpty()) { + while ((todo as ArrayDeque>).isNotEmpty()) { val (current, parent) = (todo as ArrayDeque>).pop() if ( - !(backlog as ArrayDeque).isEmpty() && - (backlog as ArrayDeque).peek().equals(current) + (backlog as ArrayDeque).isNotEmpty() && + (backlog as ArrayDeque).peek() == current ) { val exiting = (backlog as ArrayDeque).pop() onScopeExit.forEach(Consumer { c: Consumer -> c.accept(exiting) }) @@ -430,9 +430,9 @@ object SubgraphWalker { */ fun iterate(root: Node) { walker = IterativeGraphWalker() - handlers.forEach { h -> walker!!.registerOnNodeVisit { n -> handleNode(n, h) } } - walker!!.registerOnScopeExit { exiting: Node -> leaveScope(exiting) } - walker!!.iterate(root) + handlers.forEach { h -> walker?.registerOnNodeVisit { n -> handleNode(n, h) } } + walker?.registerOnScopeExit { exiting: Node -> leaveScope(exiting) } + walker?.iterate(root) } private fun handleNode( @@ -440,7 +440,7 @@ object SubgraphWalker { handler: TriConsumer ) { scopeManager.enterScopeIfExists(current) - val parent = walker!!.backlog!!.peek() + val parent = walker?.backlog?.peek() // TODO: actually we should not handle this in handleNode but have something similar to // onScopeEnter because the method declaration already correctly sets the scope @@ -457,7 +457,7 @@ object SubgraphWalker { var parentBlock: Node? = null // get containing Record or Compound - for (node in walker!!.backlog!!) { + for (node in walker?.backlog ?: listOf()) { if ( node is RecordDeclaration || node is CompoundStatement || @@ -497,13 +497,13 @@ object SubgraphWalker { // iterate all declarations from the current scope and all its parent scopes while (nodeToParentBlockAndContainedValueDeclarations.containsKey(scope)) { - val entry = nodeToParentBlockAndContainedValueDeclarations[currentScope]!! - for (`val` in entry.right) { + val entry = nodeToParentBlockAndContainedValueDeclarations[currentScope] + for (`val` in entry?.right ?: listOf()) { if (predicate.test(`val`)) { return Optional.of(`val`) } } - currentScope = entry.left + entry?.left?.let { currentScope = it } } return Optional.empty() } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/Util.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/Util.kt index 67ba6dcfd6..e7e57d3bad 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/Util.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/Util.kt @@ -345,7 +345,7 @@ object Util { } break } else { - param.addPrevDFG(arguments[j]!!) + param.addPrevDFG(arguments[j]) } } j++ diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CXXCallResolverHelper.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CXXCallResolverHelper.kt index 9d9b1c5bad..f3285238d5 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CXXCallResolverHelper.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CXXCallResolverHelper.kt @@ -49,7 +49,7 @@ fun compatibleSignatures( return if (callSignature.size == functionSignature.size) { for (i in callSignature.indices) { if ( - callSignature[i]!!.isPrimitive != functionSignature[i].isPrimitive && + callSignature[i]?.isPrimitive != functionSignature[i].isPrimitive && !TypeManager.getInstance() .isSupertypeOf(functionSignature[i], callSignature[i], provider) ) { @@ -238,7 +238,7 @@ fun resolveConstructorWithDefaults( fun shouldContinueSearchInParent(recordDeclaration: RecordDeclaration?, name: String?): Boolean { val namePattern = Pattern.compile( - "(" + Pattern.quote(recordDeclaration!!.name.toString()) + "\\.)?" + Pattern.quote(name) + "(" + Pattern.quote(recordDeclaration?.name.toString()) + "\\.)?" + Pattern.quote(name) ) val invocationCandidate = recordDeclaration.methods.filter { namePattern.matcher(it.name.toString()).matches() } @@ -404,7 +404,7 @@ fun signatureWithImplicitCastTransformation( for (i in callSignature.indices) { val callType = callSignature[i] val funcType = functionSignature[i] - if (callType!!.isPrimitive && funcType.isPrimitive && callType != funcType) { + if (callType?.isPrimitive == true && funcType.isPrimitive && callType != funcType) { val implicitCast = CastExpression() implicitCast.isImplicit = true implicitCast.castType = funcType diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolver.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolver.kt index 50a06b3354..b8218cb4ea 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolver.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolver.kt @@ -273,7 +273,7 @@ open class CallResolver : SymbolResolverPass() { private fun handleArguments(call: CallExpression) { val worklist: Deque = ArrayDeque() call.arguments.forEach { worklist.push(it) } - while (!worklist.isEmpty()) { + while (worklist.isNotEmpty()) { val curr = worklist.pop() if (curr is CallExpression) { handleNode(curr) @@ -300,7 +300,7 @@ open class CallResolver : SymbolResolverPass() { ): List { val language = call.language - if (curClass == null) { + return if (curClass == null) { // Handle function (not method) calls. C++ allows function overloading. Make sure we // have at least the same number of arguments val candidates = @@ -312,9 +312,9 @@ open class CallResolver : SymbolResolverPass() { scopeManager.resolveFunction(call).toMutableList() } - return candidates + candidates } else { - return resolveMemberCallee(callee, curClass, call) + resolveMemberCallee(callee, curClass, call) } } @@ -333,13 +333,14 @@ open class CallResolver : SymbolResolverPass() { // We need to adjust certain types of the base in case of a super call and we delegate this. // If that is successful, we can continue with regular resolving if ( - callee is MemberExpression && + curClass != null && + callee is MemberExpression && callee.base is DeclaredReferenceExpression && isSuperclassReference(callee.base as DeclaredReferenceExpression) ) { (callee.language as? HasSuperClasses)?.handleSuperCall( callee, - curClass!!, + curClass, scopeManager, recordMap ) @@ -474,8 +475,8 @@ open class CallResolver : SymbolResolverPass() { } private fun handleExplicitConstructorInvocation(eci: ExplicitConstructorInvocation) { - if (eci.containingClass != null) { - val recordDeclaration = recordMap[eci.parseName(eci.containingClass!!)] + eci.containingClass?.let { containingClass -> + val recordDeclaration = recordMap[eci.parseName(containingClass)] val signature = eci.arguments.map { it.type } if (recordDeclaration != null) { val constructor = @@ -490,12 +491,13 @@ open class CallResolver : SymbolResolverPass() { private fun getPossibleContainingTypes(node: Node?): Set { val possibleTypes = mutableSetOf() if (node is MemberCallExpression) { - val base = node.base!! - possibleTypes.add(base.type) - possibleTypes.addAll(base.possibleSubTypes) + node.base?.let { base -> + possibleTypes.add(base.type) + possibleTypes.addAll(base.possibleSubTypes) + } } else { // This could be a C++ member call with an implicit this (which we do not create), so - // lets add the current class to the possible list + // let's add the current class to the possible list scopeManager.currentRecord?.toType()?.let { possibleTypes.add(it) } } @@ -513,7 +515,7 @@ open class CallResolver : SymbolResolverPass() { Pattern.compile( "(" + Pattern.quote(recordDeclaration.name.toString()) + - Regex.escape(recordDeclaration.language!!.namespaceDelimiter) + + Regex.escape(recordDeclaration.language?.namespaceDelimiter ?: "") + ")?" + Pattern.quote(name) ) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/DFGPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/DFGPass.kt index 7a220594b7..33f4ea678f 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/DFGPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/DFGPass.kt @@ -172,13 +172,13 @@ class DFGPass : Pass() { * [VariableDeclaration] in the statement is the one we care about. */ private fun handleForEachStatement(node: ForEachStatement) { - if (node.iterable != null) { + node.iterable?.let { iterable -> if (node.variable is DeclarationStatement) { (node.variable as DeclarationStatement).declarations.forEach { - it.addPrevDFG(node.iterable!!) + it.addPrevDFG(iterable) } } else { - node.variable.variables.lastOrNull()?.addPrevDFG(node.iterable!!) + node.variable.variables.lastOrNull()?.addPrevDFG(iterable) } } node.variable?.let { node.addPrevDFG(it) } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt index 392120d7e9..55bffd9ac6 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt @@ -342,18 +342,18 @@ open class EvaluationOrderGraphPass : Pass() { currentPredecessors.add(node) var defaultArg: Expression? = null for (paramVariableDeclaration in node.parameters) { - if (paramVariableDeclaration.default != null) { - defaultArg = paramVariableDeclaration.default - pushToEOG(defaultArg!!) + paramVariableDeclaration.default?.let { + defaultArg = it + pushToEOG(it) currentPredecessors.clear() - currentPredecessors.add(defaultArg) + currentPredecessors.add(it) currentPredecessors.add(node) } } - if (defaultArg != null) { + defaultArg?.let { for (nextEOG in funcDeclNextEOG) { currentPredecessors.clear() - currentPredecessors.add(defaultArg) + currentPredecessors.add(it) pushToEOG(nextEOG) } } @@ -572,24 +572,24 @@ open class EvaluationOrderGraphPass : Pass() { createEOG(node.tryBlock) val tmpEOGNodes = ArrayList(currentPredecessors) - val catchesOrRelays = tryScope!!.catchesOrRelays + val catchesOrRelays = tryScope?.catchesOrRelays for (catchClause in node.catchClauses) { currentPredecessors.clear() // Try to catch all internally thrown exceptions under the catching clause and remove // caught ones val toRemove = mutableSetOf() - for ((throwType, eogEdges) in catchesOrRelays) { - if (catchClause.parameter == null) { // e.g. catch (...) + for ((throwType, eogEdges) in catchesOrRelays ?: mapOf()) { + val catchParam = catchClause.parameter + if (catchParam == null) { // e.g. catch (...) currentPredecessors.addAll(eogEdges) } else if ( - TypeManager.getInstance() - .isSupertypeOf(catchClause.parameter!!.type, throwType, node) + TypeManager.getInstance().isSupertypeOf(catchParam.type, throwType, node) ) { currentPredecessors.addAll(eogEdges) toRemove.add(throwType) } } - toRemove.forEach { catchesOrRelays.remove(it) } + toRemove.forEach { catchesOrRelays?.remove(it) } createEOG(catchClause.body) tmpEOGNodes.addAll(currentPredecessors) } @@ -600,26 +600,29 @@ open class EvaluationOrderGraphPass : Pass() { // finally exists if (node.finallyBlock != null) { // extends current EOG by all value EOG from open throws - currentPredecessors.addAll(catchesOrRelays.entries.flatMap { (_, value) -> value }) + catchesOrRelays + ?.entries + ?.flatMap { (_, value) -> value } + ?.let { currentPredecessors.addAll(it) } createEOG(node.finallyBlock) // all current-eog edges , result of finally execution as value List of uncaught // catchesOrRelaysThrows - for ((_, value) in catchesOrRelays) { + for ((_, value) in catchesOrRelays ?: mapOf()) { value.clear() value.addAll(currentPredecessors) } } // Forwards all open and uncaught throwing nodes to the outer scope that may handle them val outerScope = - scopeManager.firstScopeOrNull(scopeManager.currentScope!!.parent) { scope: Scope? -> + scopeManager.firstScopeOrNull(scopeManager.currentScope?.parent) { scope: Scope? -> scope is TryScope || scope is FunctionScope } if (outerScope != null) { val outerCatchesOrRelays = if (outerScope is TryScope) outerScope.catchesOrRelays else (outerScope as FunctionScope).catchesOrRelays - for ((key, value) in catchesOrRelays) { + for ((key, value) in catchesOrRelays ?: mapOf()) { val catches = outerCatchesOrRelays[key] ?: ArrayList() catches.addAll(value) outerCatchesOrRelays[key] = catches @@ -657,8 +660,8 @@ open class EvaluationOrderGraphPass : Pass() { protected fun handleGotoStatement(node: GotoStatement) { pushToEOG(node) - if (node.targetLabel != null) { - processedListener.registerObjectListener(node.targetLabel!!) { _: Any?, to: Any? -> + node.targetLabel?.let { + processedListener.registerObjectListener(it) { _: Any?, to: Any? -> addEOGEdge(node, to as Node) } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/FunctionPointerCallResolver.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/FunctionPointerCallResolver.kt index 483a13601e..8742198e69 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/FunctionPointerCallResolver.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/FunctionPointerCallResolver.kt @@ -109,7 +109,7 @@ class FunctionPointerCallResolver : Pass() { val work: Deque = ArrayDeque() val seen = IdentitySet() work.push(pointer) - while (!work.isEmpty()) { + while (work.isNotEmpty()) { val curr = work.pop() if (!seen.add(curr)) { continue diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TemplateCallResolverHelper.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TemplateCallResolverHelper.kt index 0ae6d95876..e22174aaf5 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TemplateCallResolverHelper.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TemplateCallResolverHelper.kt @@ -121,31 +121,31 @@ fun applyMissingParams( for (m in missingParams) { var missingParam = m if (missingParam is DeclaredReferenceExpression) { - missingParam = missingParam.refersTo!! + missingParam = missingParam.refersTo } if (missingParam in templateParametersExplicitInitialization) { // If default is a previously defined template argument that has been explicitly // passed - constructExpression.addTemplateParameter( - templateParametersExplicitInitialization[missingParam]!!, - TemplateDeclaration.TemplateInitialization.DEFAULT - ) + templateParametersExplicitInitialization[missingParam]?.let { + constructExpression.addTemplateParameter( + it, + TemplateDeclaration.TemplateInitialization.DEFAULT + ) + } // If template argument is a type add it as a generic to the type as well - if (templateParametersExplicitInitialization[missingParam] is TypeExpression) { - (templateParametersExplicitInitialization[missingParam] as? TypeExpression) - ?.type - ?.let { (constructExpression.type as ObjectType).addGeneric(it) } + (templateParametersExplicitInitialization[missingParam] as? TypeExpression)?.type?.let { + (constructExpression.type as ObjectType).addGeneric(it) } } else if (missingParam in templateParameterRealDefaultInitialization) { // Add default of template parameter to construct declaration - constructExpression.addTemplateParameter( - templateParameterRealDefaultInitialization[missingParam]!!, - TemplateDeclaration.TemplateInitialization.DEFAULT - ) - if (templateParametersExplicitInitialization[missingParam] is Type) { - (templateParametersExplicitInitialization[missingParam] as? TypeExpression) - ?.type - ?.let { (constructExpression.type as ObjectType).addGeneric(it) } + templateParameterRealDefaultInitialization[missingParam]?.let { + constructExpression.addTemplateParameter( + it, + TemplateDeclaration.TemplateInitialization.DEFAULT + ) + } + (templateParametersExplicitInitialization[missingParam] as? TypeExpression)?.type?.let { + (constructExpression.type as ObjectType).addGeneric(it) } } } 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 7c8cfc3893..2d57fadedb 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 @@ -169,7 +169,6 @@ open class TypeResolver : Pass() { typeState.keys } else { typeState.computeIfAbsent(subType.root, ::mutableListOf) - // typeState[subType.root]!! } val unique = trackedTypes.firstOrNull { it == subType } // TODO Why do we only take the first one even if we don't add it? diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/Inference.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/Inference.kt index ec97a07ed3..f1856e6606 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/Inference.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/Inference.kt @@ -196,7 +196,7 @@ class Inference(val start: Node, val scopeManager: ScopeManager) : } } val paramName = StringBuilder() - while (!hierarchy.isEmpty()) { + while (hierarchy.isNotEmpty()) { val part = hierarchy.pop() if (part.isEmpty()) { continue @@ -262,16 +262,16 @@ class Inference(val start: Node, val scopeManager: ScopeManager) : val inferred = newFunctionTemplateDeclaration(name, code) inferred.isInferred = true - val inferredRealization: FunctionDeclaration = + val inferredRealization = if (record != null) { record.addDeclaration(inferred) record.inferMethod(call, scopeManager = scopeManager) } else { - tu!!.addDeclaration(inferred) - tu.inferFunction(call, scopeManager = scopeManager) + tu?.addDeclaration(inferred) + tu?.inferFunction(call, scopeManager = scopeManager) } - inferred.addRealization(inferredRealization) + inferredRealization?.let { inferred.addRealization(it) } var typeCounter = 0 var nonTypeCounter = 0 diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/GraphExamples.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/GraphExamples.kt index 508cace363..91ea8727d0 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/GraphExamples.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/GraphExamples.kt @@ -372,7 +372,7 @@ class GraphExamples { translationUnit("ControlFlowSensitiveDFGIfMerge.java") { record("ControlFlowSensitiveDFGIfMerge") { field("bla", t("int")) {} - constructor() { + constructor { isImplicit = true receiver = newVariableDeclaration( @@ -806,7 +806,7 @@ class GraphExamples { translationUnit("Dataflow.java") { record("Dataflow") { field("attr", t("String")) { literal("", t("String")) } - constructor() { + constructor { isImplicit = true receiver = newVariableDeclaration("this", t("Dataflow")) body { returnStmt { isImplicit = true } } @@ -869,7 +869,7 @@ class GraphExamples { translationUnit("ShortcutClass.java") { record("ShortcutClass") { field("attr", t("int")) { literal(0, t("int")) } - constructor() { + constructor { receiver = newVariableDeclaration("this", t("ShortcutClass")) isImplicit = true body { returnStmt { isImplicit = true } } diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/calls/ConstructorsTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/calls/ConstructorsTest.kt index 3b1c84e10a..fabfa500eb 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/calls/ConstructorsTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/calls/ConstructorsTest.kt @@ -48,7 +48,7 @@ internal class ConstructorsTest : BaseTest() { val constructors = result.allChildren() val noArg = findByUniquePredicate(constructors) { - it.parameters.size == 0 && it.name.localName == "A" + it.parameters.isEmpty() && it.name.localName == "A" } val singleArg = findByUniquePredicate(constructors) { diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/UnresolvedDFGPassTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/UnresolvedDFGPassTest.kt index 76fef5fab4..4d27c86679 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/UnresolvedDFGPassTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/UnresolvedDFGPassTest.kt @@ -113,7 +113,7 @@ class UnresolvedDFGPassTest { translationUnit("DfgUnresolvedCalls.java") { record("DfgUnresolvedCalls") { field("i", t("int")) { modifiers = listOf("private") } - constructor() { + constructor { receiver = newVariableDeclaration("this", t("DfgUnresolvedCalls")) param("i", t("int")) body { diff --git a/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/DeclarationHandler.kt b/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/DeclarationHandler.kt index 8241eeca80..934d03b3b8 100644 --- a/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/DeclarationHandler.kt +++ b/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/DeclarationHandler.kt @@ -88,11 +88,7 @@ open class DeclarationHandler(lang: JavaLanguageFrontend) : frontend.scopeManager.addDeclaration(param) } - val name = - frontend.scopeManager - .firstScopeOrNull { RecordScope::class.java.isInstance(it) } - ?.astNode - ?.name + val name = frontend.scopeManager.firstScopeOrNull { it is RecordScope }?.astNode?.name if (name != null) { val type = this.parseType(name) declaration.type = type @@ -241,30 +237,37 @@ open class DeclarationHandler(lang: JavaLanguageFrontend) : (decl as? com.github.javaparser.ast.body.FieldDeclaration)?.let { handle(it) // will be added via the scopemanager } - ?: if (decl is MethodDeclaration) { - val md = - handle(decl) - as de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration? - frontend.scopeManager.addDeclaration(md) - } else if (decl is ConstructorDeclaration) { - val c = - handle(decl) - as de.fraunhofer.aisec.cpg.graph.declarations.ConstructorDeclaration? - frontend.scopeManager.addDeclaration(c) - } else if (decl is ClassOrInterfaceDeclaration) { - frontend.scopeManager.addDeclaration(handle(decl)) - } else if (decl is InitializerDeclaration) { - val id = decl - val initializerBlock = frontend.statementHandler.handleBlockStatement(id.body) - initializerBlock.isStaticBlock = id.isStatic - recordDeclaration.addStatement(initializerBlock) - } else { - log.debug( - "Member {} of type {} is something that we do not parse yet: {}", - decl, - recordDeclaration.name, - decl.javaClass.simpleName - ) + ?: 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()) { @@ -420,7 +423,7 @@ open class DeclarationHandler(lang: JavaLanguageFrontend) : // get the last statement var lastStatement: Statement? = null - if (!statements.isEmpty()) { + if (statements.isNotEmpty()) { lastStatement = statements[statements.size - 1] } // make sure, method contains a return statement 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 c41e9af33a..42bed2495a 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 @@ -354,8 +354,7 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : scope.asFieldAccessExpr().nameAsString } val qualifiedNameFromImports = frontend.getQualifiedNameFromImports(name) - val baseType: Type - baseType = + val baseType = if (qualifiedNameFromImports != null) { this.parseType(qualifiedNameFromImports) } else { @@ -433,46 +432,42 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : private fun handleLiteralExpression(expr: Expression): Literal<*>? { val literalExpr = expr.asLiteralExpr() val value = literalExpr.toString() - if (literalExpr is IntegerLiteralExpr) { - return this.newLiteral( - literalExpr.asIntegerLiteralExpr().asNumber(), - this.parseType("int"), - value - ) - } else if (literalExpr is StringLiteralExpr) { - return this.newLiteral( - literalExpr.asStringLiteralExpr().asString(), - this.parseType("java.lang.String"), - value - ) - } else if (literalExpr is BooleanLiteralExpr) { - return this.newLiteral( - literalExpr.asBooleanLiteralExpr().value, - this.parseType("boolean"), - value - ) - } else if (literalExpr is CharLiteralExpr) { - return this.newLiteral( - literalExpr.asCharLiteralExpr().asChar(), - this.parseType("char"), - value - ) - } else if (literalExpr is DoubleLiteralExpr) { - return this.newLiteral( - literalExpr.asDoubleLiteralExpr().asDouble(), - this.parseType("double"), - value - ) - } else if (literalExpr is LongLiteralExpr) { - return this.newLiteral( - literalExpr.asLongLiteralExpr().asNumber(), - this.parseType("long"), - value - ) - } else if (literalExpr is NullLiteralExpr) { - return this.newLiteral(null, this.parseType("null"), value) + return when (literalExpr) { + is IntegerLiteralExpr -> + newLiteral( + literalExpr.asIntegerLiteralExpr().asNumber(), + this.parseType("int"), + value + ) + is StringLiteralExpr -> + newLiteral( + literalExpr.asStringLiteralExpr().asString(), + this.parseType("java.lang.String"), + value + ) + is BooleanLiteralExpr -> + newLiteral( + literalExpr.asBooleanLiteralExpr().value, + this.parseType("boolean"), + value + ) + is CharLiteralExpr -> + newLiteral(literalExpr.asCharLiteralExpr().asChar(), this.parseType("char"), value) + is DoubleLiteralExpr -> + newLiteral( + literalExpr.asDoubleLiteralExpr().asDouble(), + this.parseType("double"), + value + ) + is LongLiteralExpr -> + newLiteral( + literalExpr.asLongLiteralExpr().asNumber(), + this.parseType("long"), + value + ) + is NullLiteralExpr -> newLiteral(null, this.parseType("null"), value) + else -> null } - return null } private fun handleClassExpression(expr: Expression): DeclaredReferenceExpression { @@ -599,8 +594,7 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : this.newDeclaredReferenceExpression(symbol.name, type, nameExpr.toString()) } } catch (ex: UnsolvedSymbolException) { - val typeString: String? - typeString = + val typeString: String? = if ( ex.name.startsWith( "We are unable to find the value declaration corresponding to" 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 dbdf6b8349..1aa4ea007d 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 @@ -109,13 +109,13 @@ open class JavaLanguageFrontend( context = parse(file, parser) bench.addMeasurement() bench = Benchmark(this.javaClass, "Transform to CPG") - context!!.setData(Node.SYMBOL_RESOLVER_KEY, javaSymbolResolver) + context?.setData(Node.SYMBOL_RESOLVER_KEY, javaSymbolResolver) // starting point is always a translation declaration val fileDeclaration = newTranslationUnitDeclaration(file.toString(), context.toString()) currentTU = fileDeclaration scopeManager.resetToGlobal(fileDeclaration) - val packDecl = context!!.packageDeclaration.orElse(null) + val packDecl = context?.packageDeclaration?.orElse(null) var namespaceDeclaration: NamespaceDeclaration? = null if (packDecl != null) { namespaceDeclaration = @@ -125,7 +125,7 @@ open class JavaLanguageFrontend( scopeManager.enterScope(namespaceDeclaration) } - for (type in context!!.types) { + for (type in context?.types ?: listOf()) { // handle each type. all declaration in this type will be added by the scope manager // along // the way @@ -133,7 +133,7 @@ open class JavaLanguageFrontend( scopeManager.addDeclaration(declaration) } - for (anImport in context!!.imports) { + for (anImport in context?.imports ?: listOf()) { val incl = newIncludeDeclaration(anImport.nameAsString) scopeManager.addDeclaration(incl) } @@ -301,14 +301,18 @@ open class JavaLanguageFrontend( if ( ex is UnsolvedSymbolException || ex.cause != null && ex.cause is UnsolvedSymbolException ) { - val qualifier: String = + val qualifier: String? = if (ex is UnsolvedSymbolException) { ex.name } else { - (ex.cause as UnsolvedSymbolException?)!!.name + (ex.cause as UnsolvedSymbolException?)?.name } // this comes from the JavaParser! - if (qualifier.startsWith("We are unable to find") || qualifier.startsWith("Solving ")) { + if ( + qualifier == null || + qualifier.startsWith("We are unable to find") || + qualifier.startsWith("Solving ") + ) { return null } val fromImport = getQualifiedNameFromImports(qualifier)?.toString() @@ -391,7 +395,7 @@ open class JavaLanguageFrontend( val clazz = searchType.asClassOrInterfaceType() if (clazz != null) { // try to look for imports matching the name - for (importDeclaration in context!!.imports) { + for (importDeclaration in context?.imports ?: listOf()) { if (importDeclaration.name.identifier.endsWith(clazz.name.identifier)) { // TODO: handle type parameters return parseType(importDeclaration.nameAsString) @@ -401,8 +405,8 @@ open class JavaLanguageFrontend( // no import found, so our last guess is that the type is in the same package // as our current translation unit - val o = context!!.packageDeclaration - if (o.isPresent) { + val o = context?.packageDeclaration + if (o?.isPresent == true) { name = o.get().nameAsString + language.namespaceDelimiter + name } val returnType = parseType(name) diff --git a/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/StatementHandler.kt b/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/StatementHandler.kt index b064812a6e..0ed5969add 100644 --- a/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/StatementHandler.kt +++ b/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/StatementHandler.kt @@ -603,90 +603,48 @@ class StatementHandler(lang: JavaLanguageFrontend?) : init { map[IfStmt::class.java] = HandlerInterface { stmt: Statement -> handleIfStatement(stmt) } - map[AssertStmt::class.java] = - HandlerInterface { - stmt: Statement -> - handleAssertStatement(stmt) - } - map[WhileStmt::class.java] = - HandlerInterface { - stmt: Statement -> - handleWhileStatement(stmt) - } - map[DoStmt::class.java] = - HandlerInterface { - stmt: Statement -> - handleDoStatement(stmt) - } - map[ForEachStmt::class.java] = - HandlerInterface { - stmt: Statement -> - handleForEachStatement(stmt) - } - map[ForStmt::class.java] = - HandlerInterface { - stmt: Statement -> - handleForStatement(stmt) - } - map[BreakStmt::class.java] = - HandlerInterface { - stmt: Statement -> - handleBreakStatement(stmt) - } - map[ContinueStmt::class.java] = - HandlerInterface { - stmt: Statement -> - handleContinueStatement(stmt) - } - map[ReturnStmt::class.java] = - HandlerInterface { - stmt: Statement -> - handleReturnStatement(stmt) - } - map[BlockStmt::class.java] = - HandlerInterface { - stmt: Statement -> - handleBlockStatement(stmt) - } - map[LabeledStmt::class.java] = - HandlerInterface { - stmt: Statement -> - handleLabelStatement(stmt) - } - map[ExplicitConstructorInvocationStmt::class.java] = - HandlerInterface { - stmt: Statement -> - handleExplicitConstructorInvocation(stmt) - } - map[ExpressionStmt::class.java] = - HandlerInterface { - stmt: Statement -> - handleExpressionStatement(stmt) - } - map[SwitchStmt::class.java] = - HandlerInterface { - stmt: Statement -> - handleSwitchStatement(stmt) - } - map[EmptyStmt::class.java] = - HandlerInterface { - stmt: Statement -> - handleEmptyStatement(stmt) - } - map[SynchronizedStmt::class.java] = - HandlerInterface { - stmt: Statement -> - handleSynchronizedStatement(stmt) - } - map[TryStmt::class.java] = - HandlerInterface { - stmt: Statement -> - handleTryStatement(stmt) - } - map[ThrowStmt::class.java] = - HandlerInterface { - stmt: Statement -> - handleThrowStmt(stmt) - } + map[AssertStmt::class.java] = HandlerInterface { stmt: Statement -> + handleAssertStatement(stmt) + } + map[WhileStmt::class.java] = HandlerInterface { stmt: Statement -> + handleWhileStatement(stmt) + } + map[DoStmt::class.java] = HandlerInterface { stmt: Statement -> handleDoStatement(stmt) } + map[ForEachStmt::class.java] = HandlerInterface { stmt: Statement -> + handleForEachStatement(stmt) + } + map[ForStmt::class.java] = HandlerInterface { stmt: Statement -> handleForStatement(stmt) } + map[BreakStmt::class.java] = HandlerInterface { stmt: Statement -> + handleBreakStatement(stmt) + } + map[ContinueStmt::class.java] = HandlerInterface { stmt: Statement -> + handleContinueStatement(stmt) + } + map[ReturnStmt::class.java] = HandlerInterface { stmt: Statement -> + handleReturnStatement(stmt) + } + map[BlockStmt::class.java] = HandlerInterface { stmt: Statement -> + handleBlockStatement(stmt) + } + map[LabeledStmt::class.java] = HandlerInterface { stmt: Statement -> + handleLabelStatement(stmt) + } + map[ExplicitConstructorInvocationStmt::class.java] = HandlerInterface { stmt: Statement -> + handleExplicitConstructorInvocation(stmt) + } + map[ExpressionStmt::class.java] = HandlerInterface { stmt: Statement -> + handleExpressionStatement(stmt) + } + map[SwitchStmt::class.java] = HandlerInterface { stmt: Statement -> + handleSwitchStatement(stmt) + } + map[EmptyStmt::class.java] = HandlerInterface { stmt: Statement -> + handleEmptyStatement(stmt) + } + map[SynchronizedStmt::class.java] = HandlerInterface { stmt: Statement -> + handleSynchronizedStatement(stmt) + } + map[TryStmt::class.java] = HandlerInterface { stmt: Statement -> handleTryStatement(stmt) } + map[ThrowStmt::class.java] = HandlerInterface { stmt: Statement -> handleThrowStmt(stmt) } } } diff --git a/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/calls/ConstructorsTest.kt b/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/calls/ConstructorsTest.kt index b5b7fb9def..98619506b7 100644 --- a/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/calls/ConstructorsTest.kt +++ b/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/calls/ConstructorsTest.kt @@ -46,7 +46,7 @@ internal class ConstructorsTest : BaseTest() { val result = TestUtils.analyze("java", topLevel, true) { it.registerLanguage(JavaLanguage()) } val constructors = result.allChildren() - val noArg = findByUniquePredicate(constructors) { it.parameters.size == 0 } + val noArg = findByUniquePredicate(constructors) { it.parameters.isEmpty() } val singleArg = findByUniquePredicate(constructors) { it.parameters.size == 1 } val twoArgs = findByUniquePredicate(constructors) { it.parameters.size == 2 } val variables = result.variables diff --git a/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/java/JavaLanguageFrontendTest.kt b/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/java/JavaLanguageFrontendTest.kt index c28fdbacfc..6ecb9932d2 100644 --- a/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/java/JavaLanguageFrontendTest.kt +++ b/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/java/JavaLanguageFrontendTest.kt @@ -335,7 +335,7 @@ internal class JavaLanguageFrontendTest : BaseTest() { } val graphNodes = SubgraphWalker.flattenAST(declaration) - assertTrue(graphNodes.size != 0) + assertTrue(graphNodes.isNotEmpty()) val switchStatements = graphNodes.filterIsInstance() assertEquals(3, switchStatements.size) @@ -376,8 +376,8 @@ internal class JavaLanguageFrontendTest : BaseTest() { // names // vs. fully qualified names. assertTrue( - e.type?.name?.localName == "ExtendedClass" || - e.type?.name?.toString() == "cast.ExtendedClass" + e.type.name.localName == "ExtendedClass" || + e.type.name.toString() == "cast.ExtendedClass" ) // b = (BaseClass) e @@ -386,15 +386,14 @@ internal class JavaLanguageFrontendTest : BaseTest() { val b = stmt.getSingleDeclarationAs(VariableDeclaration::class.java) assertTrue( - b.type?.name?.localName == "BaseClass" || b.type?.name?.toString() == "cast.BaseClass" + b.type.name.localName == "BaseClass" || b.type.name.toString() == "cast.BaseClass" ) // initializer val cast = b.initializer as? CastExpression assertNotNull(cast) assertTrue( - cast.type.name.localName == "BaseClass" || - cast.type.name?.toString() == "cast.BaseClass" + cast.type.name.localName == "BaseClass" || cast.type.name.toString() == "cast.BaseClass" ) // expression itself should be a reference diff --git a/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/ExpressionHandler.kt b/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/ExpressionHandler.kt index 22dbd1b177..ce70d27af5 100644 --- a/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/ExpressionHandler.kt +++ b/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/ExpressionHandler.kt @@ -66,9 +66,9 @@ class ExpressionHandler(lang: LLVMIRLanguageFrontend) : LLVMConstantTokenNoneValueKind -> newLiteral(null, newUnknownType(), frontend.getCodeFromRawNode(value)) LLVMUndefValueValueKind -> - initializeAsUndef(frontend.typeOf(value), frontend.getCodeFromRawNode(value)!!) + initializeAsUndef(frontend.typeOf(value), frontend.getCodeFromRawNode(value)) LLVMConstantAggregateZeroValueKind -> - initializeAsZero(frontend.typeOf(value), frontend.getCodeFromRawNode(value)!!) + initializeAsZero(frontend.typeOf(value), frontend.getCodeFromRawNode(value)) LLVMArgumentValueKind, LLVMGlobalVariableValueKind, // this is a little tricky. It seems weird, that an instruction value kind turns @@ -104,7 +104,7 @@ class ExpressionHandler(lang: LLVMIRLanguageFrontend) : // old stuff from getOperandValue, needs to be refactored to the when above // TODO also move the other stuff to the expression handler - if (LLVMIsConstant(value) != 1) { + return if (LLVMIsConstant(value) != 1) { val operandName: String = if (LLVMIsAGlobalAlias(value) != null || LLVMIsGlobalConstant(value) == 1) { val aliasee = LLVMAliasGetAliasee(value) @@ -115,14 +115,14 @@ class ExpressionHandler(lang: LLVMIRLanguageFrontend) : // representation LLVMPrintValueToString(value).string } - return newLiteral(operandName, cpgType, operandName) + newLiteral(operandName, cpgType, operandName) } else if (LLVMIsUndef(value) == 1) { - return newDeclaredReferenceExpression("undef", cpgType, "undef") + newDeclaredReferenceExpression("undef", cpgType, "undef") } else if (LLVMIsPoison(value) == 1) { - return newDeclaredReferenceExpression("poison", cpgType, "poison") + newDeclaredReferenceExpression("poison", cpgType, "poison") } else { log.error("Unknown expression {}", kind) - return newProblemExpression( + newProblemExpression( "Unknown expression $kind", ProblemNode.ProblemType.TRANSLATION, frontend.getCodeFromRawNode(value) @@ -348,9 +348,11 @@ class ExpressionHandler(lang: LLVMIRLanguageFrontend) : * * Returns a [ConstructExpression]. */ - private fun initializeAsUndef(type: Type, code: String): Expression { - if (!frontend.isKnownStructTypeName(type.name.toString()) && !type.name.contains("{")) { - return newLiteral(null, type, code) + private fun initializeAsUndef(type: Type, code: String?): Expression { + return if ( + !frontend.isKnownStructTypeName(type.name.toString()) && !type.name.contains("{") + ) { + newLiteral(null, type, code) } else { val expr: ConstructExpression = newConstructExpression(code) // map the construct expression to the record declaration of the type @@ -364,7 +366,7 @@ class ExpressionHandler(lang: LLVMIRLanguageFrontend) : expr.addArgument(arg) } - return expr + expr } } @@ -373,9 +375,11 @@ class ExpressionHandler(lang: LLVMIRLanguageFrontend) : * * Returns a [ConstructExpression]. */ - private fun initializeAsZero(type: Type, code: String): Expression { - if (!frontend.isKnownStructTypeName(type.name.toString()) && !type.name.contains("{")) { - return newLiteral(0, type, code) + private fun initializeAsZero(type: Type, code: String?): Expression { + return if ( + !frontend.isKnownStructTypeName(type.name.toString()) && !type.name.contains("{") + ) { + newLiteral(0, type, code) } else { val expr: ConstructExpression = newConstructExpression(code) // map the construct expression to the record declaration of the type @@ -389,7 +393,7 @@ class ExpressionHandler(lang: LLVMIRLanguageFrontend) : expr.addArgument(arg) } - return expr + expr } } diff --git a/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/LLVMIRLanguageFrontend.kt b/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/LLVMIRLanguageFrontend.kt index 72b09e660f..ea3591d8c9 100644 --- a/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/LLVMIRLanguageFrontend.kt +++ b/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/LLVMIRLanguageFrontend.kt @@ -237,7 +237,9 @@ class LLVMIRLanguageFrontend( return null } - override fun setComment(s: S, ctx: T) {} + override fun setComment(s: S, ctx: T) { + // There are no comments in LLVM + } /** Determines if a struct with [name] exists in the scope. */ fun isKnownStructTypeName(name: String): Boolean { @@ -258,10 +260,10 @@ class LLVMIRLanguageFrontend( fun guessSlotNumber(valueRef: LLVMValueRef): String { val code = getCodeFromRawNode(valueRef) - if (code?.contains("=") == true) { - return code.split("=").firstOrNull()?.trim()?.trim('%') ?: "" + return if (code?.contains("=") == true) { + code.split("=").firstOrNull()?.trim()?.trim('%') ?: "" } else { - return "" + "" } } } diff --git a/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/StatementHandler.kt b/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/StatementHandler.kt index 15dcc8c8dd..e29edb41e4 100644 --- a/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/StatementHandler.kt +++ b/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/StatementHandler.kt @@ -244,14 +244,14 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : "cleanuppad" } ) - if (unwindDest != null) { // For "unwind to caller", the destination is null + return if (unwindDest != null) { // For "unwind to caller", the destination is null val gotoStatement = assembleGotoStatement(instr, unwindDest) gotoStatement.name = name - return gotoStatement + gotoStatement } else { val emptyStatement = newEmptyStatement(frontend.getCodeFromRawNode(instr)) emptyStatement.name = name - return emptyStatement + emptyStatement } } @@ -344,7 +344,7 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : // that we will throw something here but we don't know what. We have to fix // that later once we know in which catch-block this statement is executed. val throwOperation = newUnaryOperator("throw", false, true, nodeCode) - currentIfStatement!!.elseStatement = throwOperation + currentIfStatement?.elseStatement = throwOperation } compoundStatement.addStatement(ifStatement) @@ -1119,7 +1119,7 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : var max = LLVMGetNumOperands(instr) - 1 var idx = 0 - if (calledFuncName.equals("")) { + if (calledFuncName == "") { // Function is probably called by a local variable. For some reason, this is the last // operand val opName = frontend.getOperandValueAtIndex(instr, max) @@ -1160,7 +1160,7 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : if (instr.opCode == LLVMInvoke) { // For the "invoke" instruction, the call is surrounded by a try statement which also // contains a goto statement after the call. - val tryStatement = newTryStatement(instrStr!!) + val tryStatement = newTryStatement(instrStr) frontend.scopeManager.enterScope(tryStatement) val tryBlock = newCompoundStatement(instrStr) tryBlock.addStatement(declarationOrNot(callExpr, instr)) @@ -1196,18 +1196,19 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : * [CompressLLVMPass] will move this instruction to the correct location */ private fun handleLandingpad(instr: LLVMValueRef): Statement { - val catchInstr = newCatchClause(frontend.getCodeFromRawNode(instr)!!) + val catchInstr = newCatchClause(frontend.getCodeFromRawNode(instr)) /* Get the number of clauses on the landingpad instruction and iterate through the clauses to get all types for the catch clauses */ val numClauses = LLVMGetNumClauses(instr) var catchType = "" for (i in 0 until numClauses) { val clause = LLVMGetClause(instr, i) if (LLVMIsAConstantArray(clause) == null) { - if (LLVMIsNull(clause) == 1) { - catchType += "..." + " | " - } else { - catchType += LLVMGetValueName(clause).string + " | " - } + catchType += + if (LLVMIsNull(clause) == 1) { + "..." + " | " + } else { + LLVMGetValueName(clause).string + " | " + } } else { // TODO: filter not handled yet } @@ -1616,7 +1617,9 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : } val declOp = if (unordered) binOpUnordered else binaryOperator - val decl = declarationOrNot(declOp!!, instr) + val decl = + declOp?.let { declarationOrNot(it, instr) } + ?: newProblemExpression("Could not parse declaration") (decl as? DeclarationStatement)?.let { // cache binding diff --git a/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CompressLLVMPass.kt b/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CompressLLVMPass.kt index fcbe56deca..8a7c9e404c 100644 --- a/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CompressLLVMPass.kt +++ b/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CompressLLVMPass.kt @@ -215,7 +215,7 @@ class CompressLLVMPass : Pass() { val worklist: Queue = LinkedList() worklist.add(node.body) val alreadyChecked = LinkedHashSet() - while (!worklist.isEmpty()) { + while (worklist.isNotEmpty()) { val currentNode = worklist.remove() alreadyChecked.add(currentNode) // We exclude sub-try statements as they would mess up with the results diff --git a/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/JepSingleton.kt b/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/JepSingleton.kt index b59664d3d8..c01466cb4f 100644 --- a/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/JepSingleton.kt +++ b/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/JepSingleton.kt @@ -63,7 +63,7 @@ object JepSingleton { // We want to have the parent folder of "CPGPython" so that we can do "import CPGPython" // in python. The layout looks like `.../main/CPGPython/__init__.py` -> we have to go // two levels up to get the path of `main`. - var pyFolder = Paths.get(pyInitFile.toURI()).parent.parent + val pyFolder = Paths.get(pyInitFile.toURI()).parent.parent config.addIncludePaths(pyFolder.toString()) } else { val targetFolder = tempFileHolder.pyFolder diff --git a/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonLanguage.kt b/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonLanguage.kt index 78c119b68b..d69d9a27f6 100644 --- a/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonLanguage.kt +++ b/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonLanguage.kt @@ -87,27 +87,28 @@ class PythonLanguage : Language(), HasShortCircuitOperat } override fun propagateTypeOfBinaryOperation(operation: BinaryOperator): Type { + val unknownType = UnknownType.getUnknownType(this) if ( operation.operatorCode == "/" && operation.lhs.propagationType is NumericType && operation.rhs.propagationType is NumericType ) { // In Python, the / operation automatically casts the result to a float - return getSimpleTypeOf("float")!! + return getSimpleTypeOf("float") ?: unknownType } else if ( operation.operatorCode == "//" && operation.lhs.propagationType is NumericType && operation.rhs.propagationType is NumericType ) { - if ( + return if ( operation.lhs.propagationType is IntegerType && operation.rhs.propagationType is IntegerType ) { // In Python, the // operation keeps the type as an int if both inputs are integers // or casts it to a float otherwise. - return getSimpleTypeOf("int")!! + getSimpleTypeOf("int") ?: unknownType } else { - return getSimpleTypeOf("float")!! + getSimpleTypeOf("float") ?: unknownType } } diff --git a/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/DeclarationHandler.kt b/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/DeclarationHandler.kt index 8e00976c53..e433844b9c 100644 --- a/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/DeclarationHandler.kt +++ b/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/DeclarationHandler.kt @@ -113,10 +113,12 @@ class DeclarationHandler(lang: TypeScriptLanguageFrontend) : val type = node.typeChildNode?.let { this.frontend.typeHandler.handle(it) } ?: newUnknownType() - val param = - newParamVariableDeclaration(name, type, false, this.frontend.getCodeFromRawNode(node)) - - return param + return newParamVariableDeclaration( + name, + type, + false, + this.frontend.getCodeFromRawNode(node) + ) } fun handleSourceFile(node: TypeScriptNode): TranslationUnitDeclaration { diff --git a/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/ExpressionHandler.kt b/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/ExpressionHandler.kt index abda442ac7..0837f41d35 100644 --- a/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/ExpressionHandler.kt +++ b/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/ExpressionHandler.kt @@ -65,9 +65,7 @@ class ExpressionHandler(lang: TypeScriptLanguageFrontend) : val key = node.children?.first()?.let { this.handle(it) } val value = node.children?.last()?.let { this.handle(it) } - val keyValue = newKeyValueExpression(key, value, this.frontend.getCodeFromRawNode(node)) - - return keyValue + return newKeyValueExpression(key, value, this.frontend.getCodeFromRawNode(node)) } private fun handleJsxClosingElement(node: TypeScriptNode): Expression { @@ -141,9 +139,7 @@ class ExpressionHandler(lang: TypeScriptLanguageFrontend) : val key = node.children?.first()?.let { this.handle(it) } val value = node.children?.last()?.let { this.handle(it) } - val keyValue = newKeyValueExpression(key, value, this.frontend.getCodeFromRawNode(node)) - - return keyValue + return newKeyValueExpression(key, value, this.frontend.getCodeFromRawNode(node)) } private fun handleObjectLiteralExpression(node: TypeScriptNode): InitializerListExpression { @@ -173,14 +169,11 @@ class ExpressionHandler(lang: TypeScriptLanguageFrontend) : private fun handleIdentifier(node: TypeScriptNode): Expression { val name = this.frontend.getCodeFromRawNode(node)?.trim() ?: "" - val ref = - newDeclaredReferenceExpression( - name, - newUnknownType(), - this.frontend.getCodeFromRawNode(node) - ) - - return ref + return newDeclaredReferenceExpression( + name, + newUnknownType(), + this.frontend.getCodeFromRawNode(node) + ) } private fun handlePropertyAccessExpression(node: TypeScriptNode): Expression { @@ -190,16 +183,13 @@ class ExpressionHandler(lang: TypeScriptLanguageFrontend) : val name = this.frontend.getCodeFromRawNode(node.children?.last()) ?: "" - val memberExpression = - newMemberExpression( - name, - base, - newUnknownType(), - ".", - this.frontend.getCodeFromRawNode(node) - ) - - return memberExpression + return newMemberExpression( + name, + base, + newUnknownType(), + ".", + this.frontend.getCodeFromRawNode(node) + ) } private fun handleCallExpression(node: TypeScriptNode): Expression { diff --git a/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/TypeScriptLanguageFrontend.kt b/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/TypeScriptLanguageFrontend.kt index 9772d64b2a..f814cad6ff 100644 --- a/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/TypeScriptLanguageFrontend.kt +++ b/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/TypeScriptLanguageFrontend.kt @@ -125,9 +125,11 @@ class TypeScriptLanguageFrontend( // the parser does not support comments so we // use a regex as best effort approach. We may recognize something as a comment, which is // acceptable. - val matches: Sequence = - Regex("(?:/\\*((?:[^*]|(?:\\*+[^*/]))*)\\*+/)|(?://(.*))").findAll(currentFileContent!!) - matches.toList().forEach { result -> + val matches: Sequence? = + currentFileContent?.let { + Regex("(?:/\\*((?:[^*]|(?:\\*+[^*/]))*)\\*+/)|(?://(.*))").findAll(it) + } + matches?.toList()?.forEach { result -> val groups = result.groups groups[0]?.let { val commentRegion = getRegionFromStartEnd(file, it.range.first, it.range.last) @@ -143,7 +145,7 @@ class TypeScriptLanguageFrontend( FrontendUtils.matchCommentToNode( comment, - commentRegion ?: translationUnit.location!!.region, + commentRegion ?: translationUnit.location?.region ?: Region(), translationUnit ) } @@ -151,11 +153,7 @@ class TypeScriptLanguageFrontend( } override fun getCodeFromRawNode(astNode: T): String? { - return if (astNode is TypeScriptNode) { - return astNode.code - } else { - null - } + return (astNode as? TypeScriptNode)?.code } override fun getLocationFromRawNode(astNode: T): PhysicalLocation? { @@ -165,8 +163,7 @@ class TypeScriptLanguageFrontend( // Correcting node positions as we have noticed that the parser computes wrong // positions, it is apparent when a file starts with a comment - astNode.code?.let { - val code = it + astNode.code?.let { code -> currentFileContent?.let { position = it.indexOf(code, position) } } @@ -174,14 +171,14 @@ class TypeScriptLanguageFrontend( // should hold, only exceptions are mispositioned empty ast elements val region = getRegionFromStartEnd(File(astNode.location.file), position, astNode.location.end) - return PhysicalLocation(File(astNode.location.file).toURI(), region ?: Region()) + PhysicalLocation(File(astNode.location.file).toURI(), region ?: Region()) } else { null } } fun getRegionFromStartEnd(file: File, start: Int, end: Int): Region? { - val lineNumberReader: LineNumberReader = LineNumberReader(FileReader(file)) + val lineNumberReader = LineNumberReader(FileReader(file)) // Start and end position given by the parser are sometimes including spaces in front of the // code and loc.end - loc.pos > code.length. This is caused by the parser and results in @@ -192,15 +189,17 @@ class TypeScriptLanguageFrontend( lineNumberReader.skip((end - start).toLong()) val endLine = lineNumberReader.lineNumber + 1 - val translationUnitSignature = currentFileContent!! - val region: Region? = - FrontendUtils.parseColumnPositionsFromFile( - translationUnitSignature, - end - start, - start, - startLine, - endLine - ) + val translationUnitSignature = currentFileContent + val region = + translationUnitSignature?.let { + FrontendUtils.parseColumnPositionsFromFile( + it, + end - start, + start, + startLine, + endLine + ) + } return region } @@ -221,9 +220,9 @@ class TypeScriptLanguageFrontend( private fun handleDecorator(node: TypeScriptNode): Annotation { // a decorator can contain a call expression with additional arguments - val call = node.firstChild("CallExpression") - if (call != null) { - val call = this.expressionHandler.handle(call) as CallExpression + val callExpr = node.firstChild("CallExpression") + return if (callExpr != null) { + val call = this.expressionHandler.handle(callExpr) as CallExpression val annotation = newAnnotation(call.name.localName, this.getCodeFromRawNode(node) ?: "") @@ -232,14 +231,12 @@ class TypeScriptLanguageFrontend( call.disconnectFromGraph() - return annotation + annotation } else { // or a decorator just has a simple identifier val name = this.getIdentifierName(node) - val annotation = newAnnotation(name, this.getCodeFromRawNode(node) ?: "") - - return annotation + newAnnotation(name, this.getCodeFromRawNode(node) ?: "") } } } diff --git a/cpg-neo4j/src/main/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/Application.kt b/cpg-neo4j/src/main/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/Application.kt index 32f775c6d9..c4a6c7d08b 100644 --- a/cpg-neo4j/src/main/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/Application.kt +++ b/cpg-neo4j/src/main/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/Application.kt @@ -233,7 +233,8 @@ class Application : Callable { FunctionPointerCallResolver::class, FilenameMapper::class ) - private var passClassMap = passClassList.map { Pair(it.simpleName, it) }.toMap() + private var passClassMap = passClassList.associateBy { it.simpleName } + /** The list of available passes that can be registered. */ private val passList: List get() = passClassList.mapNotNull { it.simpleName } @@ -436,7 +437,7 @@ class Application : Callable { override fun call(): Int { if (mutuallyExclusiveParameters.listPasses) { log.info("List of passes:") - passList.iterator().forEach { log.info("- " + it) } + passList.iterator().forEach { log.info("- $it") } log.info("--") log.info("End of list. Stopping.") return EXIT_SUCCESS From 99dfacfcf32281261920f8051521db8980f88716 Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Sun, 28 May 2023 10:00:41 +0200 Subject: [PATCH 052/143] Overhaul of the pass system (#1169) --- .../aisec/cpg/passes/UnreachableEOGPass.kt | 35 +-- .../cpg/analysis/MultiValueEvaluatorTest.kt | 2 +- .../fsm/ComplexDFAOrderEvaluationTest.kt | 4 +- .../fsm/SimpleDFAOrderEvaluationTest.kt | 4 +- .../cpg/passes/UnreachableEOGPassTest.kt | 2 +- .../cpg/passes/UnreachableEOGandDFGTest.kt | 2 +- .../fraunhofer/aisec/cpg/query/QueryTest.kt | 12 +- cpg-analysis/src/test/resources/log4j2.xml | 14 ++ .../aisec/cpg/graph/TypeManager.java | 2 + .../fraunhofer/aisec/cpg/PopulatedByPass.kt | 2 +- .../aisec/cpg/TranslationConfiguration.kt | 176 ++++++++++----- .../aisec/cpg/TranslationManager.kt | 74 +++---- .../fraunhofer/aisec/cpg/TranslationResult.kt | 4 +- .../fraunhofer/aisec/cpg/graph/Component.kt | 3 +- .../aisec/cpg/graph/builder/Fluent.kt | 3 +- .../TranslationUnitDeclaration.kt | 3 +- .../aisec/cpg/passes/CallResolver.kt | 17 +- .../cpg/passes/ControlFlowSensitiveDFGPass.kt | 19 +- .../de/fraunhofer/aisec/cpg/passes/DFGPass.kt | 18 +- .../aisec/cpg/passes/EdgeCachePass.kt | 21 +- .../cpg/passes/EvaluationOrderGraphPass.kt | 16 +- .../aisec/cpg/passes/FilenameMapper.kt | 17 +- .../cpg/passes/FunctionPointerCallResolver.kt | 18 +- .../aisec/cpg/passes/ImportResolver.kt | 11 +- .../de/fraunhofer/aisec/cpg/passes/Pass.kt | 206 ++++++++++++------ .../cpg/passes/StatisticsCollectionPass.kt | 16 +- .../aisec/cpg/passes/SymbolResolverPass.kt | 5 +- .../aisec/cpg/passes/TypeHierarchyResolver.kt | 13 +- .../aisec/cpg/passes/TypeResolver.kt | 13 +- .../aisec/cpg/passes/VariableUsageResolver.kt | 17 +- .../aisec/cpg/passes/order/DependsOn.kt | 2 +- .../aisec/cpg/passes/order/ExecuteBefore.kt | 2 +- .../cpg/passes/order/PassWithDependencies.kt | 33 ++- .../cpg/passes/order/PassWithDepsContainer.kt | 66 +++--- .../cpg/passes/order/RegisterExtraPass.kt | 2 +- .../aisec/cpg/passes/order/ReplacePass.kt | 47 ++++ .../frontends/cpp/CXXLanguageFrontendTest.kt | 40 ++-- .../fraunhofer/aisec/cpg/graph/FluentTest.kt | 3 +- .../aisec/cpg/graph/ShortcutsTest.kt | 2 +- .../expressions/AssignExpressionTest.kt | 2 +- .../cpg/graph/types/TypePropagationTest.kt | 2 +- .../aisec/cpg/passes/ReplaceTest.kt | 96 ++++++++ .../aisec/cpg/frontends/TestLanguage.kt | 2 +- .../aisec/cpg/passes/GoExtraPass.kt | 12 +- .../JavaExternalTypeHierarchyResolver.kt | 26 ++- .../aisec/cpg/graph/types/TypeTests.kt | 10 +- .../aisec/cpg/passes/CompressLLVMPass.kt | 14 +- .../aisec/cpg_vis_neo4j/Application.kt | 8 +- 48 files changed, 734 insertions(+), 384 deletions(-) create mode 100644 cpg-analysis/src/test/resources/log4j2.xml create mode 100644 cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/order/ReplacePass.kt create mode 100644 cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ReplaceTest.kt diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGPass.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGPass.kt index 99db6a551d..204c0ebc46 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGPass.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGPass.kt @@ -25,9 +25,11 @@ */ package de.fraunhofer.aisec.cpg.passes -import de.fraunhofer.aisec.cpg.TranslationResult +import de.fraunhofer.aisec.cpg.ScopeManager +import de.fraunhofer.aisec.cpg.TranslationConfiguration import de.fraunhofer.aisec.cpg.analysis.ValueEvaluator import de.fraunhofer.aisec.cpg.graph.Node +import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration import de.fraunhofer.aisec.cpg.graph.edge.Properties import de.fraunhofer.aisec.cpg.graph.statements.IfStatement import de.fraunhofer.aisec.cpg.graph.statements.WhileStatement @@ -40,23 +42,24 @@ import de.fraunhofer.aisec.cpg.processing.strategy.Strategy * by setting the [Properties.UNREACHABLE] property of an eog-edge to true. */ @DependsOn(ControlFlowSensitiveDFGPass::class) -class UnreachableEOGPass : Pass() { - override fun accept(t: TranslationResult) { - for (tu in t.translationUnits) { - tu.accept( - Strategy::AST_FORWARD, - object : IVisitor() { - override fun visit(t: Node) { - when (t) { - is IfStatement -> handleIfStatement(t) - is WhileStatement -> handleWhileStatement(t) - } - - super.visit(t) +class UnreachableEOGPass( + config: TranslationConfiguration, + scopeManager: ScopeManager, +) : TranslationUnitPass(config, scopeManager) { + override fun accept(tu: TranslationUnitDeclaration) { + tu.accept( + Strategy::AST_FORWARD, + object : IVisitor() { + override fun visit(t: Node) { + when (t) { + is IfStatement -> handleIfStatement(t) + is WhileStatement -> handleWhileStatement(t) } + + super.visit(t) } - ) - } + } + ) } private fun handleIfStatement(n: IfStatement) { diff --git a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluatorTest.kt b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluatorTest.kt index 232e3c4a1c..b29549de7c 100644 --- a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluatorTest.kt +++ b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluatorTest.kt @@ -195,7 +195,7 @@ class MultiValueEvaluatorTest { topLevel, true ) { - it.registerPass(EdgeCachePass()) + it.registerPass() } assertNotNull(tu) diff --git a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/fsm/ComplexDFAOrderEvaluationTest.kt b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/fsm/ComplexDFAOrderEvaluationTest.kt index fdaed7e8cd..d53b8ffca0 100644 --- a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/fsm/ComplexDFAOrderEvaluationTest.kt +++ b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/fsm/ComplexDFAOrderEvaluationTest.kt @@ -86,8 +86,8 @@ class ComplexDFAOrderEvaluationTest { true ) { it.registerLanguage() - .registerPass(UnreachableEOGPass()) - .registerPass(EdgeCachePass()) + .registerPass() + .registerPass() } } diff --git a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/fsm/SimpleDFAOrderEvaluationTest.kt b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/fsm/SimpleDFAOrderEvaluationTest.kt index 8b319d8837..3f79a849d7 100644 --- a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/fsm/SimpleDFAOrderEvaluationTest.kt +++ b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/fsm/SimpleDFAOrderEvaluationTest.kt @@ -73,8 +73,8 @@ class SimpleDFAOrderEvaluationTest { true ) { it.registerLanguage() - .registerPass(UnreachableEOGPass()) - .registerPass(EdgeCachePass()) + .registerPass() + .registerPass() } } diff --git a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGPassTest.kt b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGPassTest.kt index 78f1371dfc..5f0b701a30 100644 --- a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGPassTest.kt +++ b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGPassTest.kt @@ -55,7 +55,7 @@ class UnreachableEOGPassTest { topLevel, true ) { - it.registerLanguage().registerPass(UnreachableEOGPass()) + it.registerLanguage().registerPass() } } diff --git a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGandDFGTest.kt b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGandDFGTest.kt index 13e2bb9545..f23854f609 100644 --- a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGandDFGTest.kt +++ b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGandDFGTest.kt @@ -54,7 +54,7 @@ class UnreachableEOGandDFGTest { topLevel, true ) { - it.registerLanguage().registerPass(UnreachableEOGPass()) + it.registerLanguage().registerPass() } } diff --git a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/query/QueryTest.kt b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/query/QueryTest.kt index a035f655ed..0a70fa2e8d 100644 --- a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/query/QueryTest.kt +++ b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/query/QueryTest.kt @@ -552,7 +552,7 @@ class QueryTest { .sourceLocations(File("src/test/resources/query/array2.cpp")) .defaultPasses() .defaultLanguages() - .registerPass(EdgeCachePass()) + .registerPass() .build() val analyzer = TranslationManager.builder().config(config).build() @@ -585,7 +585,7 @@ class QueryTest { .sourceLocations(File("src/test/resources/query/array3.cpp")) .defaultPasses() .defaultLanguages() - .registerPass(EdgeCachePass()) + .registerPass() .build() val analyzer = TranslationManager.builder().config(config).build() @@ -636,7 +636,7 @@ class QueryTest { .sourceLocations(File("src/test/resources/query/array_correct.cpp")) .defaultPasses() .defaultLanguages() - .registerPass(EdgeCachePass()) + .registerPass() .build() val analyzer = TranslationManager.builder().config(config).build() @@ -817,7 +817,7 @@ class QueryTest { .defaultPasses() .defaultLanguages() .registerLanguage(JavaLanguage()) - .registerPass(EdgeCachePass()) + .registerPass() .build() val analyzer = TranslationManager.builder().config(config).build() @@ -871,7 +871,7 @@ class QueryTest { .defaultPasses() .defaultLanguages() .registerLanguage(JavaLanguage()) - .registerPass(EdgeCachePass()) + .registerPass() .build() val analyzer = TranslationManager.builder().config(config).build() @@ -925,7 +925,7 @@ class QueryTest { .defaultPasses() .defaultLanguages() .registerLanguage(JavaLanguage()) - .registerPass(EdgeCachePass()) + .registerPass() .build() val analyzer = TranslationManager.builder().config(config).build() diff --git a/cpg-analysis/src/test/resources/log4j2.xml b/cpg-analysis/src/test/resources/log4j2.xml new file mode 100644 index 0000000000..ac6e67063f --- /dev/null +++ b/cpg-analysis/src/test/resources/log4j2.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/TypeManager.java b/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/TypeManager.java index 553c0284b2..674fea298e 100644 --- a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/TypeManager.java +++ b/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/TypeManager.java @@ -121,6 +121,7 @@ public ParameterizedType getTypeParameter(RecordDeclaration recordDeclaration, S * @param typeParameters List containing all ParameterizedTypes used by the recordDeclaration and * will be stored as value in the map */ + @Deprecated public void addTypeParameter( RecordDeclaration recordDeclaration, List typeParameters) { this.recordToTypeParameters.put(recordDeclaration, typeParameters); @@ -135,6 +136,7 @@ public void addTypeParameter( * @return */ @Nullable + @Deprecated public ParameterizedType getTypeParameter(TemplateDeclaration templateDeclaration, String name) { if (this.templateToTypeParameters.containsKey(templateDeclaration)) { for (ParameterizedType parameterizedType : diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/PopulatedByPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/PopulatedByPass.kt index 594efcd9a4..1362daeabb 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/PopulatedByPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/PopulatedByPass.kt @@ -32,4 +32,4 @@ import kotlin.reflect.KClass * This annotation denotes that, this property is populates by a pass. Optionally, also specifying * which Pass class is responsible. */ -annotation class PopulatedByPass(vararg val value: KClass) +annotation class PopulatedByPass(vararg val value: KClass>) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationConfiguration.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationConfiguration.kt index 17fc3b1d83..1a8729aed1 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationConfiguration.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationConfiguration.kt @@ -25,10 +25,9 @@ */ package de.fraunhofer.aisec.cpg -import com.fasterxml.jackson.annotation.JsonIdentityInfo -import com.fasterxml.jackson.annotation.JsonIdentityReference -import com.fasterxml.jackson.annotation.ObjectIdGenerators +import com.fasterxml.jackson.databind.annotation.JsonSerialize import de.fraunhofer.aisec.cpg.frontends.CompilationDatabase +import de.fraunhofer.aisec.cpg.frontends.KClassSerializer import de.fraunhofer.aisec.cpg.frontends.Language import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend import de.fraunhofer.aisec.cpg.frontends.cpp.CLanguage @@ -38,6 +37,7 @@ import de.fraunhofer.aisec.cpg.passes.order.* import java.io.File import java.nio.file.Path import java.util.* +import kotlin.reflect.KClass import kotlin.reflect.full.createInstance import kotlin.reflect.full.findAnnotations import kotlin.reflect.full.primaryConstructor @@ -93,7 +93,14 @@ private constructor( * always take priority over those in the whitelist. */ val includeBlocklist: List, - passes: List, + passes: List>>, + /** + * This map offers the possibility to replace certain passes for specific languages with other + * passes. It can either be filled with the [Builder.replacePass] or by using the [ReplacePass] + * annotation on a [LanguageFrontend]. + */ + val replacedPasses: + Map>, KClass>>, KClass>>, languages: List>, codeInNodes: Boolean, processAnnotations: Boolean, @@ -162,12 +169,8 @@ private constructor( /** If true the (cpp) frontend connects a node to required includes. */ val addIncludesToGraph: Boolean - @get:JsonIdentityReference(alwaysAsId = true) - @get:JsonIdentityInfo( - generator = ObjectIdGenerators.PropertyGenerator::class, - property = "name" - ) - val registeredPasses: List + @get:JsonSerialize(contentUsing = KClassSerializer::class) + val registeredPasses: List>> /** This sub configuration object holds all information about inference and smart-guessing. */ val inferenceConfiguration: InferenceConfiguration @@ -202,9 +205,15 @@ private constructor( * Builds a [TranslationConfiguration]. * * Example: - *

`TranslationManager.builder() .config( TranslationConfiguration.builder()
-     * .sourceLocations(new File("example.cpp")) .defaultPasses() .debugParser(true) .build())
-     * .build(); `
* + * ``` + * TranslationManager.builder() + * .config(TranslationConfiguration.builder() + * .sourceLocations(new File("example.cpp")) + * .defaultPasses() + * .debugParser(true) + * .build()) + * .build(); + * ``` */ class Builder { private var softwareComponents: MutableMap> = HashMap() @@ -217,7 +226,9 @@ private constructor( private val includePaths = mutableListOf() private val includeWhitelist = mutableListOf() private val includeBlocklist = mutableListOf() - private val passes = mutableListOf() + private val passes = mutableListOf>>() + private val replacedPasses = + mutableMapOf>, KClass>>, KClass>>() private var codeInNodes = true private var processAnnotations = false private var disableCleanup = false @@ -367,9 +378,30 @@ private constructor( return this } + inline fun > registerPass(): Builder { + registerPass(P::class) + return this + } + /** Register an additional [Pass]. */ - fun registerPass(pass: Pass): Builder { - passes.add(pass) + fun registerPass(passType: KClass>): Builder { + passes.add(passType) + return this + } + + inline fun < + reified OldPass : Pass<*>, + reified For : Language<*>, + reified With : Pass<*>> replacePass(): Builder { + return replacePass(OldPass::class, For::class, With::class) + } + + fun replacePass( + passType: KClass>, + forLanguage: KClass>, + with: KClass> + ): Builder { + replacedPasses[Pair(passType, forLanguage)] = with return this } @@ -435,16 +467,16 @@ private constructor( * to be executed in the order specified by their annotations. */ fun defaultPasses(): Builder { - registerPass(TypeHierarchyResolver()) - registerPass(ImportResolver()) - registerPass(VariableUsageResolver()) - registerPass(CallResolver()) // creates CG - registerPass(DFGPass()) - registerPass(EvaluationOrderGraphPass()) // creates EOG - registerPass(TypeResolver()) - registerPass(ControlFlowSensitiveDFGPass()) - registerPass(FunctionPointerCallResolver()) - registerPass(FilenameMapper()) + registerPass() + registerPass() + registerPass() + registerPass() // creates CG + registerPass() + registerPass() // creates EOG + registerPass() + registerPass() + registerPass() + registerPass() return this } @@ -453,22 +485,28 @@ private constructor( private fun registerExtraFrontendPasses() { for (frontend in languages.map(Language::frontend)) { val extraPasses = frontend.findAnnotations() - if (extraPasses.isNotEmpty()) { for (p in extraPasses) { - val pass = p.value.primaryConstructor?.call() - if (pass != null) { - registerPass(pass) - - log.info( - "Registered an extra (frontend dependent) default dependency: {}", - p.value - ) - } else { - throw ConfigurationException( - "Failed to load frontend because we could not register required pass dependency: ${frontend.simpleName}" - ) - } + registerPass(p.value) + log.info( + "Registered an extra (frontend dependent) default dependency: {}", + p.value + ) + } + } + } + } + + private fun registerReplacedPasses() { + for (frontend in languages.map(Language::frontend)) { + val replacedPasses = frontend.findAnnotations() + if (replacedPasses.isNotEmpty()) { + for (p in replacedPasses) { + replacePass(p.old, p.lang, p.with) + log.info( + "Registered an extra (frontend dependent) default dependency, which replaced an existing pass: {}", + p.old + ) } } } @@ -567,6 +605,7 @@ private constructor( ) } registerExtraFrontendPasses() + registerReplacedPasses() return TranslationConfiguration( symbols, softwareComponents, @@ -578,6 +617,7 @@ private constructor( includeWhitelist, includeBlocklist, orderPasses(), + replacedPasses, languages, codeInNodes, processAnnotations, @@ -601,28 +641,58 @@ private constructor( private fun collectInitialPasses(): PassWithDepsContainer { val workingList = PassWithDepsContainer() + val softDependencies = + mutableMapOf>, MutableSet>>>() + val hardDependencies = + mutableMapOf>, MutableSet>>>() + // Add the "execute before" dependencies. for (p in passes) { - val executeBefore = p.executeBefore + val executeBefore = mutableListOf>>() + + val depAnn = p.findAnnotations() + // collect all dependencies added by [DependsOn] annotations. + for (d in depAnn) { + val deps = + if (d.softDependency) { + softDependencies.computeIfAbsent(p) { mutableSetOf() } + } else { + hardDependencies.computeIfAbsent(p) { mutableSetOf() } + } + deps += d.value + } + + val execBeforeAnn = p.findAnnotations() + for (d in execBeforeAnn) { + executeBefore.add(d.other) + } + for (eb in executeBefore) { passes - .filter { eb.isInstance(it) } - .forEach { it.addSoftDependency(p.javaClass) } + .filter { eb == it } + .forEach { + val deps = softDependencies.computeIfAbsent(it) { mutableSetOf() } + deps += p + } } } + for (p in passes) { var passFound = false for ((pass) in workingList.getWorkingList()) { - if (pass.javaClass == p.javaClass) { + if (pass == p) { passFound = true break } } if (!passFound) { - val deps: MutableSet> = HashSet() - deps.addAll(p.hardDependencies) - deps.addAll(p.softDependencies) - workingList.addToWorkingList(PassWithDependencies(p, deps)) + workingList.addToWorkingList( + PassWithDependencies( + p, + hardDependencies[p] ?: mutableSetOf(), + softDependencies[p] ?: mutableSetOf() + ) + ) } } return workingList @@ -646,16 +716,16 @@ private constructor( * [PassWithDepsContainer.workingList] * 1. The first pass [ExecuteFirst] is added to the result and removed from the other passes * dependencies - * 1. The first pass in the [workingList] without dependencies is added to the result and it + * 1. The first pass in the workingList without dependencies is added to the result and it * is removed from the other passes dependencies * 1. The above step is repeated until all passes are added to the result * * @return a sorted list of passes */ @Throws(ConfigurationException::class) - private fun orderPasses(): List { - log.info("Passes before enforcing order: {}", passes) - val result = mutableListOf() + private fun orderPasses(): List>> { + log.info("Passes before enforcing order: {}", passes.map { it.simpleName }) + val result = mutableListOf>>() // Create a local copy of all passes and their "current" dependencies without possible // duplicates @@ -692,7 +762,7 @@ private constructor( throw ConfigurationException("Failed to satisfy ordering requirements.") } } - log.info("Passes after enforcing order: {}", result) + log.info("Passes after enforcing order: {}", result.map { it.simpleName }) return result } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationManager.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationManager.kt index acd857c6c0..f58640cea8 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationManager.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationManager.kt @@ -35,7 +35,7 @@ import de.fraunhofer.aisec.cpg.graph.Name import de.fraunhofer.aisec.cpg.graph.TypeManager import de.fraunhofer.aisec.cpg.helpers.Benchmark import de.fraunhofer.aisec.cpg.helpers.Util -import de.fraunhofer.aisec.cpg.passes.Pass +import de.fraunhofer.aisec.cpg.passes.* import java.io.File import java.io.PrintWriter import java.lang.reflect.InvocationTargetException @@ -74,56 +74,44 @@ private constructor( val result = TranslationResult(this, ScopeManager()) // We wrap the analysis in a CompletableFuture, i.e. in an async task. - return CompletableFuture.supplyAsync { - val outerBench = - Benchmark( - TranslationManager::class.java, - "Translation into full graph", - false, - result - ) - val executedPasses = mutableSetOf() - var executedFrontends = setOf() - - try { - // Parse Java/C/CPP files - var bench = Benchmark(this.javaClass, "Executing Language Frontend", false, result) - executedFrontends = runFrontends(result, config) - bench.addMeasurement() + return CompletableFuture.supplyAsync { analyze2(result) } + } - // Apply passes - for (pass in config.registeredPasses) { - bench = Benchmark(pass.javaClass, "Executing Pass", false, result) - if (pass.runsWithCurrentFrontend(executedFrontends)) { - executedPasses.add(pass) - pass.accept(result) - } - bench.addMeasurement() - if (result.isCancelled) { - log.warn("Analysis interrupted, stopping Pass evaluation") - } - } - } catch (ex: TranslationException) { - throw CompletionException(ex) - } finally { - outerBench.addMeasurement() - if (!config.disableCleanup) { - log.debug("Cleaning up {} Passes", executedPasses.size) + fun analyze2(result: TranslationResult): TranslationResult { + val outerBench = + Benchmark(TranslationManager::class.java, "Translation into full graph", false, result) + var executedFrontends = setOf() - executedPasses.forEach { it.cleanup() } + try { + // Parse Java/C/CPP files + var bench = Benchmark(this.javaClass, "Executing Language Frontend", false, result) + executedFrontends = runFrontends(result, config) + bench.addMeasurement() - log.debug("Cleaning up {} Frontends", executedFrontends.size) + // Apply passes + for (pass in config.registeredPasses) { + bench = Benchmark(pass.java, "Executing Pass", false, result) + executePassSequential(pass, result, executedFrontends) - executedFrontends.forEach { it.cleanup() } - TypeManager.getInstance().cleanup() + bench.addMeasurement() + if (result.isCancelled) { + log.warn("Analysis interrupted, stopping Pass evaluation") } } - result + } catch (ex: TranslationException) { + throw CompletionException(ex) + } finally { + outerBench.addMeasurement() + if (!config.disableCleanup) { + log.debug("Cleaning up {} Frontends", executedFrontends.size) + + executedFrontends.forEach { it.cleanup() } + TypeManager.getInstance().cleanup() + } } - } - val passes: List - get() = config.registeredPasses + return result + } fun isCancelled(): Boolean { return isCancelled.get() diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationResult.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationResult.kt index 2030ef3a33..9fe035d606 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationResult.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationResult.kt @@ -29,6 +29,7 @@ import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration import de.fraunhofer.aisec.cpg.helpers.MeasurementHolder import de.fraunhofer.aisec.cpg.helpers.StatisticsHolder +import de.fraunhofer.aisec.cpg.passes.PassTarget import java.util.* import java.util.concurrent.ConcurrentHashMap import java.util.function.Consumer @@ -47,7 +48,7 @@ class TranslationResult( * and then finally merged into this one. */ val scopeManager: ScopeManager -) : Node(), StatisticsHolder { +) : Node(), StatisticsHolder, PassTarget { /** * Entry points to the CPG: "SoftwareComponent" refer to programs, application, other "bundles" @@ -80,6 +81,7 @@ class TranslationResult( * * @return the list of all translation units. */ + @Deprecated(message = "translation units of individual components should be accessed instead") val translationUnits: List get() { if (components.size == 1) { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Component.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Component.kt index 18989cc527..efbc448af7 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Component.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Component.kt @@ -26,6 +26,7 @@ package de.fraunhofer.aisec.cpg.graph import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration +import de.fraunhofer.aisec.cpg.passes.PassTarget /** * A node which presents some kind of complete piece of software, e.g., an application, a library, @@ -34,7 +35,7 @@ import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration * This node holds all translation units belonging to this software component as well as (potential) * entry points or interactions with other software. */ -open class Component() : Node() { +open class Component() : Node(), PassTarget { /** All translation units belonging to this application. */ @AST val translationUnits: MutableList = mutableListOf() diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt index 94dc1c0ba3..33a430b198 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt @@ -36,6 +36,7 @@ import de.fraunhofer.aisec.cpg.graph.scopes.RecordScope import de.fraunhofer.aisec.cpg.graph.statements.* import de.fraunhofer.aisec.cpg.graph.statements.expressions.* import de.fraunhofer.aisec.cpg.graph.types.Type +import de.fraunhofer.aisec.cpg.passes.executePassSequential fun LanguageFrontend.translationResult( config: TranslationConfiguration, @@ -46,7 +47,7 @@ fun LanguageFrontend.translationResult( node.addComponent(component) init(node) - config.registeredPasses.forEach { it.accept(node) } + config.registeredPasses.forEach { executePassSequential(it, node, listOf()) } return node } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/TranslationUnitDeclaration.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/TranslationUnitDeclaration.kt index 42e7f79852..ff4d0b6f62 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/TranslationUnitDeclaration.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/TranslationUnitDeclaration.kt @@ -33,12 +33,13 @@ import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.propertyEqualsList import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.unwrap import de.fraunhofer.aisec.cpg.graph.statements.Statement +import de.fraunhofer.aisec.cpg.passes.PassTarget import java.util.Objects import org.apache.commons.lang3.builder.ToStringBuilder import org.neo4j.ogm.annotation.Relationship /** The top most declaration, representing a translation unit, for example a file. */ -class TranslationUnitDeclaration : Declaration(), DeclarationHolder, StatementHolder { +class TranslationUnitDeclaration : Declaration(), DeclarationHolder, StatementHolder, PassTarget { /** A list of declarations within this unit. */ @Relationship(value = "DECLARATIONS", direction = Relationship.Direction.OUTGOING) @AST diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolver.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolver.kt index b8218cb4ea..2db61abddd 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolver.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolver.kt @@ -25,7 +25,8 @@ */ package de.fraunhofer.aisec.cpg.passes -import de.fraunhofer.aisec.cpg.TranslationResult +import de.fraunhofer.aisec.cpg.ScopeManager +import de.fraunhofer.aisec.cpg.TranslationConfiguration import de.fraunhofer.aisec.cpg.frontends.HasComplexCallResolution import de.fraunhofer.aisec.cpg.frontends.HasDefaultArguments import de.fraunhofer.aisec.cpg.frontends.HasSuperClasses @@ -64,7 +65,8 @@ import org.slf4j.LoggerFactory * This pass should NOT use any DFG edges because they are computed / adjusted in a later stage. */ @DependsOn(VariableUsageResolver::class) -open class CallResolver : SymbolResolverPass() { +open class CallResolver(config: TranslationConfiguration, scopeManager: ScopeManager) : + SymbolResolverPass(config, scopeManager) { /** * This seems to be a map between function declarations (more likely method declarations) and * their parent record (more accurately their type). Seems to be only used by @@ -76,10 +78,7 @@ open class CallResolver : SymbolResolverPass() { containingType.clear() } - override fun accept(translationResult: TranslationResult) { - scopeManager = translationResult.scopeManager - config = translationResult.config - + override fun accept(component: Component) { walker = ScopedWalker(scopeManager) walker.registerHandler { _, _, currNode -> walker.collectDeclarations(currNode) } walker.registerHandler { node, _ -> findRecords(node) } @@ -88,17 +87,17 @@ open class CallResolver : SymbolResolverPass() { registerMethods(currentClass, currentNode) } - for (tu in translationResult.translationUnits) { + for (tu in component.translationUnits) { walker.iterate(tu) } walker.clearCallbacks() walker.registerHandler { node, _ -> fixInitializers(node) } - for (tu in translationResult.translationUnits) { + for (tu in component.translationUnits) { walker.iterate(tu) } walker.clearCallbacks() walker.registerHandler { node, _ -> handleNode(node) } - for (tu in translationResult.translationUnits) { + for (tu in component.translationUnits) { walker.iterate(tu) } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt index 20460c3453..945cef99c6 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt @@ -25,12 +25,10 @@ */ package de.fraunhofer.aisec.cpg.passes -import de.fraunhofer.aisec.cpg.TranslationResult +import de.fraunhofer.aisec.cpg.ScopeManager +import de.fraunhofer.aisec.cpg.TranslationConfiguration import de.fraunhofer.aisec.cpg.graph.* -import de.fraunhofer.aisec.cpg.graph.declarations.Declaration -import de.fraunhofer.aisec.cpg.graph.declarations.FieldDeclaration -import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration -import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration +import de.fraunhofer.aisec.cpg.graph.declarations.* import de.fraunhofer.aisec.cpg.graph.edge.Properties import de.fraunhofer.aisec.cpg.graph.statements.* import de.fraunhofer.aisec.cpg.graph.statements.expressions.BinaryOperator @@ -48,17 +46,18 @@ import de.fraunhofer.aisec.cpg.passes.order.DependsOn */ @DependsOn(EvaluationOrderGraphPass::class) @DependsOn(DFGPass::class) -open class ControlFlowSensitiveDFGPass : Pass() { +open class ControlFlowSensitiveDFGPass( + config: TranslationConfiguration, + scopeManager: ScopeManager, +) : TranslationUnitPass(config, scopeManager) { override fun cleanup() { // Nothing to do } - override fun accept(translationResult: TranslationResult) { + override fun accept(tu: TranslationUnitDeclaration) { val walker = IterativeGraphWalker() walker.registerOnNodeVisit(::handle) - for (tu in translationResult.translationUnits) { - walker.iterate(tu) - } + walker.iterate(tu) } /** diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/DFGPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/DFGPass.kt index 33f4ea678f..6a5a223be0 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/DFGPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/DFGPass.kt @@ -25,16 +25,14 @@ */ package de.fraunhofer.aisec.cpg.passes -import de.fraunhofer.aisec.cpg.TranslationResult -import de.fraunhofer.aisec.cpg.graph.AccessValues -import de.fraunhofer.aisec.cpg.graph.Node -import de.fraunhofer.aisec.cpg.graph.allChildren +import de.fraunhofer.aisec.cpg.ScopeManager +import de.fraunhofer.aisec.cpg.TranslationConfiguration +import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.declarations.FieldDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration import de.fraunhofer.aisec.cpg.graph.statements.* import de.fraunhofer.aisec.cpg.graph.statements.expressions.* -import de.fraunhofer.aisec.cpg.graph.variables import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker.IterativeGraphWalker import de.fraunhofer.aisec.cpg.helpers.Util import de.fraunhofer.aisec.cpg.passes.order.DependsOn @@ -42,15 +40,15 @@ import de.fraunhofer.aisec.cpg.passes.order.DependsOn /** Adds the DFG edges for various types of nodes. */ @DependsOn(VariableUsageResolver::class) @DependsOn(CallResolver::class) -class DFGPass : Pass() { - override fun accept(tr: TranslationResult) { - val inferDfgForUnresolvedCalls = - tr.translationManager.config.inferenceConfiguration.inferDfgForUnresolvedSymbols +class DFGPass(config: TranslationConfiguration, scopeManager: ScopeManager) : + ComponentPass(config, scopeManager) { + override fun accept(component: Component) { + val inferDfgForUnresolvedCalls = config.inferenceConfiguration.inferDfgForUnresolvedSymbols val walker = IterativeGraphWalker() walker.registerOnNodeVisit2 { node, parent -> handle(node, parent, inferDfgForUnresolvedCalls) } - for (tu in tr.translationUnits) { + for (tu in component.translationUnits) { walker.iterate(tu) } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EdgeCachePass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EdgeCachePass.kt index fb4b77631c..daf12316d3 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EdgeCachePass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EdgeCachePass.kt @@ -25,7 +25,9 @@ */ package de.fraunhofer.aisec.cpg.passes -import de.fraunhofer.aisec.cpg.TranslationResult +import de.fraunhofer.aisec.cpg.ScopeManager +import de.fraunhofer.aisec.cpg.TranslationConfiguration +import de.fraunhofer.aisec.cpg.graph.Component import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker import de.fraunhofer.aisec.cpg.processing.IVisitor @@ -81,20 +83,21 @@ object Edges { * * The cache itself is stored in the [Edges] object. */ -class EdgeCachePass : Pass() { - override fun accept(result: TranslationResult) { +class EdgeCachePass(config: TranslationConfiguration, scopeManager: ScopeManager) : + ComponentPass(config, scopeManager) { + override fun accept(component: Component) { Edges.clear() - for (tu in result.translationUnits) { + for (tu in component.translationUnits) { tu.accept( Strategy::AST_FORWARD, object : IVisitor() { - override fun visit(n: Node) { - visitAST(n) - visitDFG(n) - visitEOG(n) + override fun visit(t: Node) { + visitAST(t) + visitDFG(t) + visitEOG(t) - super.visit(n) + super.visit(t) } } ) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt index 55bffd9ac6..7b13a853a4 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt @@ -25,7 +25,8 @@ */ package de.fraunhofer.aisec.cpg.passes -import de.fraunhofer.aisec.cpg.TranslationResult +import de.fraunhofer.aisec.cpg.ScopeManager +import de.fraunhofer.aisec.cpg.TranslationConfiguration import de.fraunhofer.aisec.cpg.frontends.HasShortCircuitOperators import de.fraunhofer.aisec.cpg.frontends.ProcessedListener import de.fraunhofer.aisec.cpg.graph.Node @@ -70,7 +71,8 @@ import org.slf4j.LoggerFactory */ @Suppress("MemberVisibilityCanBePrivate") @DependsOn(CallResolver::class) -open class EvaluationOrderGraphPass : Pass() { +open class EvaluationOrderGraphPass(config: TranslationConfiguration, scopeManager: ScopeManager) : + TranslationUnitPass(config, scopeManager) { protected val map = mutableMapOf, (Node) -> Unit>() private var currentPredecessors = mutableListOf() private val nextEdgeProperties = EnumMap(Properties::class.java) @@ -170,13 +172,9 @@ open class EvaluationOrderGraphPass : Pass() { currentPredecessors.clear() } - override fun accept(result: TranslationResult) { - scopeManager = result.scopeManager - for (tu in result.translationUnits) { - createEOG(tu) - removeUnreachableEOGEdges(tu) - // checkEOGInvariant(tu); To insert when trying to check if the invariant holds - } + override fun accept(tu: TranslationUnitDeclaration) { + createEOG(tu) + removeUnreachableEOGEdges(tu) } /** diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/FilenameMapper.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/FilenameMapper.kt index 25936f6bd9..d715c99467 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/FilenameMapper.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/FilenameMapper.kt @@ -25,20 +25,21 @@ */ package de.fraunhofer.aisec.cpg.passes -import de.fraunhofer.aisec.cpg.TranslationResult +import de.fraunhofer.aisec.cpg.ScopeManager +import de.fraunhofer.aisec.cpg.TranslationConfiguration import de.fraunhofer.aisec.cpg.graph.Node +import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration import de.fraunhofer.aisec.cpg.passes.order.ExecuteLast import de.fraunhofer.aisec.cpg.processing.IVisitor import de.fraunhofer.aisec.cpg.processing.strategy.Strategy @ExecuteLast -class FilenameMapper : Pass() { - override fun accept(translationResult: TranslationResult) { - for (tu in translationResult.translationUnits) { - val file = tu.name.toString() - tu.file = file - handle(tu, file) - } +class FilenameMapper(config: TranslationConfiguration, scopeManager: ScopeManager) : + TranslationUnitPass(config, scopeManager) { + override fun accept(tu: TranslationUnitDeclaration) { + val file = tu.name.toString() + tu.file = file + handle(tu, file) } private fun handle(node: Node, file: String) { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/FunctionPointerCallResolver.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/FunctionPointerCallResolver.kt index 8742198e69..4951f52ada 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/FunctionPointerCallResolver.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/FunctionPointerCallResolver.kt @@ -25,8 +25,10 @@ */ package de.fraunhofer.aisec.cpg.passes -import de.fraunhofer.aisec.cpg.TranslationResult +import de.fraunhofer.aisec.cpg.ScopeManager +import de.fraunhofer.aisec.cpg.TranslationConfiguration import de.fraunhofer.aisec.cpg.frontends.cpp.CXXLanguageFrontend +import de.fraunhofer.aisec.cpg.graph.Component import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration @@ -55,20 +57,20 @@ import java.util.function.Consumer @DependsOn(CallResolver::class) @DependsOn(DFGPass::class) @RequiredFrontend(CXXLanguageFrontend::class) -class FunctionPointerCallResolver : Pass() { +class FunctionPointerCallResolver(config: TranslationConfiguration, scopeManager: ScopeManager) : + ComponentPass(config, scopeManager) { private lateinit var walker: ScopedWalker private var inferDfgForUnresolvedCalls = false - override fun accept(t: TranslationResult) { - scopeManager = t.scopeManager - inferDfgForUnresolvedCalls = t.config.inferenceConfiguration.inferDfgForUnresolvedSymbols - walker = ScopedWalker(t.scopeManager) + override fun accept(component: Component) { + inferDfgForUnresolvedCalls = config.inferenceConfiguration.inferDfgForUnresolvedSymbols + walker = ScopedWalker(scopeManager) walker.registerHandler { _: RecordDeclaration?, _: Node?, currNode: Node? -> walker.collectDeclarations(currNode) } walker.registerHandler { node, _ -> resolve(node) } - for (tu in t.translationUnits) { + for (tu in component.translationUnits) { walker.iterate(tu) } } @@ -150,7 +152,7 @@ class FunctionPointerCallResolver : Pass() { call.invokes = invocationCandidates // We have to update the dfg edges because this call could now be resolved (which was not // the case before). - DFGPass().handleCallExpression(call, inferDfgForUnresolvedCalls) + DFGPass(config, scopeManager).handleCallExpression(call, inferDfgForUnresolvedCalls) } override fun cleanup() { 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 423b654bce..fb3c4360ae 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 @@ -25,7 +25,9 @@ */ package de.fraunhofer.aisec.cpg.passes -import de.fraunhofer.aisec.cpg.TranslationResult +import de.fraunhofer.aisec.cpg.ScopeManager +import de.fraunhofer.aisec.cpg.TranslationConfiguration +import de.fraunhofer.aisec.cpg.graph.Component import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.declarations.* import de.fraunhofer.aisec.cpg.graph.newFieldDeclaration @@ -38,7 +40,8 @@ import java.util.* import java.util.regex.Pattern @DependsOn(TypeHierarchyResolver::class) -open class ImportResolver : Pass() { +open class ImportResolver(config: TranslationConfiguration, scopeManager: ScopeManager) : + ComponentPass(config, scopeManager) { protected val records: MutableList = ArrayList() protected val importables: MutableMap = HashMap() @@ -47,8 +50,8 @@ open class ImportResolver : Pass() { importables.clear() } - override fun accept(result: TranslationResult) { - for (tu in result.translationUnits) { + override fun accept(component: Component) { + for (tu in component.translationUnits) { findImportables(tu) } for (recordDecl in records) { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/Pass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/Pass.kt index 7e0ec9ca50..bc54757691 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/Pass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/Pass.kt @@ -28,86 +28,72 @@ package de.fraunhofer.aisec.cpg.passes import de.fraunhofer.aisec.cpg.ScopeManager import de.fraunhofer.aisec.cpg.TranslationConfiguration import de.fraunhofer.aisec.cpg.TranslationResult +import de.fraunhofer.aisec.cpg.frontends.Language import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend +import de.fraunhofer.aisec.cpg.frontends.TranslationException +import de.fraunhofer.aisec.cpg.graph.Component +import de.fraunhofer.aisec.cpg.graph.LanguageProvider +import de.fraunhofer.aisec.cpg.graph.Node +import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration import de.fraunhofer.aisec.cpg.passes.order.* import java.util.function.Consumer +import kotlin.reflect.KClass +import kotlin.reflect.full.primaryConstructor import org.slf4j.Logger import org.slf4j.LoggerFactory /** - * Represents an abstract class that enhances the graph before it is persisted. - * - * Passes are expected to mutate the [TranslationResult]. + * A [TranslationResultPass] is a pass that operates on a [TranslationResult]. If used with + * [executePassSequential], one [Pass] object is instantiated for the whole [TranslationResult]. */ -abstract class Pass protected constructor() : Consumer { - var name: String - protected set +abstract class TranslationResultPass(config: TranslationConfiguration, scopeManager: ScopeManager) : + Pass(config, scopeManager) - /** - * Dependencies which, if present, have to be executed before this pass. Note: Dependencies - * registered here will not be added automatically to the list of active passes. Use - * [hardDependencies] to add them automatically. - */ - internal val softDependencies: MutableSet> +/** + * A [ComponentPass] is a pass that operates on a [Component]. If used with [executePassSequential], + * one [Pass] object is instantiated for each [Component] in a [TranslationResult]. + */ +abstract class ComponentPass(config: TranslationConfiguration, scopeManager: ScopeManager) : + Pass(config, scopeManager) - /** - * Dependencies which have to be executed before this pass. Note: Dependencies registered here - * will be added to the list of active passes automatically. Use [softDependencies] if this is - * not desired. - */ - internal val hardDependencies: MutableSet> - internal val executeBefore: MutableSet> +/** + * A [TranslationUnitPass] is a pass that operates on a [TranslationUnitDeclaration]. If used with + * [executePassSequential], one [Pass] object is instantiated for each [TranslationUnitDeclaration] + * in a [Component]. + */ +abstract class TranslationUnitPass(config: TranslationConfiguration, scopeManager: ScopeManager) : + Pass(config, scopeManager) - fun addSoftDependency(toAdd: Class) { - softDependencies.add(toAdd) - } +/** + * A pass target is an interface for a [Node] on which a [Pass] can operate, it should only be + * implemented by [TranslationResult], [Component] and [TranslationUnitDeclaration]. + */ +interface PassTarget - lateinit var scopeManager: ScopeManager - protected var config: TranslationConfiguration? = null +/** + * Represents an abstract class that enhances the graph before it is persisted. Passes can exist at + * three different levels: + * - the overall [TranslationResult] + * - a [Component], and + * - a [TranslationUnitDeclaration]. + * + * A level should be chosen as granular as possible, to allow for the (future) parallel execution of + * passes. Instead of directly subclassing this type, one of the types [TranslationResultPass], + * [ComponentPass] or [TranslationUnitPass] must be used. + */ +sealed class Pass( + val config: TranslationConfiguration, + val scopeManager: ScopeManager +) : Consumer { + var name: String + protected set init { name = this.javaClass.name - hardDependencies = HashSet() - softDependencies = HashSet() - executeBefore = HashSet() - - // collect all dependencies added by [DependsOn] annotations. - if (this.javaClass.getAnnotationsByType(DependsOn::class.java).isNotEmpty()) { - val dependencies = this.javaClass.getAnnotationsByType(DependsOn::class.java) - for (d in dependencies) { - if (d.softDependency) { - softDependencies.add(d.value.java) - } else { - hardDependencies.add(d.value.java) - } - } - } - if (this.javaClass.getAnnotationsByType(ExecuteBefore::class.java).isNotEmpty()) { - val dependencies = this.javaClass.getAnnotationsByType(ExecuteBefore::class.java) - for (d in dependencies) { - executeBefore.add(d.other.java) - } - } } abstract fun cleanup() - val isLastPass: Boolean - get() = - try { - this.javaClass.isAnnotationPresent(ExecuteLast::class.java) - } catch (e: Exception) { - false - } - - val isFirstPass: Boolean - get() = - try { - this.javaClass.isAnnotationPresent(ExecuteFirst::class.java) - } catch (e: Exception) { - false - } - /** * Check if the pass requires a specific language frontend and if that frontend has been * executed. @@ -125,6 +111,104 @@ abstract class Pass protected constructor() : Consumer { } companion object { + val log: Logger = LoggerFactory.getLogger(Pass::class.java) } } + +/** + * Creates a new [Pass] (based on [cls]) and executes it sequentially on the nodes of [result]. + * Depending on the type of pass, this will either execute the pass directly on the overall result, + * loop through each component or through each translation unit. + */ +fun executePassSequential( + cls: KClass>, + result: TranslationResult, + executedFrontends: Collection +) { + // This is a bit tricky but actually better than other reflection magic. We are creating a + // "prototype" instance of our pass class, so we can deduce certain type information more + // easily. + val prototype = + cls.primaryConstructor?.call(result.config, result.scopeManager) + ?: throw TranslationException("Could not create prototype pass") + + when (prototype) { + is TranslationResultPass -> { + executePass( + (prototype as TranslationResultPass)::class, + result, + result.config, + result.scopeManager, + executedFrontends + ) + } + is ComponentPass -> { + for (component in result.components) { + executePass( + (prototype as ComponentPass)::class, + component, + result.config, + result.scopeManager, + executedFrontends + ) + } + } + is TranslationUnitPass -> { + for (component in result.components) { + for (tu in component.translationUnits) { + executePass( + (prototype as TranslationUnitPass)::class, + tu, + result.config, + result.scopeManager, + executedFrontends + ) + } + } + } + } +} + +inline fun executePass( + cls: KClass>, + target: T, + config: TranslationConfiguration, + scopeManager: ScopeManager, + executedFrontends: Collection +): Pass? { + val language = + if (target is LanguageProvider) { + target.language + } else { + null + } + + val realClass = checkForReplacement(cls, language, config) + + val pass = realClass.primaryConstructor?.call(config, scopeManager) + if (pass?.runsWithCurrentFrontend(executedFrontends) == true) { + pass.accept(target) + pass.cleanup() + return pass + } + + return null +} + +/** + * Checks, whether the specified pass has a replacement configured in [config] for the given + * [language]. Currently, we only allow replacement on translation unit level, as this is the only + * level which has a single language set. + */ +fun checkForReplacement( + cls: KClass>, + language: Language<*>?, + config: TranslationConfiguration +): KClass> { + if (language == null) { + return cls + } + + return config.replacedPasses[Pair(cls, language::class)] as? KClass> ?: cls +} diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/StatisticsCollectionPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/StatisticsCollectionPass.kt index 62a4a040d1..452a5e1597 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/StatisticsCollectionPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/StatisticsCollectionPass.kt @@ -25,6 +25,8 @@ */ package de.fraunhofer.aisec.cpg.passes +import de.fraunhofer.aisec.cpg.ScopeManager +import de.fraunhofer.aisec.cpg.TranslationConfiguration import de.fraunhofer.aisec.cpg.TranslationResult import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.ProblemNode @@ -36,13 +38,14 @@ import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker.ScopedWalker * A [Pass] collecting statistics for the graph. Currently, it collects the number of nodes and the * number of problem nodes (i.e., nodes where the translation failed for some reason). */ -class StatisticsCollectionPass : Pass() { +class StatisticsCollectionPass(config: TranslationConfiguration, scopeManager: ScopeManager) : + TranslationResultPass(config, scopeManager) { - /** Iterates the nodes of the [translationResult] to collect statistics. */ - override fun accept(translationResult: TranslationResult) { + /** Iterates the nodes of the [result] to collect statistics. */ + override fun accept(result: TranslationResult) { var problemNodes = 0 var nodes = 0 - val walker = ScopedWalker(translationResult.scopeManager) + val walker = ScopedWalker(result.scopeManager) walker.registerHandler { _: RecordDeclaration?, _: Node?, currNode: Node? -> nodes++ if (currNode is ProblemNode) { @@ -50,12 +53,11 @@ class StatisticsCollectionPass : Pass() { } } - for (tu in translationResult.translationUnits) { + for (tu in result.translationUnits) { walker.iterate(tu) } - val nodeMeasurement = - MeasurementHolder(this.javaClass, "Measuring Nodes", false, translationResult) + val nodeMeasurement = MeasurementHolder(this.javaClass, "Measuring Nodes", false, result) nodeMeasurement.addMeasurement("Total graph nodes", nodes.toString()) nodeMeasurement.addMeasurement("Problem nodes", problemNodes.toString()) } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/SymbolResolverPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/SymbolResolverPass.kt index 273e39d253..5851eed767 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/SymbolResolverPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/SymbolResolverPass.kt @@ -25,6 +25,8 @@ */ package de.fraunhofer.aisec.cpg.passes +import de.fraunhofer.aisec.cpg.ScopeManager +import de.fraunhofer.aisec.cpg.TranslationConfiguration import de.fraunhofer.aisec.cpg.frontends.HasSuperClasses import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.declarations.* @@ -32,7 +34,8 @@ import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExp import de.fraunhofer.aisec.cpg.graph.types.* import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker -abstract class SymbolResolverPass : Pass() { +abstract class SymbolResolverPass(config: TranslationConfiguration, scopeManager: ScopeManager) : + ComponentPass(config, scopeManager) { protected lateinit var walker: SubgraphWalker.ScopedWalker lateinit var currentTU: TranslationUnitDeclaration diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TypeHierarchyResolver.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TypeHierarchyResolver.kt index fdf5957138..61684954ee 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TypeHierarchyResolver.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TypeHierarchyResolver.kt @@ -25,7 +25,9 @@ */ package de.fraunhofer.aisec.cpg.passes -import de.fraunhofer.aisec.cpg.TranslationResult +import de.fraunhofer.aisec.cpg.ScopeManager +import de.fraunhofer.aisec.cpg.TranslationConfiguration +import de.fraunhofer.aisec.cpg.graph.Component import de.fraunhofer.aisec.cpg.graph.Name import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.declarations.EnumDeclaration @@ -54,12 +56,13 @@ import java.util.* * at places where it is crucial to have parsed all [RecordDeclaration]s. Otherwise, type * information in the graph might not be fully correct */ -open class TypeHierarchyResolver : Pass() { +open class TypeHierarchyResolver(config: TranslationConfiguration, scopeManager: ScopeManager) : + ComponentPass(config, scopeManager) { protected val recordMap = mutableMapOf() protected val enums = mutableListOf() - override fun accept(translationResult: TranslationResult) { - for (tu in translationResult.translationUnits) { + override fun accept(component: Component) { + for (tu in component.translationUnits) { findRecordsAndEnums(tu) } for (recordDecl in recordMap.values) { @@ -75,7 +78,7 @@ open class TypeHierarchyResolver : Pass() { enumDecl.superTypeDeclarations = allSupertypes } - translationResult.translationUnits.forEach { SubgraphWalker.refreshType(it) } + component.translationUnits.forEach { SubgraphWalker.refreshType(it) } } protected fun findRecordsAndEnums(node: Node) { 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 2d57fadedb..15bc3c637c 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 @@ -25,7 +25,9 @@ */ package de.fraunhofer.aisec.cpg.passes -import de.fraunhofer.aisec.cpg.TranslationResult +import de.fraunhofer.aisec.cpg.ScopeManager +import de.fraunhofer.aisec.cpg.TranslationConfiguration +import de.fraunhofer.aisec.cpg.graph.Component import de.fraunhofer.aisec.cpg.graph.HasType import de.fraunhofer.aisec.cpg.graph.HasType.SecondaryTypeEdge import de.fraunhofer.aisec.cpg.graph.Node @@ -36,7 +38,8 @@ import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker.IterativeGraphWalker import de.fraunhofer.aisec.cpg.passes.order.DependsOn @DependsOn(CallResolver::class) -open class TypeResolver : Pass() { +open class TypeResolver(config: TranslationConfiguration, scopeManager: ScopeManager) : + ComponentPass(config, scopeManager) { protected val firstOrderTypes = mutableSetOf() protected val typeState = mutableMapOf>() @@ -147,16 +150,16 @@ open class TypeResolver : Pass() { * Pass on the TypeSystem: Sets RecordDeclaration Relationship from ObjectType to * RecordDeclaration * - * @param translationResult + * @param component */ - override fun accept(translationResult: TranslationResult) { + override fun accept(component: Component) { removeDuplicateTypes() val walker = IterativeGraphWalker() walker.registerOnNodeVisit(::ensureUniqueType) walker.registerOnNodeVisit(::handle) walker.registerOnNodeVisit(::ensureUniqueSecondaryTypeEdge) - for (tu in translationResult.translationUnits) { + for (tu in component.translationUnits) { walker.iterate(tu) } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/VariableUsageResolver.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/VariableUsageResolver.kt index a304060c91..3a28492440 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/VariableUsageResolver.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/VariableUsageResolver.kt @@ -25,7 +25,8 @@ */ package de.fraunhofer.aisec.cpg.passes -import de.fraunhofer.aisec.cpg.TranslationResult +import de.fraunhofer.aisec.cpg.ScopeManager +import de.fraunhofer.aisec.cpg.TranslationConfiguration import de.fraunhofer.aisec.cpg.frontends.HasStructs import de.fraunhofer.aisec.cpg.frontends.HasSuperClasses import de.fraunhofer.aisec.cpg.graph.* @@ -58,14 +59,12 @@ import org.slf4j.LoggerFactory * rather makes their "refersTo" point to the appropriate [ValueDeclaration]. */ @DependsOn(TypeHierarchyResolver::class) -open class VariableUsageResolver : SymbolResolverPass() { - - override fun accept(result: TranslationResult) { - scopeManager = result.scopeManager - config = result.config +open class VariableUsageResolver(config: TranslationConfiguration, scopeManager: ScopeManager) : + SymbolResolverPass(config, scopeManager) { + override fun accept(component: Component) { walker = ScopedWalker(scopeManager) - for (tu in result.translationUnits) { + for (tu in component.translationUnits) { currentTU = tu walker.clearCallbacks() walker.registerHandler { _, _, currNode -> walker.collectDeclarations(currNode) } @@ -76,14 +75,14 @@ open class VariableUsageResolver : SymbolResolverPass() { collectSupertypes() - for (tu in result.translationUnits) { + for (tu in component.translationUnits) { walker.clearCallbacks() walker.registerHandler { curClass, parent, node -> resolveFieldUsages(curClass, parent, node) } walker.iterate(tu) } - for (tu in result.translationUnits) { + for (tu in component.translationUnits) { walker.clearCallbacks() walker.registerHandler(::resolveLocalVarUsage) walker.iterate(tu) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/order/DependsOn.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/order/DependsOn.kt index c91cc6a6bd..747db30267 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/order/DependsOn.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/order/DependsOn.kt @@ -39,4 +39,4 @@ import kotlin.reflect.KClass @Retention(AnnotationRetention.RUNTIME) @Target(AnnotationTarget.CLASS) @Repeatable -annotation class DependsOn(val value: KClass, val softDependency: Boolean = false) +annotation class DependsOn(val value: KClass>, val softDependency: Boolean = false) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/order/ExecuteBefore.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/order/ExecuteBefore.kt index ae3aafe977..3271d714e4 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/order/ExecuteBefore.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/order/ExecuteBefore.kt @@ -35,4 +35,4 @@ import kotlin.reflect.KClass @Retention(AnnotationRetention.RUNTIME) @Target(AnnotationTarget.CLASS) @Repeatable -annotation class ExecuteBefore(val other: KClass) +annotation class ExecuteBefore(val other: KClass>) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/order/PassWithDependencies.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/order/PassWithDependencies.kt index 5dcd1d7130..b0ed9b6119 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/order/PassWithDependencies.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/order/PassWithDependencies.kt @@ -25,7 +25,38 @@ */ package de.fraunhofer.aisec.cpg.passes.order +import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.passes.Pass +import kotlin.reflect.KClass +import kotlin.reflect.full.hasAnnotation +import org.apache.commons.lang3.builder.ToStringBuilder /** A simple helper class to match a pass with dependencies. */ -data class PassWithDependencies(val pass: Pass, val dependencies: MutableSet>) +data class PassWithDependencies( + val pass: KClass>, + val softDependencies: MutableSet>>, + val hardDependencies: MutableSet>> +) { + val dependencies: Set>> + get() { + return softDependencies + hardDependencies + } + + val isFirstPass: Boolean + get() { + return pass.hasAnnotation() + } + + val isLastPass: Boolean + get() { + return pass.hasAnnotation() + } + + override fun toString(): String { + return ToStringBuilder(this, Node.TO_STRING_STYLE) + .append("pass", pass.simpleName) + .append("softDependencies", softDependencies.map { it.simpleName }) + .append("hardDependencies", hardDependencies.map { it.simpleName }) + .toString() + } +} diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/order/PassWithDepsContainer.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/order/PassWithDepsContainer.kt index d9bbeb9e1b..0708a1d74b 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/order/PassWithDepsContainer.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/order/PassWithDepsContainer.kt @@ -28,8 +28,9 @@ package de.fraunhofer.aisec.cpg.passes.order import de.fraunhofer.aisec.cpg.ConfigurationException import de.fraunhofer.aisec.cpg.passes.Pass import de.fraunhofer.aisec.cpg.passes.Pass.Companion.log -import java.lang.reflect.InvocationTargetException import java.util.* +import kotlin.reflect.KClass +import kotlin.reflect.full.findAnnotations /** * A simple helper class for keeping track of passes and their (currently not satisfied) @@ -61,9 +62,10 @@ class PassWithDepsContainer { * Iterate through all elements and remove the provided dependency [cls] from all passes in the * working list. */ - private fun removeDependencyByClass(cls: Class) { - for ((_, value) in workingList) { - value.remove(cls) + private fun removeDependencyByClass(cls: KClass>) { + for (pass in workingList) { + pass.softDependencies.remove(cls) + pass.hardDependencies.remove(cls) } } @@ -72,17 +74,17 @@ class PassWithDepsContainer { } fun getFirstPasses(): List { - return workingList.filter { it.pass.isFirstPass } + return workingList.filter { it.isFirstPass } } fun getLastPasses(): List { - return workingList.filter { it.pass.isLastPass } + return workingList.filter { it.isLastPass } } - private fun dependencyPresent(dep: Class): Boolean { + private fun dependencyPresent(dep: KClass>): Boolean { var result = false for (currentElement in workingList) { - if (dep == currentElement.pass.javaClass) { + if (dep == currentElement.pass) { result = true break } @@ -91,24 +93,20 @@ class PassWithDepsContainer { return result } - private fun createNewPassWithDependency(cls: Class): PassWithDependencies { - val newPass = - try { - cls.getConstructor().newInstance() - } catch (e: InstantiationException) { - throw ConfigurationException(e) - } catch (e: InvocationTargetException) { - throw ConfigurationException(e) - } catch (e: IllegalAccessException) { - throw ConfigurationException(e) - } catch (e: NoSuchMethodException) { - throw ConfigurationException(e) + private fun createNewPassWithDependency(cls: KClass>): PassWithDependencies { + val softDependencies = mutableSetOf>>() + val hardDependencies = mutableSetOf>>() + + val dependencies = cls.findAnnotations() + for (d in dependencies) { + if (d.softDependency) { + softDependencies += d.value + } else { + hardDependencies += d.value } + } - val deps: MutableSet> = HashSet() - deps.addAll(newPass.hardDependencies) - deps.addAll(newPass.softDependencies) - return PassWithDependencies(newPass, deps) + return PassWithDependencies(cls, softDependencies, hardDependencies) } /** @@ -119,7 +117,7 @@ class PassWithDepsContainer { val it = workingList.listIterator() while (it.hasNext()) { val current = it.next() - for (dependency in current.pass.hardDependencies) { + for (dependency in current.hardDependencies) { if (!dependencyPresent(dependency)) { log.info( "Registering a required hard dependency which was not registered explicitly: {}", @@ -131,11 +129,11 @@ class PassWithDepsContainer { } // add required dependencies to the working list - val missingPasses: MutableList> = ArrayList() + val missingPasses: MutableList>> = ArrayList() // initially populate the missing dependencies list given the current passes for (currentElement in workingList) { - for (dependency in currentElement.pass.hardDependencies) { + for (dependency in currentElement.hardDependencies) { if (!dependencyPresent(dependency)) { missingPasses.add(dependency) } @@ -149,11 +147,11 @@ class PassWithDepsContainer { * * @return The first pass that has no active dependencies on success. null otherwise. */ - fun getAndRemoveFirstPassWithoutDependencies(): Pass? { - var result: Pass? = null + fun getAndRemoveFirstPassWithoutDependencies(): KClass>? { + var result: KClass>? = null for (currentElement in workingList) { - if (workingList.size > 1 && currentElement.pass.isLastPass) { + if (workingList.size > 1 && currentElement.isLastPass) { // last pass can only be added at the end continue } @@ -162,7 +160,7 @@ class PassWithDepsContainer { result = currentElement.pass // remove the pass from the other pass's dependencies - removeDependencyByClass(result.javaClass) + removeDependencyByClass(result) workingList.remove(currentElement) break } @@ -177,7 +175,7 @@ class PassWithDepsContainer { * * @return The first pass if present. Otherwise, null. */ - fun getAndRemoveFirstPass(): Pass? { + fun getAndRemoveFirstPass(): KClass>? { val firstPasses = getFirstPasses() if (firstPasses.size > 1) { throw ConfigurationException( @@ -186,10 +184,10 @@ class PassWithDepsContainer { } return if (firstPasses.isNotEmpty()) { val firstPass = firstPasses.first() - if (firstPass.pass.hardDependencies.isNotEmpty()) { + if (firstPass.hardDependencies.isNotEmpty()) { throw ConfigurationException("The first pass has a hard dependency.") } else { - removeDependencyByClass(firstPass.pass.javaClass) + removeDependencyByClass(firstPass.pass) workingList.remove(firstPass) firstPass.pass } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/order/RegisterExtraPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/order/RegisterExtraPass.kt index e5ef60d926..7d2824504f 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/order/RegisterExtraPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/order/RegisterExtraPass.kt @@ -32,4 +32,4 @@ import kotlin.reflect.KClass @Retention(AnnotationRetention.RUNTIME) @Target(AnnotationTarget.CLASS) @Repeatable -annotation class RegisterExtraPass(val value: KClass) +annotation class RegisterExtraPass(val value: KClass>) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/order/ReplacePass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/order/ReplacePass.kt new file mode 100644 index 0000000000..8becb30951 --- /dev/null +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/order/ReplacePass.kt @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2023, 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.order + +import de.fraunhofer.aisec.cpg.frontends.Language +import de.fraunhofer.aisec.cpg.passes.EvaluationOrderGraphPass +import de.fraunhofer.aisec.cpg.passes.Pass +import kotlin.reflect.KClass + +/** + * This annotation can be used to replace a certain [Pass] (identified by [old]) for a specific + * [Language] (identified by [lang]) with another [Pass] (identified by [with]). + * + * The primary use-case for this annotation is to allow language frontends to override specific + * passes, such as the [EvaluationOrderGraphPass] in order to optimize language specific graphs. + */ +@Retention(AnnotationRetention.RUNTIME) +@Target(AnnotationTarget.CLASS) +@Repeatable +annotation class ReplacePass( + val old: KClass>, + val lang: KClass>, + val with: KClass> +) diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CXXLanguageFrontendTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CXXLanguageFrontendTest.kt index e7c46e5c51..24ae3164c3 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CXXLanguageFrontendTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CXXLanguageFrontendTest.kt @@ -1420,16 +1420,16 @@ internal class CXXLanguageFrontendTest : BaseTest() { val file = File("src/test/resources/c/func_ptr_call.c") val tu = analyzeAndGetFirstTU(listOf(file), file.parentFile.toPath(), false) { - it.registerPass(TypeHierarchyResolver()) - it.registerPass(ImportResolver()) - it.registerPass(VariableUsageResolver()) - it.registerPass(CallResolver()) // creates CG - it.registerPass(DFGPass()) - it.registerPass(EvaluationOrderGraphPass()) // creates EOG - it.registerPass(TypeResolver()) - it.registerPass(ControlFlowSensitiveDFGPass()) - it.registerPass(FunctionPointerCallResolver()) - it.registerPass(FilenameMapper()) + it.registerPass() + it.registerPass() + it.registerPass() + it.registerPass() // creates CG + it.registerPass() + it.registerPass() // creates EOG + it.registerPass() + it.registerPass() + it.registerPass() + it.registerPass() } val target = tu.functions["target"] @@ -1468,16 +1468,16 @@ internal class CXXLanguageFrontendTest : BaseTest() { val file = File("src/test/resources/c/func_ptr_call.c") val tu = analyzeAndGetFirstTU(listOf(file), file.parentFile.toPath(), false) { - it.registerPass(TypeHierarchyResolver()) - it.registerPass(ImportResolver()) - it.registerPass(VariableUsageResolver()) - it.registerPass(CallResolver()) // creates CG - it.registerPass(DFGPass()) - it.registerPass(EvaluationOrderGraphPass()) // creates EOG - it.registerPass(TypeResolver()) - it.registerPass(FunctionPointerCallResolver()) - it.registerPass(ControlFlowSensitiveDFGPass()) - it.registerPass(FilenameMapper()) + it.registerPass() + it.registerPass() + it.registerPass() + it.registerPass() // creates CG + it.registerPass() + it.registerPass() // creates EOG + it.registerPass() + it.registerPass() + it.registerPass() + it.registerPass() } val target = tu.functions["target"] diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/FluentTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/FluentTest.kt index f73cb9fcac..0c08fddd10 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/FluentTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/FluentTest.kt @@ -170,7 +170,8 @@ class FluentTest { assertNotNull(lit2.scope) assertEquals(2, lit2.value) - VariableUsageResolver().accept(result) + VariableUsageResolver(TranslationConfiguration.builder().build(), scopeManager) + .accept(result.components.first()) // Now the reference should be resolved assertRefersTo(ref, variable) diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/ShortcutsTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/ShortcutsTest.kt index 4c14aff028..1f5f0c1d02 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/ShortcutsTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/ShortcutsTest.kt @@ -227,7 +227,7 @@ class ShortcutsTest { GraphExamples.getShortcutClass( TranslationConfiguration.builder() .defaultPasses() - .registerPass(EdgeCachePass()) + .registerPass() .registerLanguage(TestLanguage(".")) .build() ) diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/AssignExpressionTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/AssignExpressionTest.kt index 7db194f165..8fe39f93b3 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/AssignExpressionTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/AssignExpressionTest.kt @@ -109,7 +109,7 @@ class AssignExpressionTest { assertLocalName("error", refErr.type) // Invoke the DFG pass - DFGPass().accept(result) + DFGPass(result.config, result.scopeManager).accept(result.components.first()) assertTrue(refA.prevDFG.contains(call)) assertTrue(refErr.prevDFG.contains(call)) diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/types/TypePropagationTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/types/TypePropagationTest.kt index d3bc26420c..cf08b107ea 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/types/TypePropagationTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/types/TypePropagationTest.kt @@ -92,7 +92,7 @@ class TypePropagationTest { } } } - VariableUsageResolver().accept(result) + VariableUsageResolver(result.config, result.scopeManager).accept(result.components.first()) val binaryOp = (result.functions["main"]?.body as? CompoundStatement)?.statements?.get(2) diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ReplaceTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ReplaceTest.kt new file mode 100644 index 0000000000..ddf9e1fee8 --- /dev/null +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ReplaceTest.kt @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2023, 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.ScopeManager +import de.fraunhofer.aisec.cpg.TranslationConfiguration +import de.fraunhofer.aisec.cpg.frontends.StructTestLanguage +import de.fraunhofer.aisec.cpg.frontends.TestLanguage +import de.fraunhofer.aisec.cpg.frontends.TestLanguageFrontend +import de.fraunhofer.aisec.cpg.passes.order.ReplacePass +import kotlin.reflect.KClass +import kotlin.test.* + +class ReplaceTest { + + @ReplacePass(EvaluationOrderGraphPass::class, ReplaceTestLanguage::class, ReplacedPass::class) + class ReplaceTestLanguageFrontend : TestLanguageFrontend() + + class ReplaceTestLanguage : TestLanguage() { + override val frontend: KClass + get() = ReplaceTestLanguageFrontend::class + + override fun newFrontend( + config: TranslationConfiguration, + scopeManager: ScopeManager + ): TestLanguageFrontend { + return ReplaceTestLanguageFrontend() + } + } + + class ReplacedPass(config: TranslationConfiguration, scopeManager: ScopeManager) : + EvaluationOrderGraphPass(config, scopeManager) + + @Test + fun testReplaceAnnotation() { + val config = + TranslationConfiguration.builder().registerLanguage().build() + + assertContains(config.replacedPasses.values, ReplacedPass::class) + assertContains( + config.replacedPasses.keys, + Pair(EvaluationOrderGraphPass::class, ReplaceTestLanguage::class) + ) + + val cls = + checkForReplacement(EvaluationOrderGraphPass::class, ReplaceTestLanguage(), config) + assertEquals(ReplacedPass::class, cls) + } + + @Test + fun testReplaceFunction() { + val config = + TranslationConfiguration.builder() + .replacePass() + .replacePass() + .build() + + assertContains(config.replacedPasses.values, ReplacedPass::class) + assertContains( + config.replacedPasses.keys, + Pair(EvaluationOrderGraphPass::class, StructTestLanguage::class) + ) + + var cls = checkForReplacement(EvaluationOrderGraphPass::class, TestLanguage(), config) + assertEquals(EvaluationOrderGraphPass::class, cls) + + cls = checkForReplacement(EvaluationOrderGraphPass::class, StructTestLanguage(), config) + assertEquals(ReplacedPass::class, cls) + + cls = checkForReplacement(EvaluationOrderGraphPass::class, ReplaceTestLanguage(), config) + assertEquals(ReplacedPass::class, cls) + } +} diff --git a/cpg-core/src/testFixtures/kotlin/de/fraunhofer/aisec/cpg/frontends/TestLanguage.kt b/cpg-core/src/testFixtures/kotlin/de/fraunhofer/aisec/cpg/frontends/TestLanguage.kt index 09ad8082ea..ce9ba9c91e 100644 --- a/cpg-core/src/testFixtures/kotlin/de/fraunhofer/aisec/cpg/frontends/TestLanguage.kt +++ b/cpg-core/src/testFixtures/kotlin/de/fraunhofer/aisec/cpg/frontends/TestLanguage.kt @@ -77,7 +77,7 @@ open class TestLanguage(namespaceDelimiter: String = "::") : Language = TestLanguage(namespaceDelimiter) diff --git a/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/GoExtraPass.kt b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/GoExtraPass.kt index 62ae002773..4709f809cd 100644 --- a/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/GoExtraPass.kt +++ b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/GoExtraPass.kt @@ -25,7 +25,8 @@ */ package de.fraunhofer.aisec.cpg.passes -import de.fraunhofer.aisec.cpg.TranslationResult +import de.fraunhofer.aisec.cpg.ScopeManager +import de.fraunhofer.aisec.cpg.TranslationConfiguration import de.fraunhofer.aisec.cpg.frontends.Language import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend import de.fraunhofer.aisec.cpg.frontends.golang.GoLanguage @@ -107,14 +108,13 @@ import de.fraunhofer.aisec.cpg.passes.order.ExecuteBefore @ExecuteBefore(VariableUsageResolver::class) @ExecuteBefore(CallResolver::class) @ExecuteBefore(DFGPass::class) -class GoExtraPass : Pass(), ScopeProvider { +class GoExtraPass(config: TranslationConfiguration, scopeManager: ScopeManager) : + ComponentPass(config, scopeManager), ScopeProvider { override val scope: Scope? get() = scopeManager.currentScope - override fun accept(t: TranslationResult) { - scopeManager = t.scopeManager - + override fun accept(component: Component) { val walker = SubgraphWalker.ScopedWalker(scopeManager) walker.registerHandler { _, parent, node -> when (node) { @@ -125,7 +125,7 @@ class GoExtraPass : Pass(), ScopeProvider { } } - for (tu in t.translationUnits) { + for (tu in component.translationUnits) { walker.iterate(tu) } } diff --git a/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/JavaExternalTypeHierarchyResolver.kt b/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/JavaExternalTypeHierarchyResolver.kt index 36c54d0d77..3d85b4b1d8 100644 --- a/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/JavaExternalTypeHierarchyResolver.kt +++ b/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/JavaExternalTypeHierarchyResolver.kt @@ -29,8 +29,10 @@ import com.github.javaparser.resolution.UnsolvedSymbolException import com.github.javaparser.symbolsolver.resolution.typesolvers.CombinedTypeSolver import com.github.javaparser.symbolsolver.resolution.typesolvers.JavaParserTypeSolver import com.github.javaparser.symbolsolver.resolution.typesolvers.ReflectionTypeSolver -import de.fraunhofer.aisec.cpg.TranslationResult +import de.fraunhofer.aisec.cpg.ScopeManager +import de.fraunhofer.aisec.cpg.TranslationConfiguration import de.fraunhofer.aisec.cpg.frontends.java.JavaLanguageFrontend +import de.fraunhofer.aisec.cpg.graph.Component import de.fraunhofer.aisec.cpg.graph.TypeManager import de.fraunhofer.aisec.cpg.graph.types.Type import de.fraunhofer.aisec.cpg.graph.types.TypeParser @@ -43,23 +45,23 @@ import org.slf4j.LoggerFactory @DependsOn(TypeHierarchyResolver::class) @ExecuteBefore(ImportResolver::class) @RequiredFrontend(JavaLanguageFrontend::class) -class JavaExternalTypeHierarchyResolver : Pass() { - override fun accept(translationResult: TranslationResult) { +class JavaExternalTypeHierarchyResolver( + config: TranslationConfiguration, + scopeManager: ScopeManager +) : ComponentPass(config, scopeManager) { + override fun accept(component: Component) { val resolver = CombinedTypeSolver() resolver.add(ReflectionTypeSolver()) - var root = translationResult.config.topLevel - if (root == null && translationResult.config.softwareComponents.size == 1) { + var root = config.topLevel + if (root == null && config.softwareComponents.size == 1) { root = - translationResult.config.softwareComponents[ - translationResult.config.softwareComponents.keys.first()] - ?.let { CommonPath.commonPath(it) } + config.softwareComponents[config.softwareComponents.keys.first()]?.let { + CommonPath.commonPath(it) + } } if (root == null) { - log.warn( - "Could not determine source root for {}", - translationResult.config.softwareComponents - ) + log.warn("Could not determine source root for {}", config.softwareComponents) } else { log.info("Source file root used for type solver: {}", root) resolver.add(JavaParserTypeSolver(root)) diff --git a/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/types/TypeTests.kt b/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/types/TypeTests.kt index df42ee4248..97d6e6aff2 100644 --- a/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/types/TypeTests.kt +++ b/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/types/TypeTests.kt @@ -127,16 +127,12 @@ internal class TypeTests : BaseTest() { val result = analyze("java", topLevel, true) { it.registerLanguage(JavaLanguage()) } // Check Parameterized - val recordDeclarations = result.records - val recordDeclarationBox = findByUniqueName(recordDeclarations, "Box") - val typeT = TypeManager.getInstance().getTypeParameter(recordDeclarationBox, "T") - assertNotNull(typeT) - assertEquals(typeT, TypeManager.getInstance().getTypeParameter(recordDeclarationBox, "T")) - // Type of field t val fieldDeclarations = result.fields val fieldDeclarationT = findByUniqueName(fieldDeclarations, "t") - assertEquals(typeT, fieldDeclarationT.type) + val typeT = fieldDeclarationT.type + assertIs(typeT) + assertLocalName("T", typeT) assertTrue(fieldDeclarationT.possibleSubTypes.contains(typeT)) // Parameter of set Method diff --git a/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CompressLLVMPass.kt b/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CompressLLVMPass.kt index 8a7c9e404c..5984b08c16 100644 --- a/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CompressLLVMPass.kt +++ b/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CompressLLVMPass.kt @@ -25,8 +25,10 @@ */ package de.fraunhofer.aisec.cpg.passes -import de.fraunhofer.aisec.cpg.TranslationResult +import de.fraunhofer.aisec.cpg.ScopeManager +import de.fraunhofer.aisec.cpg.TranslationConfiguration import de.fraunhofer.aisec.cpg.frontends.llvm.LLVMIRLanguageFrontend +import de.fraunhofer.aisec.cpg.graph.Component import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.newDeclaredReferenceExpression import de.fraunhofer.aisec.cpg.graph.newVariableDeclaration @@ -41,9 +43,10 @@ import java.util.* @ExecuteFirst @RequiredFrontend(LLVMIRLanguageFrontend::class) -class CompressLLVMPass : Pass() { - override fun accept(t: TranslationResult) { - val flatAST = SubgraphWalker.flattenAST(t) +class CompressLLVMPass(config: TranslationConfiguration, scopeManager: ScopeManager) : + ComponentPass(config, scopeManager) { + override fun accept(component: Component) { + val flatAST = SubgraphWalker.flattenAST(component) // Get all goto statements val allGotos = flatAST.filterIsInstance() // Get all LabelStatements which are only referenced from a single GotoStatement @@ -84,8 +87,7 @@ class CompressLLVMPass : Pass() { (node.thenStatement as GotoStatement).targetLabel?.subStatement } // Replace the else-statement with the basic block it jumps to iff we found that - // its - // goto statement is the only one jumping to the target + // its goto statement is the only one jumping to the target if ( node.elseStatement in gotosToReplace && node !in diff --git a/cpg-neo4j/src/main/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/Application.kt b/cpg-neo4j/src/main/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/Application.kt index c4a6c7d08b..a10ff48df6 100644 --- a/cpg-neo4j/src/main/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/Application.kt +++ b/cpg-neo4j/src/main/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/Application.kt @@ -37,7 +37,7 @@ import java.lang.Class import java.net.ConnectException import java.nio.file.Paths import java.util.concurrent.Callable -import kotlin.reflect.full.createInstance +import kotlin.reflect.KClass import kotlin.system.exitProcess import org.neo4j.driver.exceptions.AuthenticationException import org.neo4j.ogm.config.Configuration @@ -382,15 +382,13 @@ class Application : Callable { for (pass in pieces) { if (pass.contains(".")) { translationConfiguration.registerPass( - Class.forName(pass).kotlin.createInstance() as Pass + Class.forName(pass).kotlin as KClass> ) } else { if (pass !in passClassMap) { throw ConfigurationException("Asked to produce unknown pass") } - passClassMap[pass]?.let { - translationConfiguration.registerPass(it.createInstance()) - } + passClassMap[pass]?.let { translationConfiguration.registerPass(it) } } } } From dac21e40c10416384aaceff67fa76dffc63d9c77 Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Wed, 31 May 2023 15:50:54 +0200 Subject: [PATCH 053/143] Make `TypeManager` not a singleton anymore (#1183) Moving TypeManager towards non-singleton --- .../aisec/cpg/passes/UnreachableEOGPass.kt | 8 +- .../aisec/cpg/analysis/ValueEvaluatorTest.kt | 11 +- .../aisec/cpg/graph/TypeManager.java | 57 +- .../aisec/cpg/graph/types/TypeParser.java | 74 ++- .../de/fraunhofer/aisec/cpg/ScopeManager.kt | 11 +- .../aisec/cpg/TranslationContext.kt | 51 ++ .../aisec/cpg/TranslationManager.kt | 85 +-- .../fraunhofer/aisec/cpg/TranslationResult.kt | 38 +- .../fraunhofer/aisec/cpg/frontends/Handler.kt | 7 +- .../aisec/cpg/frontends/Language.kt | 17 +- .../aisec/cpg/frontends/LanguageFrontend.kt | 23 +- .../aisec/cpg/frontends/LanguageTraits.kt | 10 +- .../aisec/cpg/frontends/cpp/CLanguage.kt | 29 +- .../aisec/cpg/frontends/cpp/CPPLanguage.kt | 44 +- .../cpg/frontends/cpp/CXXLanguageFrontend.kt | 14 +- .../cpg/frontends/cpp/DeclarationHandler.kt | 27 +- .../cpg/frontends/cpp/ExpressionHandler.kt | 2 +- .../aisec/cpg/graph/ExpressionBuilder.kt | 2 + .../de/fraunhofer/aisec/cpg/graph/Node.kt | 18 +- .../fraunhofer/aisec/cpg/graph/NodeBuilder.kt | 27 +- .../aisec/cpg/graph/builder/Fluent.kt | 18 +- .../declarations/ConstructorDeclaration.kt | 4 +- .../graph/declarations/FieldDeclaration.kt | 14 +- .../graph/declarations/FunctionDeclaration.kt | 7 +- .../graph/declarations/RecordDeclaration.kt | 4 +- .../graph/declarations/ValueDeclaration.kt | 49 +- .../graph/declarations/VariableDeclaration.kt | 9 +- .../expressions/ArrayCreationExpression.kt | 6 +- .../ArraySubscriptionExpression.kt | 4 +- .../expressions/AssignExpression.kt | 4 +- .../statements/expressions/BinaryOperator.kt | 4 +- .../statements/expressions/CallExpression.kt | 6 +- .../statements/expressions/CastExpression.kt | 6 +- .../expressions/ConditionalExpression.kt | 6 +- .../expressions/ConstructExpression.kt | 8 +- .../DeclaredReferenceExpression.kt | 6 +- .../statements/expressions/Expression.kt | 41 +- .../statements/expressions/ExpressionList.kt | 6 +- .../expressions/InitializerListExpression.kt | 21 +- .../expressions/LambdaExpression.kt | 7 +- .../statements/expressions/UnaryOperator.kt | 6 +- .../aisec/cpg/graph/types/FunctionType.kt | 4 +- .../aisec/cpg/graph/types/HasType.kt | 34 +- .../aisec/cpg/passes/CXXCallResolverHelper.kt | 67 ++- .../aisec/cpg/passes/CallResolver.kt | 34 +- .../cpg/passes/ControlFlowSensitiveDFGPass.kt | 9 +- .../de/fraunhofer/aisec/cpg/passes/DFGPass.kt | 6 +- .../aisec/cpg/passes/EdgeCachePass.kt | 6 +- .../cpg/passes/EvaluationOrderGraphPass.kt | 11 +- .../aisec/cpg/passes/FilenameMapper.kt | 6 +- .../cpg/passes/FunctionPointerCallResolver.kt | 8 +- .../aisec/cpg/passes/ImportResolver.kt | 11 +- .../de/fraunhofer/aisec/cpg/passes/Pass.kt | 55 +- .../cpg/passes/StatisticsCollectionPass.kt | 8 +- .../aisec/cpg/passes/SymbolResolverPass.kt | 8 +- .../aisec/cpg/passes/TypeHierarchyResolver.kt | 6 +- .../aisec/cpg/passes/TypeResolver.kt | 9 +- .../aisec/cpg/passes/VariableUsageResolver.kt | 27 +- .../aisec/cpg/passes/inference/Inference.kt | 54 +- .../de/fraunhofer/aisec/cpg/GraphExamples.kt | 129 +++-- .../frontends/cpp/CXXLanguageFrontendTest.kt | 90 ++- .../aisec/cpg/frontends/cpp/CXXLiteralTest.kt | 39 +- .../cpp/CXXSymbolConfigurationTest.kt | 30 +- .../fraunhofer/aisec/cpg/graph/FluentTest.kt | 8 +- .../expressions/AssignExpressionTest.kt | 8 +- .../cpg/graph/types/TypePropagationTest.kt | 11 +- .../aisec/cpg/graph/types/TypeTests.kt | 529 ++++++++++-------- .../aisec/cpg/graph/types/TypedefTest.kt | 10 +- .../aisec/cpg/passes/CallResolverTest.kt | 9 +- .../aisec/cpg/passes/ReplaceTest.kt | 10 +- .../aisec/cpg/passes/UnresolvedDFGPassTest.kt | 31 +- .../cpg/passes/scopes/ScopeManagerTest.kt | 49 +- .../de/fraunhofer/aisec/cpg/BaseTest.kt | 12 - .../de/fraunhofer/aisec/cpg/TestUtils.kt | 10 - .../aisec/cpg/frontends/TestLanguage.kt | 36 +- .../src/main/golang/frontend/frontend.go | 10 + .../src/main/golang/frontend/handler.go | 18 +- cpg-language-go/src/main/golang/language.go | 19 + cpg-language-go/src/main/golang/types.go | 6 +- .../aisec/cpg/frontends/golang/GoLanguage.kt | 9 - .../frontends/golang/GoLanguageFrontend.kt | 10 +- .../aisec/cpg/passes/GoExtraPass.kt | 22 +- .../golang/GoLanguageFrontendTest.kt | 31 +- .../cpg/frontends/java/DeclarationHandler.kt | 27 +- .../cpg/frontends/java/ExpressionHandler.kt | 20 +- .../aisec/cpg/frontends/java/JavaLanguage.kt | 8 - .../frontends/java/JavaLanguageFrontend.kt | 16 +- .../cpg/passes/JavaCallResolverHelper.kt | 6 +- .../JavaExternalTypeHierarchyResolver.kt | 24 +- .../java/JavaLanguageFrontendTest.kt | 38 +- .../aisec/cpg/graph/types/TypeTests.kt | 238 ++++---- .../aisec/cpg/passes/CallResolverTest.kt | 8 +- .../cpg/frontends/llvm/LLVMIRLanguage.kt | 9 - .../frontends/llvm/LLVMIRLanguageFrontend.kt | 28 +- .../aisec/cpg/passes/CompressLLVMPass.kt | 11 +- .../llvm/LLVMIRLanguageFrontendTest.kt | 28 +- .../cpg/frontends/python/PythonLanguage.kt | 9 - .../python/PythonLanguageFrontend.kt | 10 +- .../frontends/python/PythonFrontendTest.kt | 33 +- .../typescript/JavaScriptLanguage.kt | 9 - .../typescript/TypeScriptLanguageFrontend.kt | 16 +- .../TypescriptLanguageFrontendTest.kt | 12 +- 102 files changed, 1404 insertions(+), 1436 deletions(-) create mode 100644 cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationContext.kt diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGPass.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGPass.kt index 204c0ebc46..6631cdfb00 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGPass.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGPass.kt @@ -25,8 +25,7 @@ */ package de.fraunhofer.aisec.cpg.passes -import de.fraunhofer.aisec.cpg.ScopeManager -import de.fraunhofer.aisec.cpg.TranslationConfiguration +import de.fraunhofer.aisec.cpg.TranslationContext import de.fraunhofer.aisec.cpg.analysis.ValueEvaluator import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration @@ -42,10 +41,7 @@ import de.fraunhofer.aisec.cpg.processing.strategy.Strategy * by setting the [Properties.UNREACHABLE] property of an eog-edge to true. */ @DependsOn(ControlFlowSensitiveDFGPass::class) -class UnreachableEOGPass( - config: TranslationConfiguration, - scopeManager: ScopeManager, -) : TranslationUnitPass(config, scopeManager) { +class UnreachableEOGPass(ctx: TranslationContext) : TranslationUnitPass(ctx) { override fun accept(tu: TranslationUnitDeclaration) { tu.accept( Strategy::AST_FORWARD, diff --git a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluatorTest.kt b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluatorTest.kt index 88d54c8d9d..eea8ccebcd 100644 --- a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluatorTest.kt +++ b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluatorTest.kt @@ -27,6 +27,7 @@ package de.fraunhofer.aisec.cpg.analysis import de.fraunhofer.aisec.cpg.TestUtils import de.fraunhofer.aisec.cpg.frontends.TestHandler +import de.fraunhofer.aisec.cpg.frontends.TestLanguageFrontend import de.fraunhofer.aisec.cpg.frontends.java.JavaLanguage import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration @@ -181,7 +182,7 @@ class ValueEvaluatorTest { @Test fun testHandlePlus() { - with(TestHandler()) { + with(TestHandler(TestLanguageFrontend())) { val binOp = newBinaryOperator("+") binOp.lhs = newLiteral(3, parseType("int")) binOp.rhs = newLiteral(2, parseType("int")) @@ -242,7 +243,7 @@ class ValueEvaluatorTest { @Test fun testHandleMinus() { - with(TestHandler()) { + with(TestHandler(TestLanguageFrontend())) { val binOp = newBinaryOperator("-") binOp.lhs = newLiteral(3, parseType("int")) binOp.rhs = newLiteral(2, parseType("int")) @@ -300,7 +301,7 @@ class ValueEvaluatorTest { @Test fun testHandleTimes() { - with(TestHandler()) { + with(TestHandler(TestLanguageFrontend())) { val binOp = newBinaryOperator("*") binOp.lhs = newLiteral(3, parseType("int")) binOp.rhs = newLiteral(2, parseType("int")) @@ -358,7 +359,7 @@ class ValueEvaluatorTest { @Test fun testHandleDiv() { - with(TestHandler()) { + with(TestHandler(TestLanguageFrontend())) { // For two integer values, we keep the result as a long. val binOp = newBinaryOperator("/") binOp.lhs = newLiteral(3, parseType("int")) @@ -421,7 +422,7 @@ class ValueEvaluatorTest { @Test fun testHandleUnary() { - with(TestHandler()) { + with(TestHandler(TestLanguageFrontend())) { val neg = newUnaryOperator("-", false, true) neg.input = newLiteral(3, parseType("int")) assertEquals(-3, ValueEvaluator().evaluate(neg)) diff --git a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/TypeManager.java b/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/TypeManager.java index 674fea298e..853a378d4a 100644 --- a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/TypeManager.java +++ b/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/TypeManager.java @@ -28,6 +28,7 @@ import static de.fraunhofer.aisec.cpg.graph.DeclarationBuilderKt.newTypedefDeclaration; import de.fraunhofer.aisec.cpg.ScopeManager; +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.cpp.CLanguage; @@ -56,7 +57,7 @@ public class TypeManager { // TODO: document/remove this regexp, merge with other pattern private static final Pattern funPointerPattern = Pattern.compile("\\(?\\*(?[^()]+)\\)?\\(.*\\)"); - @NotNull private static TypeManager instance = new TypeManager(); + private static boolean typeSystemActive = true; @NotNull @@ -90,10 +91,6 @@ public class TypeManager { private final Set firstOrderTypes = Collections.synchronizedSet(new HashSet<>()); private final Set secondOrderTypes = Collections.synchronizedSet(new HashSet<>()); - public static void reset() { - instance = new TypeManager(); - } - /** * @param recordDeclaration that is instantiated by a template containing parameterizedtypes * @param name of the ParameterizedType we want to get @@ -121,7 +118,6 @@ public ParameterizedType getTypeParameter(RecordDeclaration recordDeclaration, S * @param typeParameters List containing all ParameterizedTypes used by the recordDeclaration and * will be stored as value in the map */ - @Deprecated public void addTypeParameter( RecordDeclaration recordDeclaration, List typeParameters) { this.recordToTypeParameters.put(recordDeclaration, typeParameters); @@ -136,7 +132,6 @@ public void addTypeParameter( * @return */ @Nullable - @Deprecated public ParameterizedType getTypeParameter(TemplateDeclaration templateDeclaration, String name) { if (this.templateToTypeParameters.containsKey(templateDeclaration)) { for (ParameterizedType parameterizedType : @@ -259,11 +254,7 @@ public boolean typeExists(String name) { .anyMatch(type -> type.getRoot().getName().toString().equals(name)); } - private TypeManager() {} - - public static @NotNull TypeManager getInstance() { - return instance; - } + public TypeManager() {} public static boolean isTypeSystemActive() { return typeSystemActive; @@ -413,11 +404,13 @@ private Set unwrapTypes(Collection types, WrapState wrapState) { * information and their record declarations. We want to get rid of that in the future. * * @param types the types to compare - * @param provider a {@link ScopeProvider}. + * @param ctx a {@link TranslationContext}. * @return the common type */ @NotNull - public Optional getCommonType(@NotNull Collection types, ScopeProvider provider) { + public Optional getCommonType(@NotNull Collection types, TranslationContext ctx) { + var provider = ctx.getScopeManager(); + // TODO: Documentation needed. boolean sameType = types.stream().map(t -> t.getClass().getCanonicalName()).collect(Collectors.toSet()).size() @@ -513,7 +506,10 @@ public Optional getCommonType(@NotNull Collection types, ScopeProvid Optional lca = commonAncestors.stream().max(Comparator.comparingInt(Ancestor::getDepth)); Optional commonType = - lca.map(a -> TypeParser.createFrom(a.getRecord().getName(), a.getRecord().getLanguage())); + lca.map( + a -> + TypeParser.createFrom( + a.getRecord().getName().toString(), a.getRecord().getLanguage(), false, ctx)); Type finalType; if (commonType.isPresent()) { @@ -549,6 +545,7 @@ private Set getAncestors(RecordDeclaration recordDeclaration, int dept public boolean isSupertypeOf(Type superType, Type subType, MetadataProvider provider) { Language language = null; + TranslationContext ctx; if (superType instanceof UnknownType && subType instanceof UnknownType) return true; @@ -560,6 +557,13 @@ public boolean isSupertypeOf(Type superType, Type subType, MetadataProvider prov language = languageProvider.getLanguage(); } + if (provider instanceof ContextProvider contextProvider) { + ctx = contextProvider.getCtx(); + } else { + log.error("Missing context provider"); + return false; + } + // arrays and pointers match in C/C++ // TODO: Make this independent from the specific language if (language instanceof CLanguage && checkArrayAndPointer(superType, subType)) { @@ -576,8 +580,7 @@ public boolean isSupertypeOf(Type superType, Type subType, MetadataProvider prov return false; } - Optional commonType = - getCommonType(new HashSet<>(List.of(superType, subType)), scopeProvider); + Optional commonType = getCommonType(new HashSet<>(List.of(superType, subType)), ctx); if (commonType.isPresent()) { return commonType.get().equals(superType); } else { @@ -608,10 +611,11 @@ public void cleanup() { this.typeToRecord.clear(); } - private Type getTargetType(Type currTarget, String alias) { + private Type getTargetType(Type currTarget, String alias, TranslationContext ctx) { if (alias.contains("(") && alias.contains("*")) { // function pointer - return TypeParser.createFrom(currTarget.getName() + " " + alias, currTarget.getLanguage()); + return TypeParser.createFrom( + currTarget.getName() + " " + alias, currTarget.getLanguage(), false, ctx); } else if (alias.endsWith("]")) { // array type return currTarget.reference(PointerType.PointerOrigin.ARRAY); @@ -627,20 +631,23 @@ private Type getTargetType(Type currTarget, String alias) { } } - private Type getAlias(String alias, @NotNull Language language) { + private Type getAlias( + String alias, + @NotNull Language language, + TranslationContext ctx) { if (alias.contains("(") && alias.contains("*")) { // function pointer Matcher matcher = funPointerPattern.matcher(alias); if (matcher.find()) { - return TypeParser.createIgnoringAlias(matcher.group("alias"), language); + return TypeParser.createIgnoringAlias(matcher.group("alias"), language, ctx); } else { log.error("Could not find alias name in function pointer typedef: {}", alias); - return TypeParser.createIgnoringAlias(alias, language); + return TypeParser.createIgnoringAlias(alias, language, ctx); } } else { alias = alias.split("\\[")[0]; alias = alias.replace("*", ""); - return TypeParser.createIgnoringAlias(alias, language); + return TypeParser.createIgnoringAlias(alias, language, ctx); } } @@ -658,9 +665,9 @@ private Type getAlias(String alias, @NotNull Language separate( } private static List getParameterList( - String parameterList, Language language) { + String parameterList, Language language, TranslationContext ctx) { if (parameterList.startsWith("(") && parameterList.endsWith(")")) { parameterList = parameterList.trim().substring(1, parameterList.trim().length() - 1); } @@ -291,7 +292,7 @@ private static List getParameterList( for (String parameter : parametersSplit) { // ignore void parameters // TODO: WHY?? if (parameter.length() > 0 && !parameter.trim().equals("void")) { - parameters.add(createFrom(parameter.trim(), language)); + parameters.add(createFrom(parameter.trim(), language, false, ctx)); } } @@ -299,7 +300,7 @@ private static List getParameterList( } private static List getGenerics( - String typeName, Language language) { + String typeName, Language language, TranslationContext ctx) { List genericList = new ArrayList<>(); if (language instanceof HasGenerics hasGenerics && typeName.indexOf(hasGenerics.getStartCharacter()) > -1 @@ -311,7 +312,7 @@ private static List getGenerics( String[] parametersSplit = generics.split(","); for (String parameter : parametersSplit) { - genericList.add(createFrom(parameter.trim(), language)); + genericList.add(createFrom(parameter.trim(), language, false, ctx)); } } return genericList; @@ -457,16 +458,18 @@ public static Type reWrapType(@NotNull Type oldChain, @NotNull Type newRoot) { } /** - * Does the same as {@link #createIgnoringAlias(String, Language)} but explicitly does not use - * type alias resolution. This is usually not what you want. Use with care! + * Does the same as createFrom but explicitly does not use type alias resolution. This is usually + * not what you want. Use with care! * * @param string the string representation of the type * @return the type */ @NotNull public static Type createIgnoringAlias( - @NotNull String string, @NotNull Language language) { - return createFrom(string, language); + @NotNull String string, + @NotNull Language language, + TranslationContext ctx) { + return createFrom(string, language, false, ctx); } @NotNull @@ -547,7 +550,10 @@ private static Type createFromUnsafe( @NotNull String type, boolean resolveAlias, @NotNull Language language, - @Nullable ScopeManager scopeManager) { + TranslationContext ctx) { + var typeManager = ctx.getTypeManager(); + var scopeManager = ctx.getScopeManager(); + // Check if Problems during Parsing if (!checkValidTypeString(type)) { return UnknownType.getUnknownType(language); @@ -597,7 +603,6 @@ private static Type createFromUnsafe( counter++; Type finalType; - TypeManager typeManager = TypeManager.getInstance(); // Check if type is FunctionPointer Matcher funcptr = getFunctionPtrMatcher(typeBlocks.subList(counter, typeBlocks.size())); @@ -606,8 +611,8 @@ private static Type createFromUnsafe( if (finalType != null) { // Nothing to do here } else if (funcptr != null) { - Type returnType = createFrom(typeName, language); - List parameterList = getParameterList(funcptr.group("args"), language); + Type returnType = createFrom(typeName, language, false, ctx); + List parameterList = getParameterList(funcptr.group("args"), language, ctx); return typeManager.registerType(new FunctionPointerType(parameterList, language, returnType)); } else if (isIncompleteType(typeName)) { @@ -619,7 +624,7 @@ private static Type createFromUnsafe( } else { // ObjectType // Obtain possible generic List from TypeString - List generics = getGenerics(typeName, language); + List generics = getGenerics(typeName, language, ctx); typeName = removeGenerics(typeName, language); finalType = new ObjectType(typeName, generics, primitiveType, language); } @@ -638,7 +643,7 @@ private static Type createFromUnsafe( // the graph representing the type finalType = typeManager.registerType(finalType); - if (resolveAlias && scopeManager != null) { + if (resolveAlias) { return typeManager.registerType(typeManager.resolvePossibleTypedef(finalType, scopeManager)); } @@ -650,18 +655,22 @@ private static Type createFromUnsafe( * magic with generics and typedefs. This is legacy code and currently only used for CXX frontend * and should be removed at some point. */ - public static Type createFrom(@NotNull String type, boolean resolveAlias, LanguageFrontend lang) { - Type templateType = searchForTemplateTypes(type, lang.getScopeManager()); + public static Type createFrom( + @NotNull String type, boolean resolveAlias, LanguageFrontend frontend) { + Type templateType = + searchForTemplateTypes(type, frontend.getScopeManager(), frontend.getTypeManager()); if (templateType != null) { return templateType; } - Type createdType = createFrom(type, lang.getLanguage(), resolveAlias, lang.getScopeManager()); + Type createdType = createFrom(type, frontend.getLanguage(), resolveAlias, frontend.getCtx()); if (createdType instanceof SecondOrderType) { templateType = searchForTemplateTypes( - createdType.getRoot().getName().toString(), lang.getScopeManager()); + createdType.getRoot().getName().toString(), + frontend.getScopeManager(), + frontend.getTypeManager()); if (templateType != null) { createdType.setRoot(templateType); } @@ -670,9 +679,10 @@ public static Type createFrom(@NotNull String type, boolean resolveAlias, Langua return createdType; } - private static Type searchForTemplateTypes(@NotNull String type, ScopeManager scopeManager) { - return TypeManager.getInstance() - .searchTemplateScopeForDefinedParameterizedTypes(scopeManager.getCurrentScope(), type); + private static Type searchForTemplateTypes( + @NotNull String type, ScopeManager scopeManager, TypeManager typeManager) { + return typeManager.searchTemplateScopeForDefinedParameterizedTypes( + scopeManager.getCurrentScope(), type); } /** @@ -682,7 +692,7 @@ private static Type searchForTemplateTypes(@NotNull String type, ScopeManager sc * @param type string with type information * @param language the language in which the type exists. * @param resolveAlias should replace with original type in typedefs - * @param scopeManager optional, but required if resolveAlias is true + * @param ctx the translation context * @return new type representing the type string. If an exception occurs during the parsing, * UnknownType is returned */ @@ -691,28 +701,12 @@ public static Type createFrom( @NotNull String type, Language language, boolean resolveAlias, - ScopeManager scopeManager) { + TranslationContext ctx) { try { - return createFromUnsafe(type, resolveAlias, language, scopeManager); + return createFromUnsafe(type, resolveAlias, language, ctx); } catch (Exception e) { log.error("Could not parse the type correctly", e); return UnknownType.getUnknownType(language); } } - - /** Parses the type from a char sequence and the supplied language. */ - @NotNull - public static Type createFrom( - @NotNull CharSequence name, - Boolean resolveAlias, - Language language) { - return createFrom(name.toString(), language, resolveAlias, null); - } - - /** Parses the type from a char sequence and the supplied language. */ - @NotNull - public static Type createFrom( - @NotNull CharSequence name, Language language) { - return createFrom(name.toString(), language, false, null); - } } 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 1ca4756958..42eb46de39 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 @@ -791,9 +791,9 @@ class ScopeManager : ScopeProvider { override val scope: Scope? get() = currentScope - fun activateTypes(node: Node) { + fun activateTypes(node: Node, typeManager: TypeManager) { val num = AtomicInteger() - val typeCache = TypeManager.getInstance().typeCache + val typeCache = typeManager.typeCache node.accept( Strategy::AST_FORWARD, object : IVisitor() { @@ -802,8 +802,7 @@ class ScopeManager : ScopeProvider { val typeNode = t as HasType typeCache.getOrDefault(typeNode, emptyList()).forEach { (t as HasType).type = - TypeManager.getInstance() - .resolvePossibleTypedef(it, this@ScopeManager) + typeManager.resolvePossibleTypedef(it, this@ScopeManager) } typeCache.remove(t as HasType) num.getAndIncrement() @@ -816,9 +815,7 @@ class ScopeManager : ScopeProvider { // For some nodes it may happen that they are not reachable via AST, but we still need to // set their type to the requested value typeCache.forEach { (n, types) -> - types.forEach { t -> - n.type = TypeManager.getInstance().resolvePossibleTypedef(t, this) - } + types.forEach { t -> n.type = typeManager.resolvePossibleTypedef(t, this) } } } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationContext.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationContext.kt new file mode 100644 index 0000000000..0cdfc1333c --- /dev/null +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationContext.kt @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2023, 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 + +import de.fraunhofer.aisec.cpg.graph.TypeManager + +/** + * The translation context holds all necessary managers and configurations needed during the + * translation process. + */ +class TranslationContext( + /** The configuration for this translation. */ + val config: TranslationConfiguration, + + /** + * The scope manager which comprises the complete translation result. In case of sequential + * parsing, this scope manager is passed to the individual frontends one after another. In case + * of sequential parsing, individual scope managers will be passed to each language frontend + * (through individual contexts) and then finally merged into a final one. + */ + val scopeManager: ScopeManager, + + /** + * The type manager is responsible for managing type information. Currently, we have one + * instance of a [TypeManager] for the overall [TranslationResult]. + */ + val typeManager: TypeManager +) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationManager.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationManager.kt index f58640cea8..5b7f2b1cbc 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationManager.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationManager.kt @@ -71,27 +71,32 @@ private constructor( * @return a [CompletableFuture] with the [TranslationResult]. */ fun analyze(): CompletableFuture { - val result = TranslationResult(this, ScopeManager()) - // We wrap the analysis in a CompletableFuture, i.e. in an async task. - return CompletableFuture.supplyAsync { analyze2(result) } + return CompletableFuture.supplyAsync { analyzeNonAsync() } } - fun analyze2(result: TranslationResult): TranslationResult { + private fun analyzeNonAsync(): TranslationResult { + var executedFrontends = setOf() + + // Build a new global translation context + val ctx = TranslationContext(config, ScopeManager(), TypeManager()) + + // Build a new translation result + val result = TranslationResult(this, ctx) + val outerBench = Benchmark(TranslationManager::class.java, "Translation into full graph", false, result) - var executedFrontends = setOf() try { // Parse Java/C/CPP files var bench = Benchmark(this.javaClass, "Executing Language Frontend", false, result) - executedFrontends = runFrontends(result, config) + executedFrontends = runFrontends(ctx, result) bench.addMeasurement() // Apply passes for (pass in config.registeredPasses) { bench = Benchmark(pass.java, "Executing Pass", false, result) - executePassSequential(pass, result, executedFrontends) + executePassSequential(pass, ctx, result, executedFrontends) bench.addMeasurement() if (result.isCancelled) { @@ -106,7 +111,7 @@ private constructor( log.debug("Cleaning up {} Frontends", executedFrontends.size) executedFrontends.forEach { it.cleanup() } - TypeManager.getInstance().cleanup() + ctx.typeManager.cleanup() } } @@ -122,25 +127,25 @@ private constructor( * of AST nodes. * * @param result the translation result that is being mutated - * @param config the translation configuration + * @param ctx the translation context * @throws TranslationException if the language front-end runs into an error and * [TranslationConfiguration.failOnError] * * is `true`. */ @Throws(TranslationException::class) private fun runFrontends( - result: TranslationResult, - config: TranslationConfiguration, + ctx: TranslationContext, + result: TranslationResult ): Set { val usedFrontends = mutableSetOf() - for (sc in this.config.softwareComponents.keys) { + for (sc in ctx.config.softwareComponents.keys) { val component = Component() component.name = Name(sc) result.addComponent(component) - var sourceLocations: List = this.config.softwareComponents[sc] ?: listOf() + var sourceLocations: List = ctx.config.softwareComponents[sc] ?: listOf() - var useParallelFrontends = config.useParallelFrontends + var useParallelFrontends = ctx.config.useParallelFrontends val list = sourceLocations.flatMap { file -> @@ -174,15 +179,15 @@ private constructor( listOf(file) } } - if (config.useUnityBuild) { + if (ctx.config.useUnityBuild) { val tmpFile = Files.createTempFile("compile", ".cpp").toFile() tmpFile.deleteOnExit() PrintWriter(tmpFile).use { writer -> list.forEach { if (CXXLanguageFrontend.CXX_EXTENSIONS.contains(Util.getExtension(it))) { - if (config.topLevel != null) { - val topLevel = config.topLevel.toPath() + if (ctx.config.topLevel != null) { + val topLevel = ctx.config.topLevel.toPath() writer.write( """ #include "${topLevel.relativize(it.toPath())}" @@ -204,24 +209,24 @@ private constructor( } sourceLocations = listOf(tmpFile) - if (config.compilationDatabase != null) { + if (ctx.config.compilationDatabase != null) { // merge include paths from all translation units - config.compilationDatabase.addIncludePath( + ctx.config.compilationDatabase.addIncludePath( tmpFile, - config.compilationDatabase.allIncludePaths + ctx.config.compilationDatabase.allIncludePaths ) } } else { sourceLocations = list } - TypeManager.setTypeSystemActive(config.typeSystemActiveInFrontend) + TypeManager.setTypeSystemActive(ctx.config.typeSystemActiveInFrontend) usedFrontends.addAll( if (useParallelFrontends) { - parseParallel(component, result, sourceLocations) + parseParallel(component, result, ctx, sourceLocations) } else { - parseSequentially(component, result, sourceLocations) + parseSequentially(component, result, ctx, sourceLocations) } ) @@ -232,7 +237,7 @@ private constructor( s.translationUnits.forEach { val bench = Benchmark(this.javaClass, "Activating types for ${it.name}", true) - result.scopeManager.activateTypes(it) + ctx.scopeManager.activateTypes(it, ctx.typeManager) bench.stop() } } @@ -245,25 +250,29 @@ private constructor( private fun parseParallel( component: Component, result: TranslationResult, + globalCtx: TranslationContext, sourceLocations: Collection ): Set { val usedFrontends = mutableSetOf() log.info("Parallel parsing started") val futures = mutableListOf>>() - val parallelScopeManagers = mutableListOf() + val parallelContexts = mutableListOf() val futureToFile: MutableMap>, File> = IdentityHashMap() for (sourceLocation in sourceLocations) { - val scopeManager = ScopeManager() - parallelScopeManagers.add(scopeManager) + // Build a new translation context for this parallel parsing process. We need to do this + // until we can use a single scope manager concurrently. We can re-use the global + // configuration and type manager. + val ctx = TranslationContext(globalCtx.config, ScopeManager(), globalCtx.typeManager) + parallelContexts.add(ctx) val future = CompletableFuture.supplyAsync { try { - return@supplyAsync parse(component, scopeManager, sourceLocation) + return@supplyAsync parse(component, ctx, sourceLocation) } catch (e: TranslationException) { throw RuntimeException("Error parsing $sourceLocation", e) } @@ -288,7 +297,7 @@ private constructor( } // We want to merge everything into the final scope manager of the result - result.scopeManager.mergeFrom(parallelScopeManagers) + globalCtx.scopeManager.mergeFrom(parallelContexts.map { it.scopeManager }) log.info("Parallel parsing completed") @@ -299,6 +308,7 @@ private constructor( private fun parseSequentially( component: Component, result: TranslationResult, + ctx: TranslationContext, sourceLocations: Collection ): Set { val usedFrontends = mutableSetOf() @@ -306,7 +316,7 @@ private constructor( for (sourceLocation in sourceLocations) { log.info("Parsing {}", sourceLocation.absolutePath) - parse(component, result.scopeManager, sourceLocation).ifPresent { f: LanguageFrontend -> + parse(component, ctx, sourceLocation).ifPresent { f: LanguageFrontend -> handleCompletion(result, usedFrontends, sourceLocation, f) } } @@ -333,12 +343,12 @@ private constructor( @Throws(TranslationException::class) private fun parse( component: Component, - scopeManager: ScopeManager, - sourceLocation: File + ctx: TranslationContext, + sourceLocation: File, ): Optional { var frontend: LanguageFrontend? = null try { - frontend = getFrontend(sourceLocation, scopeManager) + frontend = getFrontend(sourceLocation, ctx) if (frontend == null) { log.error("Found no parser frontend for ${sourceLocation.name}") @@ -360,19 +370,16 @@ private constructor( return Optional.ofNullable(frontend) } - private fun getFrontend( - file: File, - scopeManager: ScopeManager, - ): LanguageFrontend? { + private fun getFrontend(file: File, ctx: TranslationContext): LanguageFrontend? { val language = file.language return if (language != null) { try { // Make sure, that our simple types are also known to the type manager - language.builtInTypes.values.forEach { TypeManager.getInstance().registerType(it) } + language.builtInTypes.values.forEach { ctx.typeManager.registerType(it) } // Return a new language frontend - language.newFrontend(config, scopeManager) + language.newFrontend(ctx) } catch (e: Exception) { when (e) { is InstantiationException, diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationResult.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationResult.kt index 9fe035d606..8f06161eb2 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationResult.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationResult.kt @@ -25,29 +25,29 @@ */ package de.fraunhofer.aisec.cpg +import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration import de.fraunhofer.aisec.cpg.helpers.MeasurementHolder import de.fraunhofer.aisec.cpg.helpers.StatisticsHolder +import de.fraunhofer.aisec.cpg.passes.Pass import de.fraunhofer.aisec.cpg.passes.PassTarget import java.util.* import java.util.concurrent.ConcurrentHashMap -import java.util.function.Consumer -import java.util.stream.Collectors /** - * The global (intermediate) result of the translation. A [ ] will initially populate it and a [ ] - * can extend it. + * The global (intermediate) result of the translation. A [LanguageFrontend] will initially populate + * it and a [Pass] can extend it. */ class TranslationResult( - val translationManager: TranslationManager, + /** A reference to our [TranslationManager]. */ + private val translationManager: TranslationManager, /** - * The scope manager which comprises the complete translation result. In case of sequential - * parsing, this scope manager is passed to the individual frontends one after another. In case - * of sequential parsing, individual scope managers will be spawned by each language frontend - * and then finally merged into this one. + * The final [TranslationContext] of this translation result. Currently, for parallel + * processing, we are creating one translation context for each parsed file (containing a + * dedicated [ScopeManager] each). This property will contain the final, merged context. */ - val scopeManager: ScopeManager + var finalCtx: TranslationContext, ) : Node(), StatisticsHolder, PassTarget { /** @@ -150,22 +150,16 @@ class TranslationResult( override val translatedFiles: List get() { val result: MutableList = ArrayList() - components.forEach( - Consumer { sc: Component -> - result.addAll( - sc.translationUnits - .stream() - .map(TranslationUnitDeclaration::name) - .map { obj: Name -> obj.toString() } - .collect(Collectors.toList()) - ) - } - ) + components.forEach { sc: Component -> + result.addAll( + sc.translationUnits.map(TranslationUnitDeclaration::name).map(Name::toString) + ) + } return result } override val config: TranslationConfiguration - get() = translationManager.config + get() = finalCtx.config companion object { const val SOURCE_LOCATIONS_TO_FRONTEND = "sourceLocationsToFrontend" diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/Handler.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/Handler.kt index fefe5b5cd4..c5857a75cc 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/Handler.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/Handler.kt @@ -25,8 +25,8 @@ */ package de.fraunhofer.aisec.cpg.frontends +import de.fraunhofer.aisec.cpg.TranslationContext import de.fraunhofer.aisec.cpg.graph.* -import de.fraunhofer.aisec.cpg.graph.newCallExpression import de.fraunhofer.aisec.cpg.graph.scopes.Scope import de.fraunhofer.aisec.cpg.helpers.Util.errorWithFileLocation import java.lang.reflect.ParameterizedType @@ -51,7 +51,7 @@ abstract class Handler( protected val configConstructor: Supplier, /** Returns the frontend which used this handler. */ var frontend: L -) : LanguageProvider, CodeAndLocationProvider, ScopeProvider, NamespaceProvider { +) : LanguageProvider, CodeAndLocationProvider, ScopeProvider, NamespaceProvider, ContextProvider { protected val map = HashMap, HandlerInterface>() private val typeOfT: Class<*>? @@ -181,6 +181,9 @@ abstract class Handler( override val namespace: Name? get() = frontend.namespace + override val ctx: TranslationContext + get() = frontend.ctx + companion object { @JvmStatic protected val log: Logger = LoggerFactory.getLogger(Handler::class.java) } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/Language.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/Language.kt index d9a4ad0661..c332229432 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/Language.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/Language.kt @@ -30,8 +30,7 @@ import com.fasterxml.jackson.core.JsonGenerator import com.fasterxml.jackson.databind.SerializerProvider import com.fasterxml.jackson.databind.annotation.JsonSerialize import com.fasterxml.jackson.databind.ser.std.StdSerializer -import de.fraunhofer.aisec.cpg.ScopeManager -import de.fraunhofer.aisec.cpg.TranslationConfiguration +import de.fraunhofer.aisec.cpg.TranslationContext import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.newUnknownType import de.fraunhofer.aisec.cpg.graph.statements.expressions.BinaryOperator @@ -39,6 +38,7 @@ import de.fraunhofer.aisec.cpg.graph.types.* import de.fraunhofer.aisec.cpg.graph.types.Type import java.io.File import kotlin.reflect.KClass +import kotlin.reflect.full.primaryConstructor /** * Represents a programming language. When creating new languages in the CPG, one must derive custom @@ -78,11 +78,14 @@ abstract class Language : Node() { /** All operators which perform and assignment and an operation using lhs and rhs. */ abstract val compoundAssignmentOperators: Set - /** Creates a new [LanguageFrontend] object to parse the language. */ - abstract fun newFrontend( - config: TranslationConfiguration, - scopeManager: ScopeManager = ScopeManager(), - ): T + /** + * Creates a new [LanguageFrontend] object to parse the language. It requires the + * [TranslationContext], which holds the necessary managers. + */ + open fun newFrontend(ctx: TranslationContext): T { + return this.frontend.primaryConstructor?.call(this, ctx) + ?: throw TranslationException("could not instantiate language frontend") + } /** * Returns the type conforming to the given [typeString]. If no matching type is found in the diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/LanguageFrontend.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/LanguageFrontend.kt index 71910958d6..383462388f 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/LanguageFrontend.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/LanguageFrontend.kt @@ -27,6 +27,7 @@ package de.fraunhofer.aisec.cpg.frontends import de.fraunhofer.aisec.cpg.ScopeManager import de.fraunhofer.aisec.cpg.TranslationConfiguration +import de.fraunhofer.aisec.cpg.TranslationContext import de.fraunhofer.aisec.cpg.TranslationResult import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration @@ -47,23 +48,27 @@ import org.slf4j.LoggerFactory * [github wiki page](https://github.com/Fraunhofer-AISEC/cpg/wiki/Language-Frontends). */ abstract class LanguageFrontend( + /** The language this frontend works for. */ override val language: Language, - val config: TranslationConfiguration, - scopeManager: ScopeManager, + + /** + * The translation context, which contains all necessary managers used in this frontend parsing + * process. Note, that different contexts could passed to frontends, e.g., in parallel parsing + * to supply different managers to different frontends. + */ + final override var ctx: TranslationContext, ) : ProcessedListener(), CodeAndLocationProvider, LanguageProvider, ScopeProvider, - NamespaceProvider { - var scopeManager: ScopeManager = scopeManager - set(scopeManager) { - field = scopeManager - field.lang = this - } + NamespaceProvider, + ContextProvider { + val scopeManager: ScopeManager = ctx.scopeManager + val typeManager: TypeManager = ctx.typeManager + val config: TranslationConfiguration = ctx.config init { - // this.scopeManager = scopeManager this.scopeManager.lang = this } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/LanguageTraits.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/LanguageTraits.kt index c1d962c284..956dbb8a81 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/LanguageTraits.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/LanguageTraits.kt @@ -26,6 +26,7 @@ package de.fraunhofer.aisec.cpg.frontends import de.fraunhofer.aisec.cpg.ScopeManager +import de.fraunhofer.aisec.cpg.TranslationContext import de.fraunhofer.aisec.cpg.graph.Name import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration @@ -71,7 +72,7 @@ interface HasTemplates : HasGenerics { curClass: RecordDeclaration?, templateCall: CallExpression, applyInference: Boolean, - scopeManager: ScopeManager, + ctx: TranslationContext, currentTU: TranslationUnitDeclaration ): Pair> } @@ -97,7 +98,7 @@ interface HasComplexCallResolution : LanguageTrait { */ fun refineNormalCallResolution( call: CallExpression, - scopeManager: ScopeManager, + ctx: TranslationContext, currentTU: TranslationUnitDeclaration ): List @@ -113,7 +114,7 @@ interface HasComplexCallResolution : LanguageTrait { curClass: RecordDeclaration?, possibleContainingTypes: Set, call: CallExpression, - scopeManager: ScopeManager, + ctx: TranslationContext, currentTU: TranslationUnitDeclaration, callResolver: CallResolver ): List @@ -130,7 +131,8 @@ interface HasComplexCallResolution : LanguageTrait { fun refineInvocationCandidatesFromRecord( recordDeclaration: RecordDeclaration, call: CallExpression, - namePattern: Pattern + namePattern: Pattern, + ctx: TranslationContext ): List } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CLanguage.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CLanguage.kt index 83007ad5a7..244b0da599 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CLanguage.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CLanguage.kt @@ -26,8 +26,7 @@ package de.fraunhofer.aisec.cpg.frontends.cpp import com.fasterxml.jackson.annotation.JsonIgnore -import de.fraunhofer.aisec.cpg.ScopeManager -import de.fraunhofer.aisec.cpg.TranslationConfiguration +import de.fraunhofer.aisec.cpg.TranslationContext import de.fraunhofer.aisec.cpg.frontends.* import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration @@ -113,22 +112,15 @@ open class CLanguage : IntegerType("unsigned long long int", 64, this, NumericType.Modifier.UNSIGNED) ) - override fun newFrontend( - config: TranslationConfiguration, - scopeManager: ScopeManager, - ): CXXLanguageFrontend { - return CXXLanguageFrontend(this, config, scopeManager) - } - override fun refineNormalCallResolution( call: CallExpression, - scopeManager: ScopeManager, + ctx: TranslationContext, currentTU: TranslationUnitDeclaration ): List { - val invocationCandidates = scopeManager.resolveFunction(call).toMutableList() + val invocationCandidates = ctx.scopeManager.resolveFunction(call).toMutableList() if (invocationCandidates.isEmpty()) { // Check for implicit casts - invocationCandidates.addAll(resolveWithImplicitCastFunc(call, scopeManager)) + invocationCandidates.addAll(resolveWithImplicitCastFunc(call, ctx)) } return invocationCandidates } @@ -137,7 +129,7 @@ open class CLanguage : curClass: RecordDeclaration?, possibleContainingTypes: Set, call: CallExpression, - scopeManager: ScopeManager, + ctx: TranslationContext, currentTU: TranslationUnitDeclaration, callResolver: CallResolver ): List = emptyList() @@ -145,7 +137,8 @@ open class CLanguage : override fun refineInvocationCandidatesFromRecord( recordDeclaration: RecordDeclaration, call: CallExpression, - namePattern: Pattern + namePattern: Pattern, + ctx: TranslationContext ): List = emptyList() /** @@ -155,10 +148,12 @@ open class CLanguage : */ protected fun resolveWithImplicitCastFunc( call: CallExpression, - scopeManager: ScopeManager + ctx: TranslationContext, ): List { val initialInvocationCandidates = - listOf(*scopeManager.resolveFunctionStopScopeTraversalOnDefinition(call).toTypedArray()) - return resolveWithImplicitCast(call, initialInvocationCandidates) + listOf( + *ctx.scopeManager.resolveFunctionStopScopeTraversalOnDefinition(call).toTypedArray() + ) + return resolveWithImplicitCast(call, initialInvocationCandidates, ctx) } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CPPLanguage.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CPPLanguage.kt index 09af4db425..b986a512fb 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CPPLanguage.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CPPLanguage.kt @@ -25,7 +25,7 @@ */ package de.fraunhofer.aisec.cpg.frontends.cpp -import de.fraunhofer.aisec.cpg.ScopeManager +import de.fraunhofer.aisec.cpg.TranslationContext import de.fraunhofer.aisec.cpg.frontends.* import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.declarations.* @@ -93,7 +93,7 @@ class CPPLanguage : curClass: RecordDeclaration?, possibleContainingTypes: Set, call: CallExpression, - scopeManager: ScopeManager, + ctx: TranslationContext, currentTU: TranslationUnitDeclaration, callResolver: CallResolver ): List { @@ -107,11 +107,11 @@ class CPPLanguage : } if (invocationCandidates.isEmpty()) { // Check for usage of default args - invocationCandidates.addAll(resolveWithDefaultArgsFunc(call, scopeManager)) + invocationCandidates.addAll(resolveWithDefaultArgsFunc(call, ctx)) } if (invocationCandidates.isEmpty()) { val (ok, candidates) = - handleTemplateFunctionCalls(curClass, call, false, scopeManager, currentTU) + handleTemplateFunctionCalls(curClass, call, false, ctx, currentTU) if (ok) { return candidates } @@ -120,7 +120,7 @@ class CPPLanguage : } if (invocationCandidates.isEmpty()) { // Check for usage of implicit cast - invocationCandidates.addAll(resolveWithImplicitCastFunc(call, scopeManager)) + invocationCandidates.addAll(resolveWithImplicitCastFunc(call, ctx)) } // Make sure, that our invocation candidates for member call expressions are really METHODS, @@ -136,7 +136,8 @@ class CPPLanguage : override fun refineInvocationCandidatesFromRecord( recordDeclaration: RecordDeclaration, call: CallExpression, - namePattern: Pattern + namePattern: Pattern, + ctx: TranslationContext ): List { val invocationCandidate = mutableListOf( @@ -165,7 +166,8 @@ class CPPLanguage : call, recordDeclaration.methods.filter { m -> namePattern.matcher(m.name).matches() /*&& !m.isImplicit()*/ - } + }, + ctx ) ) } @@ -174,21 +176,20 @@ class CPPLanguage : override fun refineNormalCallResolution( call: CallExpression, - scopeManager: ScopeManager, + ctx: TranslationContext, currentTU: TranslationUnitDeclaration ): List { - val invocationCandidates = scopeManager.resolveFunction(call).toMutableList() + val invocationCandidates = ctx.scopeManager.resolveFunction(call).toMutableList() if (invocationCandidates.isEmpty()) { // Check for usage of default args - invocationCandidates.addAll(resolveWithDefaultArgsFunc(call, scopeManager)) + invocationCandidates.addAll(resolveWithDefaultArgsFunc(call, ctx)) } if (invocationCandidates.isEmpty()) { // Check if the call can be resolved to a function template instantiation. If it can be // resolver, we resolve the call. Otherwise, there won't be an inferred template, we // will do an inferred FunctionDeclaration instead. call.templateParameterEdges = mutableListOf() - val (ok, candidates) = - handleTemplateFunctionCalls(null, call, false, scopeManager, currentTU) + val (ok, candidates) = handleTemplateFunctionCalls(null, call, false, ctx, currentTU) if (ok) { return candidates } @@ -198,7 +199,7 @@ class CPPLanguage : if (invocationCandidates.isEmpty()) { // If we don't find any candidate and our current language is c/c++ we check if there is // a candidate with an implicit cast - invocationCandidates.addAll(resolveWithImplicitCastFunc(call, scopeManager)) + invocationCandidates.addAll(resolveWithImplicitCastFunc(call, ctx)) } return invocationCandidates @@ -213,10 +214,10 @@ class CPPLanguage : */ private fun resolveWithDefaultArgsFunc( call: CallExpression, - scopeManager: ScopeManager + ctx: TranslationContext ): List { val invocationCandidates = - scopeManager.resolveFunctionStopScopeTraversalOnDefinition(call).filter { + ctx.scopeManager.resolveFunctionStopScopeTraversalOnDefinition(call).filter { call.signature.size < it.signatureTypes.size } return resolveWithDefaultArgs(call, invocationCandidates) @@ -238,10 +239,11 @@ class CPPLanguage : curClass: RecordDeclaration?, templateCall: CallExpression, applyInference: Boolean, - scopeManager: ScopeManager, + ctx: TranslationContext, currentTU: TranslationUnitDeclaration ): Pair> { - val instantiationCandidates = scopeManager.resolveFunctionTemplateDeclaration(templateCall) + val instantiationCandidates = + ctx.scopeManager.resolveFunctionTemplateDeclaration(templateCall) for (functionTemplateDeclaration in instantiationCandidates) { val initializationType = mutableMapOf() @@ -259,7 +261,8 @@ class CPPLanguage : templateCall, initializationType, orderedInitializationSignature, - explicitInstantiation + explicitInstantiation, + ctx ) val function = functionTemplateDeclaration.realization[0] if ( @@ -285,7 +288,8 @@ class CPPLanguage : function, initializationSignature, initializationType, - orderedInitializationSignature + orderedInitializationSignature, + ctx ) return Pair(true, candidates) } @@ -297,7 +301,7 @@ class CPPLanguage : // If we want to use an inferred functionTemplateDeclaration, this needs to be provided. // Otherwise, we could not resolve to a template and no modifications are made val functionTemplateDeclaration = - holder.startInference(scopeManager).createInferredFunctionTemplate(templateCall) + holder.startInference(ctx).createInferredFunctionTemplate(templateCall) templateCall.templateInstantiation = functionTemplateDeclaration val edges = templateCall.templateParameterEdges // Set instantiation propertyEdges diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CXXLanguageFrontend.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CXXLanguageFrontend.kt index c6d613849d..cf8fd8cc4d 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CXXLanguageFrontend.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CXXLanguageFrontend.kt @@ -26,8 +26,7 @@ package de.fraunhofer.aisec.cpg.frontends.cpp import de.fraunhofer.aisec.cpg.ResolveInFrontend -import de.fraunhofer.aisec.cpg.ScopeManager -import de.fraunhofer.aisec.cpg.TranslationConfiguration +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.TranslationException @@ -76,11 +75,8 @@ import org.slf4j.LoggerFactory * ad [GPPLanguage]). This enables us (to some degree) to deal with the finer difference between C * and C++ code. */ -class CXXLanguageFrontend( - language: Language, - config: TranslationConfiguration, - scopeManager: ScopeManager, -) : LanguageFrontend(language, config, scopeManager) { +class CXXLanguageFrontend(language: Language, ctx: TranslationContext) : + LanguageFrontend(language, ctx) { /** * The dialect used by this language frontend, either [GCCLanguage] for C or [GPPLanguage] for @@ -556,7 +552,7 @@ class CXXLanguageFrontend( } } - type = TypeManager.getInstance().registerType(type) + type = typeManager.registerType(type) type = this.adjustType(declarator, type) return type @@ -637,7 +633,7 @@ class CXXLanguageFrontend( } // Make sure, the type manager knows about this type - return TypeManager.getInstance().registerType(type) + return typeManager.registerType(type) } companion object { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/DeclarationHandler.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/DeclarationHandler.kt index c7f5b82151..a314cdd2bf 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/DeclarationHandler.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/DeclarationHandler.kt @@ -312,12 +312,11 @@ class DeclarationHandler(lang: CXXLanguageFrontend) : val typeParamDeclaration = frontend.declaratorHandler.handle(templateParameter) as TypeParamDeclaration val parameterizedType = - TypeManager.getInstance() - .createOrGetTypeParameter( - templateDeclaration, - templateParameter.name.toString(), - language - ) + frontend.typeManager.createOrGetTypeParameter( + templateDeclaration, + templateParameter.name.toString(), + language + ) typeParamDeclaration.type = parameterizedType if (templateParameter.defaultType != null) { val defaultType = @@ -377,8 +376,7 @@ class DeclarationHandler(lang: CXXLanguageFrontend) : templateDeclaration: TemplateDeclaration, innerDeclaration: RecordDeclaration ) { - val parameterizedTypes = - TypeManager.getInstance().getAllParameterizedType(templateDeclaration) + val parameterizedTypes = frontend.typeManager.getAllParameterizedType(templateDeclaration) // Loop through all the methods and adjust their receiver types for (method in (innerDeclaration as? RecordDeclaration)?.methods ?: listOf()) { @@ -543,13 +541,12 @@ class DeclarationHandler(lang: CXXLanguageFrontend) : val (nameDecl: IASTDeclarator, _) = declarator.realName() val declaration = - TypeManager.getInstance() - .createTypeAlias( - frontend, - frontend.getCodeFromRawNode(ctx), - type, - nameDecl.name.toString() - ) + frontend.typeManager.createTypeAlias( + frontend, + frontend.getCodeFromRawNode(ctx), + type, + nameDecl.name.toString() + ) // Add the declaration to the current scope frontend.scopeManager.addDeclaration(declaration) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/ExpressionHandler.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/ExpressionHandler.kt index a16882a344..bc23a553c0 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/ExpressionHandler.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/ExpressionHandler.kt @@ -356,7 +356,7 @@ class ExpressionHandler(lang: CXXLanguageFrontend) : ) { // this can either be just a meaningless bracket or it can be a cast expression val typeName = (ctx.operand as IASTIdExpression).name.toString() - if (TypeManager.getInstance().typeExists(typeName)) { + if (frontend.typeManager.typeExists(typeName)) { val cast = newCastExpression(frontend.getCodeFromRawNode(ctx)) cast.castType = parseType(typeName) cast.expression = input ?: newProblemExpression("could not parse input") diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/ExpressionBuilder.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/ExpressionBuilder.kt index ca734943b3..d072d2359a 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/ExpressionBuilder.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/ExpressionBuilder.kt @@ -588,6 +588,7 @@ fun MetadataProvider.newProblemExpression( fun Literal.duplicate(implicit: Boolean): Literal { val duplicate = Literal() + duplicate.ctx = this.ctx duplicate.language = this.language duplicate.value = this.value duplicate.type = this.type @@ -610,6 +611,7 @@ fun Literal.duplicate(implicit: Boolean): Literal { fun TypeExpression.duplicate(implicit: Boolean): TypeExpression { val duplicate = TypeExpression() + duplicate.ctx = this.ctx duplicate.name = this.name.clone() duplicate.language = this.language duplicate.type = this.type diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Node.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Node.kt index 05f4144bb7..788468fbb0 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Node.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Node.kt @@ -26,7 +26,9 @@ package de.fraunhofer.aisec.cpg.graph import com.fasterxml.jackson.annotation.JsonBackReference +import com.fasterxml.jackson.annotation.JsonIgnore import de.fraunhofer.aisec.cpg.PopulatedByPass +import de.fraunhofer.aisec.cpg.TranslationContext import de.fraunhofer.aisec.cpg.frontends.Handler import de.fraunhofer.aisec.cpg.frontends.Language import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend @@ -52,15 +54,21 @@ import de.fraunhofer.aisec.cpg.sarif.PhysicalLocation import java.util.* import org.apache.commons.lang3.builder.ToStringBuilder import org.apache.commons.lang3.builder.ToStringStyle -import org.neo4j.ogm.annotation.GeneratedValue -import org.neo4j.ogm.annotation.Id -import org.neo4j.ogm.annotation.Relationship +import org.neo4j.ogm.annotation.* import org.neo4j.ogm.annotation.typeconversion.Convert import org.slf4j.Logger import org.slf4j.LoggerFactory /** The base class for all graph objects that are going to be persisted in the database. */ -open class Node : IVisitable, Persistable, LanguageProvider, ScopeProvider { +open class Node : IVisitable, Persistable, LanguageProvider, ScopeProvider, ContextProvider { + /** + * Because we are updating type information in the properties of the node, we need a reference + * to managers such as the [TypeManager] instance which is responsible for this particular node. + * All managers are bundled in [TranslationContext]. It is set in [Node.applyMetadata] when a + * [ContextProvider] is provided. + */ + @get:JsonIgnore @Transient override var ctx: TranslationContext? = null + /** * This property holds the full name using our new [Name] class. It is currently not persisted * in the graph database. @@ -112,7 +120,7 @@ open class Node : IVisitable, Persistable, LanguageProvider, ScopeProvider var prevEOGEdges: MutableList> = ArrayList() protected set - /** outgoing control flow edges. */ + /** Outgoing control flow edges. */ @PopulatedByPass(EvaluationOrderGraphPass::class) @Relationship(value = "EOG", direction = Relationship.Direction.OUTGOING) var nextEOGEdges: MutableList> = ArrayList() diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/NodeBuilder.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/NodeBuilder.kt index 6e25d2c6c7..5d947622fa 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/NodeBuilder.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/NodeBuilder.kt @@ -25,11 +25,13 @@ */ package de.fraunhofer.aisec.cpg.graph +import de.fraunhofer.aisec.cpg.TranslationContext import de.fraunhofer.aisec.cpg.frontends.* import de.fraunhofer.aisec.cpg.graph.Node.Companion.EMPTY_NAME import de.fraunhofer.aisec.cpg.graph.NodeBuilder.log import de.fraunhofer.aisec.cpg.graph.scopes.Scope import de.fraunhofer.aisec.cpg.graph.statements.expressions.* +import de.fraunhofer.aisec.cpg.graph.types.Type import de.fraunhofer.aisec.cpg.graph.types.TypeParser import de.fraunhofer.aisec.cpg.graph.types.UnknownType import de.fraunhofer.aisec.cpg.passes.inference.IsInferredProvider @@ -38,7 +40,7 @@ import org.slf4j.LoggerFactory object NodeBuilder { internal val LOGGER = LoggerFactory.getLogger(NodeBuilder::class.java) - fun log(node: Node?) { + fun log(node: Node) { LOGGER.trace("Creating {}", node) } } @@ -117,6 +119,16 @@ fun Node.applyMetadata( this.scope = provider.scope } + if (provider is ContextProvider) { + this.ctx = provider.ctx + } + + if (this.ctx == null) { + throw TranslationException( + "Trying to create a node without a ContextProvider. This will fail." + ) + } + if (name != null) { val namespace = if (provider is NamespaceProvider) { @@ -224,10 +236,19 @@ fun MetadataProvider?.newUnknownType(): UnknownType { * since we are moving away from the [TypeParser] altogether. */ @JvmOverloads -fun LanguageProvider.parseType(name: CharSequence, resolveAlias: Boolean = false) = - TypeParser.createFrom(name, resolveAlias, language) +fun LanguageProvider.parseType(name: CharSequence, resolveAlias: Boolean = false): Type { + return if (this is ContextProvider) { + TypeParser.createFrom(name.toString(), language, resolveAlias, this.ctx) + } else { + throw TranslationException("Cannot parse type without translation context") + } +} /** Returns a new [Name] based on the [localName] and the current namespace as parent. */ fun NamespaceProvider.fqn(localName: String): Name { return this.namespace.fqn(localName) } + +interface ContextProvider : MetadataProvider { + val ctx: TranslationContext? +} diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt index 33a430b198..1ac97cad94 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt @@ -25,10 +25,7 @@ */ package de.fraunhofer.aisec.cpg.graph.builder -import de.fraunhofer.aisec.cpg.ScopeManager -import de.fraunhofer.aisec.cpg.TranslationConfiguration -import de.fraunhofer.aisec.cpg.TranslationManager -import de.fraunhofer.aisec.cpg.TranslationResult +import de.fraunhofer.aisec.cpg.* import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.declarations.* @@ -38,16 +35,17 @@ import de.fraunhofer.aisec.cpg.graph.statements.expressions.* import de.fraunhofer.aisec.cpg.graph.types.Type import de.fraunhofer.aisec.cpg.passes.executePassSequential -fun LanguageFrontend.translationResult( - config: TranslationConfiguration, - init: TranslationResult.() -> Unit -): TranslationResult { - val node = TranslationResult(TranslationManager.builder().config(config).build(), scopeManager) +fun LanguageFrontend.translationResult(init: TranslationResult.() -> Unit): TranslationResult { + val node = + TranslationResult( + TranslationManager.builder().config(ctx.config).build(), + ctx, + ) val component = Component() node.addComponent(component) init(node) - config.registeredPasses.forEach { executePassSequential(it, node, listOf()) } + ctx.config.registeredPasses.forEach { executePassSequential(it, ctx, node, listOf()) } return node } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/ConstructorDeclaration.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/ConstructorDeclaration.kt index 138c3df43e..98ce6fe469 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/ConstructorDeclaration.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/ConstructorDeclaration.kt @@ -25,7 +25,7 @@ */ package de.fraunhofer.aisec.cpg.graph.declarations -import de.fraunhofer.aisec.cpg.graph.types.TypeParser +import de.fraunhofer.aisec.cpg.graph.parseType /** * The declaration of a constructor within a [RecordDeclaration]. Is it essentially a special case @@ -39,7 +39,7 @@ class ConstructorDeclaration : MethodDeclaration() { super.recordDeclaration = recordDeclaration if (recordDeclaration != null) { // constructors always have implicitly the return type of their class - returnTypes = listOf(TypeParser.createFrom(recordDeclaration.name, language)) + returnTypes = listOf(parseType(recordDeclaration.name)) } } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/FieldDeclaration.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/FieldDeclaration.kt index f657ed9ec8..b2f3c985f2 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/FieldDeclaration.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/FieldDeclaration.kt @@ -25,13 +25,11 @@ */ package de.fraunhofer.aisec.cpg.graph.declarations -import de.fraunhofer.aisec.cpg.graph.AST -import de.fraunhofer.aisec.cpg.graph.HasInitializer -import de.fraunhofer.aisec.cpg.graph.HasType -import de.fraunhofer.aisec.cpg.graph.TypeManager +import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression import de.fraunhofer.aisec.cpg.graph.statements.expressions.InitializerListExpression import de.fraunhofer.aisec.cpg.graph.types.Type +import de.fraunhofer.aisec.cpg.graph.types.UnknownType import java.util.* import org.apache.commons.lang3.builder.ToStringBuilder import org.neo4j.ogm.annotation.Relationship @@ -81,10 +79,10 @@ class FieldDeclaration : ValueDeclaration(), HasType.TypeListener, HasInitialize var modifiers: List = mutableListOf() override fun typeChanged(src: HasType, root: MutableList, oldType: Type) { - if (!TypeManager.isTypeSystemActive()) { + if (!isTypeSystemActive) { return } - if (!TypeManager.getInstance().isUnknown(type) && src.propagationType == oldType) { + if (type !is UnknownType && src.propagationType == oldType) { return } val previous = type @@ -98,7 +96,7 @@ class FieldDeclaration : ValueDeclaration(), HasType.TypeListener, HasInitialize // can be ignored once we have a type if (isArray) { src.type - } else if (!TypeManager.getInstance().isUnknown(type)) { + } else if (type !is UnknownType) { return } else { src.type.dereference() @@ -113,7 +111,7 @@ class FieldDeclaration : ValueDeclaration(), HasType.TypeListener, HasInitialize } override fun possibleSubTypesChanged(src: HasType, root: MutableList) { - if (!TypeManager.isTypeSystemActive()) { + if (!isTypeSystemActive) { return } val subTypes: MutableList = ArrayList(possibleSubTypes) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/FunctionDeclaration.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/FunctionDeclaration.kt index 12ec422885..fa5ab715b2 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/FunctionDeclaration.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/FunctionDeclaration.kt @@ -25,14 +25,11 @@ */ package de.fraunhofer.aisec.cpg.graph.declarations -import de.fraunhofer.aisec.cpg.graph.AST -import de.fraunhofer.aisec.cpg.graph.DeclarationHolder -import de.fraunhofer.aisec.cpg.graph.TypeManager +import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.edge.Properties import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.propertyEqualsList import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdgeDelegate -import de.fraunhofer.aisec.cpg.graph.newUnknownType import de.fraunhofer.aisec.cpg.graph.statements.CompoundStatement import de.fraunhofer.aisec.cpg.graph.statements.Statement import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression @@ -133,7 +130,7 @@ open class FunctionDeclaration : ValueDeclaration(), DeclarationHolder { return true } val provided = targetSignature[i] - if (!TypeManager.getInstance().isSupertypeOf(declared.type, provided, this)) { + if (!isSupertypeOf(declared.type, provided)) { return false } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/RecordDeclaration.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/RecordDeclaration.kt index 0b997e3357..839b75cd91 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/RecordDeclaration.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/RecordDeclaration.kt @@ -31,10 +31,10 @@ import de.fraunhofer.aisec.cpg.graph.StatementHolder import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.propertyEqualsList import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdgeDelegate +import de.fraunhofer.aisec.cpg.graph.parseType import de.fraunhofer.aisec.cpg.graph.statements.Statement import de.fraunhofer.aisec.cpg.graph.types.ObjectType import de.fraunhofer.aisec.cpg.graph.types.Type -import de.fraunhofer.aisec.cpg.graph.types.TypeParser import java.util.stream.Collectors import java.util.stream.Stream import org.apache.commons.lang3.builder.ToStringBuilder @@ -216,7 +216,7 @@ class RecordDeclaration : Declaration(), DeclarationHolder, StatementHolder { * @return the type */ fun toType(): Type { - val type = TypeParser.createFrom(name, language) + val type = parseType(name) if (type is ObjectType) { // as a shortcut, directly set the record declaration. This will be otherwise done // later by a pass, but for some frontends we need this immediately, so we set diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/ValueDeclaration.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/ValueDeclaration.kt index f48bc6d454..ab7b1106f6 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/ValueDeclaration.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/ValueDeclaration.kt @@ -26,12 +26,10 @@ package de.fraunhofer.aisec.cpg.graph.declarations import de.fraunhofer.aisec.cpg.PopulatedByPass -import de.fraunhofer.aisec.cpg.graph.HasType -import de.fraunhofer.aisec.cpg.graph.TypeManager +import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.edge.Properties import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.unwrap -import de.fraunhofer.aisec.cpg.graph.newUnknownType import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression import de.fraunhofer.aisec.cpg.graph.types.FunctionPointerType import de.fraunhofer.aisec.cpg.graph.types.ReferenceType @@ -59,13 +57,13 @@ abstract class ValueDeclaration : Declaration(), HasType { override var type: Type get() { val result: Type = - if (TypeManager.isTypeSystemActive()) { + if (isTypeSystemActive) { _type } else { - TypeManager.getInstance() - .typeCache - .computeIfAbsent(this) { mutableListOf() } - .firstOrNull() + ctx?.typeManager + ?.typeCache + ?.computeIfAbsent(this) { mutableListOf() } + ?.firstOrNull() ?: newUnknownType() } return result @@ -78,8 +76,8 @@ abstract class ValueDeclaration : Declaration(), HasType { @Relationship("POSSIBLE_SUB_TYPES") protected var _possibleSubTypes = mutableListOf() override var possibleSubTypes: List get() { - return if (!TypeManager.isTypeSystemActive()) { - TypeManager.getInstance().typeCache.getOrDefault(this, emptyList()) + return if (!isTypeSystemActive) { + ctx?.typeManager?.typeCache?.getOrDefault(this, emptyList()) ?: listOf() } else _possibleSubTypes } set(value) { @@ -134,39 +132,36 @@ abstract class ValueDeclaration : Declaration(), HasType { } override fun setType(type: Type, root: MutableList?) { - var t: Type? = type + var t: Type = type var r: MutableList? = root - if (!TypeManager.isTypeSystemActive()) { - TypeManager.getInstance().cacheType(this, t) + if (!isTypeSystemActive) { + cacheType(t) return } if (r == null) { r = ArrayList() } if ( - t == null || - r.contains(this) || - TypeManager.getInstance().isUnknown(t) || + r.contains(this) || + t is UnknownType || this._type is FunctionPointerType && t !is FunctionPointerType ) { return } val oldType = this.type t = t.duplicate() - val subTypes: MutableSet = HashSet() + val subTypes = mutableSetOf() for (t in possibleSubTypes) { if (!t.isSimilar(t)) { subTypes.add(t) } } subTypes.add(t) - this._type = - TypeManager.getInstance() - .registerType(TypeManager.getInstance().getCommonType(subTypes, this).orElse(t)) + this._type = registerType(getCommonType(subTypes).orElse(t)) val newSubtypes: MutableList = ArrayList() for (s in subTypes) { - if (TypeManager.getInstance().isSupertypeOf(this.type, s, this)) { - newSubtypes.add(TypeManager.getInstance().registerType(s)) + if (isSupertypeOf(this.type, s)) { + newSubtypes.add(registerType(s)) } } possibleSubTypes = newSubtypes @@ -185,13 +180,9 @@ abstract class ValueDeclaration : Declaration(), HasType { override fun setPossibleSubTypes(possibleSubTypes: List, root: MutableList) { var list = possibleSubTypes - list = - list - .filterNot { type -> TypeManager.getInstance().isUnknown(type) } - .distinct() - .toMutableList() - if (!TypeManager.isTypeSystemActive()) { - list.forEach { t -> TypeManager.getInstance().cacheType(this, t) } + list = list.filterNot { type -> type is UnknownType }.distinct().toMutableList() + if (!isTypeSystemActive) { + list.forEach { t -> cacheType(t) } return } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/VariableDeclaration.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/VariableDeclaration.kt index 94b8476f18..3ca71622bb 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/VariableDeclaration.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/VariableDeclaration.kt @@ -29,6 +29,7 @@ import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression import de.fraunhofer.aisec.cpg.graph.statements.expressions.InitializerListExpression import de.fraunhofer.aisec.cpg.graph.types.Type +import de.fraunhofer.aisec.cpg.graph.types.UnknownType import org.apache.commons.lang3.builder.ToStringBuilder import org.neo4j.ogm.annotation.Relationship @@ -81,10 +82,10 @@ class VariableDeclaration : ValueDeclaration(), HasType.TypeListener, HasInitial } override fun typeChanged(src: HasType, root: MutableList, oldType: Type) { - if (!TypeManager.isTypeSystemActive()) { + if (!isTypeSystemActive) { return } - if (!TypeManager.getInstance().isUnknown(type) && src.propagationType == oldType) { + if (type !is UnknownType && src.propagationType == oldType) { return } val previous = type @@ -97,7 +98,7 @@ class VariableDeclaration : ValueDeclaration(), HasType.TypeListener, HasInitial // otherwise it can be ignored once we have a type if (isArray) { src.type - } else if (!TypeManager.getInstance().isUnknown(type)) { + } else if (type !is UnknownType) { return } else { src.type.dereference() @@ -112,7 +113,7 @@ class VariableDeclaration : ValueDeclaration(), HasType.TypeListener, HasInitial } override fun possibleSubTypesChanged(src: HasType, root: MutableList) { - if (!TypeManager.isTypeSystemActive()) { + if (!isTypeSystemActive) { return } val subTypes: MutableList = ArrayList(possibleSubTypes) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ArrayCreationExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ArrayCreationExpression.kt index 395db86cdc..258a9e9251 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ArrayCreationExpression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ArrayCreationExpression.kt @@ -27,11 +27,11 @@ package de.fraunhofer.aisec.cpg.graph.statements.expressions import de.fraunhofer.aisec.cpg.graph.AST import de.fraunhofer.aisec.cpg.graph.HasType -import de.fraunhofer.aisec.cpg.graph.TypeManager import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.propertyEqualsList import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdgeDelegate +import de.fraunhofer.aisec.cpg.graph.isTypeSystemActive import de.fraunhofer.aisec.cpg.graph.types.Type import java.util.* import org.neo4j.ogm.annotation.Relationship @@ -82,7 +82,7 @@ class ArrayCreationExpression : Expression(), HasType.TypeListener { override fun hashCode() = Objects.hash(super.hashCode(), initializer, dimensions) override fun typeChanged(src: HasType, root: MutableList, oldType: Type) { - if (!TypeManager.isTypeSystemActive()) { + if (!isTypeSystemActive) { return } val previous = type @@ -93,7 +93,7 @@ class ArrayCreationExpression : Expression(), HasType.TypeListener { } override fun possibleSubTypesChanged(src: HasType, root: MutableList) { - if (!TypeManager.isTypeSystemActive()) { + if (!isTypeSystemActive) { return } val subTypes: MutableList = ArrayList(possibleSubTypes) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ArraySubscriptionExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ArraySubscriptionExpression.kt index 0485da095c..7cc3d05d6c 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ArraySubscriptionExpression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ArraySubscriptionExpression.kt @@ -76,7 +76,7 @@ class ArraySubscriptionExpression : Expression(), HasType.TypeListener, HasBase } override fun typeChanged(src: HasType, root: MutableList, oldType: Type) { - if (!TypeManager.isTypeSystemActive()) { + if (!isTypeSystemActive) { return } val previous = type @@ -87,7 +87,7 @@ class ArraySubscriptionExpression : Expression(), HasType.TypeListener, HasBase } override fun possibleSubTypesChanged(src: HasType, root: MutableList) { - if (!TypeManager.isTypeSystemActive()) { + if (!isTypeSystemActive) { return } val subTypes: MutableList = ArrayList(possibleSubTypes) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/AssignExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/AssignExpression.kt index 18a1867162..e240f0e67e 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/AssignExpression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/AssignExpression.kt @@ -113,7 +113,7 @@ class AssignExpression : Expression(), AssignmentHolder, HasType.TypeListener { override var declarations = mutableListOf() override fun typeChanged(src: HasType, root: MutableList, oldType: Type) { - if (!TypeManager.isTypeSystemActive()) { + if (!isTypeSystemActive) { return } @@ -138,7 +138,7 @@ class AssignExpression : Expression(), AssignmentHolder, HasType.TypeListener { } override fun possibleSubTypesChanged(src: HasType, root: MutableList) { - if (!TypeManager.isTypeSystemActive()) { + if (!isTypeSystemActive) { return } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/BinaryOperator.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/BinaryOperator.kt index cf6e13ca5f..c3185f0954 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/BinaryOperator.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/BinaryOperator.kt @@ -125,7 +125,7 @@ class BinaryOperator : } override fun typeChanged(src: HasType, root: MutableList, oldType: Type) { - if (!TypeManager.isTypeSystemActive()) { + if (!isTypeSystemActive) { return } val previous = type @@ -164,7 +164,7 @@ class BinaryOperator : } override fun possibleSubTypesChanged(src: HasType, root: MutableList) { - if (!TypeManager.isTypeSystemActive()) { + if (!isTypeSystemActive) { return } val subTypes: MutableList = ArrayList(possibleSubTypes) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/CallExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/CallExpression.kt index d31602de2d..4a434eb14d 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/CallExpression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/CallExpression.kt @@ -267,7 +267,7 @@ open class CallExpression : Expression(), HasType.TypeListener, SecondaryTypeEdg } override fun typeChanged(src: HasType, root: MutableList, oldType: Type) { - if (!TypeManager.isTypeSystemActive()) { + if (!isTypeSystemActive) { return } @@ -288,7 +288,7 @@ open class CallExpression : Expression(), HasType.TypeListener, SecondaryTypeEdg null } val alternative = if (types.isNotEmpty()) types[0] else newUnknownType() - val commonType = TypeManager.getInstance().getCommonType(types, this).orElse(alternative) + val commonType = getCommonType(types).orElse(alternative) val subTypes: MutableList = ArrayList(possibleSubTypes) subTypes.remove(oldType) @@ -301,7 +301,7 @@ open class CallExpression : Expression(), HasType.TypeListener, SecondaryTypeEdg } override fun possibleSubTypesChanged(src: HasType, root: MutableList) { - if (!TypeManager.isTypeSystemActive()) { + if (!isTypeSystemActive) { return } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/CastExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/CastExpression.kt index 13dc0014c1..30024d3bd3 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/CastExpression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/CastExpression.kt @@ -46,11 +46,11 @@ class CastExpression : Expression(), HasType.TypeListener { } override fun typeChanged(src: HasType, root: MutableList, oldType: Type) { - if (!TypeManager.isTypeSystemActive()) { + if (!isTypeSystemActive) { return } val previous = type - if (TypeManager.getInstance().isSupertypeOf(castType, src.propagationType, this)) { + if (isSupertypeOf(castType, src.propagationType)) { setType(src.propagationType, root) } else { resetTypes(castType) @@ -61,7 +61,7 @@ class CastExpression : Expression(), HasType.TypeListener { } override fun possibleSubTypesChanged(src: HasType, root: MutableList) { - if (!TypeManager.isTypeSystemActive()) { + if (!isTypeSystemActive) { return } setPossibleSubTypes(ArrayList(src.possibleSubTypes), root) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ConditionalExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ConditionalExpression.kt index 2ff4a459c7..e53e5539aa 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ConditionalExpression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ConditionalExpression.kt @@ -55,7 +55,7 @@ class ConditionalExpression : Expression(), HasType.TypeListener, ArgumentHolder } override fun typeChanged(src: HasType, root: MutableList, oldType: Type) { - if (!TypeManager.isTypeSystemActive()) { + if (!isTypeSystemActive) { return } val previous = type @@ -68,7 +68,7 @@ class ConditionalExpression : Expression(), HasType.TypeListener, ArgumentHolder subTypes.remove(oldType) subTypes.addAll(types) val alternative = if (types.isNotEmpty()) types[0] else newUnknownType() - setType(TypeManager.getInstance().getCommonType(types, this).orElse(alternative), root) + setType(getCommonType(types).orElse(alternative), root) setPossibleSubTypes(subTypes, root) if (previous != type) { type.typeOrigin = Type.Origin.DATAFLOW @@ -76,7 +76,7 @@ class ConditionalExpression : Expression(), HasType.TypeListener, ArgumentHolder } override fun possibleSubTypesChanged(src: HasType, root: MutableList) { - if (!TypeManager.isTypeSystemActive()) { + if (!isTypeSystemActive) { return } val subTypes: MutableList = ArrayList(possibleSubTypes) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ConstructExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ConstructExpression.kt index ef53ed66a9..421b0730da 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ConstructExpression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ConstructExpression.kt @@ -28,11 +28,11 @@ package de.fraunhofer.aisec.cpg.graph.statements.expressions import de.fraunhofer.aisec.cpg.PopulatedByPass import de.fraunhofer.aisec.cpg.graph.AST import de.fraunhofer.aisec.cpg.graph.HasType -import de.fraunhofer.aisec.cpg.graph.TypeManager import de.fraunhofer.aisec.cpg.graph.declarations.* +import de.fraunhofer.aisec.cpg.graph.isTypeSystemActive +import de.fraunhofer.aisec.cpg.graph.parseType import de.fraunhofer.aisec.cpg.graph.types.FunctionType import de.fraunhofer.aisec.cpg.graph.types.Type -import de.fraunhofer.aisec.cpg.graph.types.TypeParser import de.fraunhofer.aisec.cpg.graph.types.UnknownType import de.fraunhofer.aisec.cpg.passes.CallResolver import java.util.* @@ -80,7 +80,7 @@ class ConstructExpression : CallExpression(), HasType.TypeListener { set(value) { field = value if (value != null && this.type is UnknownType) { - type = TypeParser.createFrom(value.name, language) + type = parseType(value.name) } } @@ -107,7 +107,7 @@ class ConstructExpression : CallExpression(), HasType.TypeListener { * work around the first case in a different way. */ override fun typeChanged(src: HasType, root: MutableList, oldType: Type) { - if (!TypeManager.isTypeSystemActive()) { + if (!isTypeSystemActive) { return } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/DeclaredReferenceExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/DeclaredReferenceExpression.kt index 3c9b839fc8..10c72885bd 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/DeclaredReferenceExpression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/DeclaredReferenceExpression.kt @@ -28,10 +28,10 @@ package de.fraunhofer.aisec.cpg.graph.statements.expressions import de.fraunhofer.aisec.cpg.PopulatedByPass import de.fraunhofer.aisec.cpg.graph.AccessValues import de.fraunhofer.aisec.cpg.graph.HasType -import de.fraunhofer.aisec.cpg.graph.TypeManager import de.fraunhofer.aisec.cpg.graph.declarations.Declaration import de.fraunhofer.aisec.cpg.graph.declarations.ValueDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration +import de.fraunhofer.aisec.cpg.graph.isTypeSystemActive import de.fraunhofer.aisec.cpg.graph.types.Type import de.fraunhofer.aisec.cpg.passes.VariableUsageResolver import java.util.* @@ -100,7 +100,7 @@ open class DeclaredReferenceExpression : Expression(), HasType.TypeListener { } override fun typeChanged(src: HasType, root: MutableList, oldType: Type) { - if (!TypeManager.isTypeSystemActive()) { + if (!isTypeSystemActive) { return } val previous = type @@ -111,7 +111,7 @@ open class DeclaredReferenceExpression : Expression(), HasType.TypeListener { } override fun possibleSubTypesChanged(src: HasType, root: MutableList) { - if (!TypeManager.isTypeSystemActive()) { + if (!isTypeSystemActive) { return } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/Expression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/Expression.kt index 5ed5e3ecb1..28f7c18f49 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/Expression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/Expression.kt @@ -30,6 +30,7 @@ import de.fraunhofer.aisec.cpg.graph.statements.Statement import de.fraunhofer.aisec.cpg.graph.types.FunctionPointerType import de.fraunhofer.aisec.cpg.graph.types.ReferenceType import de.fraunhofer.aisec.cpg.graph.types.Type +import de.fraunhofer.aisec.cpg.graph.types.UnknownType import java.util.* import org.apache.commons.lang3.builder.ToStringBuilder import org.neo4j.ogm.annotation.Relationship @@ -54,13 +55,13 @@ abstract class Expression : Statement(), HasType { override var type: Type get() { val result: Type = - if (TypeManager.isTypeSystemActive()) { + if (isTypeSystemActive) { _type } else { - TypeManager.getInstance() - .typeCache - .computeIfAbsent(this) { mutableListOf() } - .firstOrNull() + ctx?.typeManager + ?.typeCache + ?.computeIfAbsent(this) { mutableListOf() } + ?.firstOrNull() ?: newUnknownType() } return result @@ -74,8 +75,8 @@ abstract class Expression : Statement(), HasType { override var possibleSubTypes: List get() { - return if (!TypeManager.isTypeSystemActive()) { - TypeManager.getInstance().typeCache.getOrDefault(this, emptyList()) + return if (!isTypeSystemActive) { + ctx?.typeManager?.typeCache?.getOrDefault(this, emptyList()) ?: listOf() } else _possibleSubTypes } set(value) { @@ -98,9 +99,9 @@ abstract class Expression : Statement(), HasType { // TODO Document this method. It is called very often (potentially for each AST node) and // performs less than optimal. - if (!TypeManager.isTypeSystemActive()) { + if (!isTypeSystemActive) { this._type = t - TypeManager.getInstance().cacheType(this, t) + cacheType(t) return } @@ -112,8 +113,8 @@ abstract class Expression : Statement(), HasType { // do. if ( r.contains(this) || - TypeManager.getInstance().isUnknown(t) || - TypeManager.getInstance().stopPropagation(this.type, t) || + t is UnknownType || + stopPropagation(this.type, t) || (this.type is FunctionPointerType && t !is FunctionPointerType) ) { return @@ -135,16 +136,14 @@ abstract class Expression : Statement(), HasType { subTypes.add(t) // Probably tries to get something like the best supertype of all possible subtypes. - this._type = - TypeManager.getInstance() - .registerType(TypeManager.getInstance().getCommonType(subTypes, this).orElse(t)) + this._type = registerType(getCommonType(subTypes).orElse(t)) // TODO: Why do we need this loop? Shouldn't the condition be ensured by the previous line // getting the common type?? val newSubtypes = mutableListOf() for (s in subTypes) { - if (TypeManager.getInstance().isSupertypeOf(this.type, s, this)) { - newSubtypes.add(TypeManager.getInstance().registerType(s)) + if (isSupertypeOf(this.type, s)) { + newSubtypes.add(registerType(s)) } } @@ -168,13 +167,9 @@ abstract class Expression : Statement(), HasType { override fun setPossibleSubTypes(possibleSubTypes: List, root: MutableList) { var list = possibleSubTypes - list = - list - .filterNot { type -> TypeManager.getInstance().isUnknown(type) } - .distinct() - .toMutableList() - if (!TypeManager.isTypeSystemActive()) { - list.forEach { t -> TypeManager.getInstance().cacheType(this, t) } + list = list.filterNot { type -> type is UnknownType }.distinct().toMutableList() + if (!isTypeSystemActive) { + list.forEach { t -> cacheType(t) } return } if (root.contains(this)) { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ExpressionList.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ExpressionList.kt index a943e759b0..a82869e10d 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ExpressionList.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ExpressionList.kt @@ -27,12 +27,12 @@ package de.fraunhofer.aisec.cpg.graph.statements.expressions import de.fraunhofer.aisec.cpg.graph.AST import de.fraunhofer.aisec.cpg.graph.HasType -import de.fraunhofer.aisec.cpg.graph.TypeManager import de.fraunhofer.aisec.cpg.graph.edge.Properties import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.propertyEqualsList import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.transformIntoOutgoingPropertyEdgeList import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.unwrap +import de.fraunhofer.aisec.cpg.graph.isTypeSystemActive import de.fraunhofer.aisec.cpg.graph.statements.Statement import de.fraunhofer.aisec.cpg.graph.types.Type import java.util.* @@ -76,7 +76,7 @@ class ExpressionList : Expression(), HasType.TypeListener { } override fun typeChanged(src: HasType, root: MutableList, oldType: Type) { - if (!TypeManager.isTypeSystemActive()) { + if (!isTypeSystemActive) { return } val previous = type @@ -88,7 +88,7 @@ class ExpressionList : Expression(), HasType.TypeListener { } override fun possibleSubTypesChanged(src: HasType, root: MutableList) { - if (!TypeManager.isTypeSystemActive()) { + if (!isTypeSystemActive) { return } setPossibleSubTypes(ArrayList(src.possibleSubTypes), root) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/InitializerListExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/InitializerListExpression.kt index 224cdb92c1..c7c02ddd28 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/InitializerListExpression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/InitializerListExpression.kt @@ -25,15 +25,13 @@ */ package de.fraunhofer.aisec.cpg.graph.statements.expressions -import de.fraunhofer.aisec.cpg.graph.AST -import de.fraunhofer.aisec.cpg.graph.HasType -import de.fraunhofer.aisec.cpg.graph.TypeManager +import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.propertyEqualsList import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdgeDelegate -import de.fraunhofer.aisec.cpg.graph.newUnknownType import de.fraunhofer.aisec.cpg.graph.types.PointerType.PointerOrigin import de.fraunhofer.aisec.cpg.graph.types.Type +import de.fraunhofer.aisec.cpg.graph.types.UnknownType import java.util.* import org.apache.commons.lang3.builder.ToStringBuilder import org.neo4j.ogm.annotation.Relationship @@ -60,10 +58,10 @@ class InitializerListExpression : Expression(), HasType.TypeListener { var initializers by PropertyEdgeDelegate(InitializerListExpression::initializerEdges) override fun typeChanged(src: HasType, root: MutableList, oldType: Type) { - if (!TypeManager.isTypeSystemActive()) { + if (!isTypeSystemActive) { return } - if (!TypeManager.getInstance().isUnknown(type) && src.propagationType == oldType) { + if (type !is UnknownType && src.propagationType == oldType) { return } val previous = type @@ -71,14 +69,9 @@ class InitializerListExpression : Expression(), HasType.TypeListener { val subTypes: MutableList if (initializers.contains(src)) { val types = - initializers - .map { - TypeManager.getInstance() - .registerType(it.type.reference(PointerOrigin.ARRAY)) - } - .toSet() + initializers.map { registerType(it.type.reference(PointerOrigin.ARRAY)) }.toSet() val alternative = if (types.isNotEmpty()) types.iterator().next() else newUnknownType() - newType = TypeManager.getInstance().getCommonType(types, this).orElse(alternative) + newType = getCommonType(types).orElse(alternative) subTypes = ArrayList(possibleSubTypes) subTypes.remove(oldType) subTypes.addAll(types) @@ -96,7 +89,7 @@ class InitializerListExpression : Expression(), HasType.TypeListener { } override fun possibleSubTypesChanged(src: HasType, root: MutableList) { - if (!TypeManager.isTypeSystemActive()) { + if (!isTypeSystemActive) { return } val subTypes: MutableList = ArrayList(possibleSubTypes) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/LambdaExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/LambdaExpression.kt index c866e25b2b..08a1fd0bbd 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/LambdaExpression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/LambdaExpression.kt @@ -27,11 +27,12 @@ package de.fraunhofer.aisec.cpg.graph.statements.expressions import de.fraunhofer.aisec.cpg.graph.AST import de.fraunhofer.aisec.cpg.graph.HasType -import de.fraunhofer.aisec.cpg.graph.TypeManager import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.ValueDeclaration +import de.fraunhofer.aisec.cpg.graph.isTypeSystemActive import de.fraunhofer.aisec.cpg.graph.types.FunctionPointerType import de.fraunhofer.aisec.cpg.graph.types.Type +import de.fraunhofer.aisec.cpg.graph.types.UnknownType /** * This expression denotes the usage of an anonymous / lambda function. It connects the inner @@ -62,11 +63,11 @@ class LambdaExpression : Expression(), HasType.TypeListener { } override fun typeChanged(src: HasType, root: MutableList, oldType: Type) { - if (!TypeManager.isTypeSystemActive()) { + if (!isTypeSystemActive) { return } - if (!TypeManager.getInstance().isUnknown(type) && src.propagationType == oldType) { + if (type !is UnknownType && src.propagationType == oldType) { return } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/UnaryOperator.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/UnaryOperator.kt index 3c1c44db28..be71e58f86 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/UnaryOperator.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/UnaryOperator.kt @@ -28,7 +28,7 @@ package de.fraunhofer.aisec.cpg.graph.statements.expressions import de.fraunhofer.aisec.cpg.graph.AST import de.fraunhofer.aisec.cpg.graph.AccessValues import de.fraunhofer.aisec.cpg.graph.HasType -import de.fraunhofer.aisec.cpg.graph.TypeManager +import de.fraunhofer.aisec.cpg.graph.isTypeSystemActive import de.fraunhofer.aisec.cpg.graph.types.PointerType import de.fraunhofer.aisec.cpg.graph.types.Type import de.fraunhofer.aisec.cpg.helpers.Util.distinctBy @@ -103,7 +103,7 @@ class UnaryOperator : Expression(), HasType.TypeListener { } override fun typeChanged(src: HasType, root: MutableList, oldType: Type) { - if (!TypeManager.isTypeSystemActive()) { + if (!isTypeSystemActive) { return } val previous = type @@ -135,7 +135,7 @@ class UnaryOperator : Expression(), HasType.TypeListener { } override fun possibleSubTypesChanged(src: HasType, root: MutableList) { - if (!TypeManager.isTypeSystemActive()) { + if (!isTypeSystemActive) { return } if (src is HasType.TypeListener && getsDataFromInput(src as HasType.TypeListener)) { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/FunctionType.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/FunctionType.kt index db94a55534..182a2b2d47 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/FunctionType.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/FunctionType.kt @@ -27,9 +27,9 @@ package de.fraunhofer.aisec.cpg.graph.types import de.fraunhofer.aisec.cpg.frontends.Language import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend -import de.fraunhofer.aisec.cpg.graph.TypeManager import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration import de.fraunhofer.aisec.cpg.graph.newUnknownType +import de.fraunhofer.aisec.cpg.graph.registerType /** * A type representing a function. It contains a list of parameters and one or more return types. @@ -80,7 +80,7 @@ constructor( func.language ) - return TypeManager.getInstance().registerType(type) + return func.registerType(type) } } } 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 4d28388065..b8c3081e9c 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 @@ -25,9 +25,11 @@ */ package de.fraunhofer.aisec.cpg.graph +import de.fraunhofer.aisec.cpg.frontends.TranslationException import de.fraunhofer.aisec.cpg.graph.types.Type +import java.util.Optional -interface HasType { +interface HasType : ContextProvider { var type: Type /** @@ -100,3 +102,33 @@ interface HasType { fun updateType(typeState: Collection) } } + +val Node.isTypeSystemActive: Boolean + get() { + return TypeManager.isTypeSystemActive() + } + +fun Node.isSupertypeOf(superType: Type, subType: Type?): Boolean { + val c = ctx ?: throw TranslationException("context not available") + return c.typeManager.isSupertypeOf(superType, subType, this) +} + +fun HasType.cacheType(type: Type) { + val c = ctx ?: throw TranslationException("context not available") + c.typeManager.cacheType(this, type) +} + +fun Node.registerType(type: T): T { + val c = ctx ?: throw TranslationException("context not available") + return c.typeManager.registerType(type) +} + +fun Node.getCommonType(types: Collection): Optional { + val c = ctx ?: throw TranslationException("context not available") + return c.typeManager.getCommonType(types, this.ctx) +} + +fun Node.stopPropagation(type: Type, newType: Type): Boolean { + val c = ctx ?: throw TranslationException("context not available") + return c.typeManager.stopPropagation(type, newType) +} diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CXXCallResolverHelper.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CXXCallResolverHelper.kt index f3285238d5..e58db03f34 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CXXCallResolverHelper.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CXXCallResolverHelper.kt @@ -25,6 +25,7 @@ */ package de.fraunhofer.aisec.cpg.passes +import de.fraunhofer.aisec.cpg.TranslationContext import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.declarations.* import de.fraunhofer.aisec.cpg.graph.statements.expressions.* @@ -44,14 +45,17 @@ import java.util.regex.Pattern fun compatibleSignatures( callSignature: List, functionSignature: List, - provider: ScopeProvider + ctx: TranslationContext ): Boolean { return if (callSignature.size == functionSignature.size) { for (i in callSignature.indices) { if ( callSignature[i]?.isPrimitive != functionSignature[i].isPrimitive && - !TypeManager.getInstance() - .isSupertypeOf(functionSignature[i], callSignature[i], provider) + !ctx.typeManager.isSupertypeOf( + functionSignature[i], + callSignature[i], + ctx.scopeManager + ) ) { return false } @@ -92,7 +96,8 @@ fun getCallSignatureWithDefaults( */ fun resolveWithImplicitCast( call: CallExpression, - initialInvocationCandidates: List + initialInvocationCandidates: List, + ctx: TranslationContext ): List { // Output list for invocationTargets obtaining a valid signature by performing implicit @@ -105,12 +110,13 @@ fun resolveWithImplicitCast( for (functionDeclaration in initialInvocationCandidates) { val callSignature = getCallSignatureWithDefaults(call, functionDeclaration) // Check if the signatures match by implicit casts - if (compatibleSignatures(callSignature, functionDeclaration.signatureTypes, call)) { + if (compatibleSignatures(callSignature, functionDeclaration.signatureTypes, ctx)) { val implicitCastTargets = signatureWithImplicitCastTransformation( getCallSignatureWithDefaults(call, functionDeclaration), call.arguments, - functionDeclaration.signatureTypes + functionDeclaration.signatureTypes, + ctx ) if (implicitCasts == null) { implicitCasts = implicitCastTargets @@ -119,7 +125,7 @@ fun resolveWithImplicitCast( // to the same target type checkMostCommonImplicitCast(implicitCasts, implicitCastTargets) } - if (compatibleSignatures(call.signature, functionDeclaration.signatureTypes, call)) { + if (compatibleSignatures(call.signature, functionDeclaration.signatureTypes, ctx)) { invocationTargetsWithImplicitCast.add(functionDeclaration) } else { invocationTargetsWithImplicitCastAndDefaults.add(functionDeclaration) @@ -255,7 +261,8 @@ fun shouldContinueSearchInParent(recordDeclaration: RecordDeclaration?, name: St */ fun resolveConstructorWithImplicitCast( constructExpression: ConstructExpression, - recordDeclaration: RecordDeclaration + recordDeclaration: RecordDeclaration, + ctx: TranslationContext ): ConstructorDeclaration? { for (constructorDeclaration in recordDeclaration.constructors) { val workingSignature = mutableListOf(*constructExpression.signature.toTypedArray()) @@ -272,29 +279,27 @@ fun resolveConstructorWithImplicitCast( compatibleSignatures( constructExpression.signature, constructorDeclaration.signatureTypes, - constructExpression + ctx ) ) { val implicitCasts = signatureWithImplicitCastTransformation( constructExpression.signature, constructExpression.arguments, - constructorDeclaration.signatureTypes + constructorDeclaration.signatureTypes, + ctx ) applyImplicitCastToArguments(constructExpression, implicitCasts) return constructorDeclaration } else if ( - compatibleSignatures( - workingSignature, - constructorDeclaration.signatureTypes, - constructExpression - ) + compatibleSignatures(workingSignature, constructorDeclaration.signatureTypes, ctx) ) { val implicitCasts = signatureWithImplicitCastTransformation( getCallSignatureWithDefaults(constructExpression, constructorDeclaration), constructExpression.arguments, - constructorDeclaration.signatureTypes + constructorDeclaration.signatureTypes, + ctx ) applyImplicitCastToArguments(constructExpression, implicitCasts) return constructorDeclaration @@ -327,7 +332,8 @@ fun applyTemplateInstantiation( function: FunctionDeclaration, initializationSignature: Map, initializationType: Map, - orderedInitializationSignature: Map + orderedInitializationSignature: Map, + ctx: TranslationContext ): List { val templateInstantiationParameters = mutableListOf(*orderedInitializationSignature.keys.toTypedArray()) @@ -357,7 +363,8 @@ fun applyTemplateInstantiation( signatureWithImplicitCastTransformation( templateCallSignature, templateCall.arguments, - templateFunctionSignature + templateFunctionSignature, + ctx ) for (i in callSignatureImplicit.indices) { val cast = callSignatureImplicit[i] @@ -396,7 +403,8 @@ fun applyTemplateInstantiation( fun signatureWithImplicitCastTransformation( callSignature: List, arguments: List, - functionSignature: List + functionSignature: List, + ctx: TranslationContext ): MutableList { val implicitCasts = mutableListOf() if (callSignature.size != functionSignature.size) return implicitCasts @@ -406,6 +414,7 @@ fun signatureWithImplicitCastTransformation( val funcType = functionSignature[i] if (callType?.isPrimitive == true && funcType.isPrimitive && callType != funcType) { val implicitCast = CastExpression() + implicitCast.ctx = ctx implicitCast.isImplicit = true implicitCast.castType = funcType implicitCast.language = funcType.language @@ -466,7 +475,8 @@ fun getTemplateInitializationSignature( templateCall: CallExpression, instantiationType: MutableMap, orderedInitializationSignature: MutableMap, - explicitInstantiated: MutableList + explicitInstantiated: MutableList, + ctx: TranslationContext ): Map? { // Construct Signature val signature = @@ -475,7 +485,8 @@ fun getTemplateInitializationSignature( templateCall, instantiationType, orderedInitializationSignature, - explicitInstantiated + explicitInstantiated, + ctx ) ?: return null val parameterizedTypeResolution = getParameterizedSignaturesFromInitialization(signature) @@ -528,14 +539,15 @@ fun constructTemplateInitializationSignatureFromTemplateParameters( templateCall: CallExpression, instantiationType: MutableMap, orderedInitializationSignature: MutableMap, - explicitInstantiated: MutableList + explicitInstantiated: MutableList, + ctx: TranslationContext ): MutableMap? { val instantiationSignature: MutableMap = HashMap() for (i in functionTemplateDeclaration.parameters.indices) { if (i < templateCall.templateParameters.size) { val callParameter = templateCall.templateParameters[i] val templateParameter = functionTemplateDeclaration.parameters[i] - if (isInstantiated(callParameter, templateParameter)) { + if (isInstantiated(callParameter, templateParameter, ctx.typeManager)) { instantiationSignature[templateParameter] = callParameter instantiationType[callParameter] = TemplateDeclaration.TemplateInitialization.EXPLICIT @@ -570,7 +582,11 @@ fun constructTemplateInitializationSignatureFromTemplateParameters( * callParameterArg must be an Expression and its type must match the type of the * ParamVariableDeclaration (same type or subtype) => returns true Otherwise return false */ -fun isInstantiated(callParameterArg: Node, templateParameter: Declaration?): Boolean { +fun isInstantiated( + callParameterArg: Node, + templateParameter: Declaration?, + typeManager: TypeManager +): Boolean { var callParameter = callParameterArg if (callParameter is TypeExpression) { callParameter = callParameter.type @@ -579,8 +595,7 @@ fun isInstantiated(callParameterArg: Node, templateParameter: Declaration?): Boo callParameter is ObjectType } else if (callParameter is Expression && templateParameter is ParamVariableDeclaration) { callParameter.type == templateParameter.type || - TypeManager.getInstance() - .isSupertypeOf(templateParameter.type, callParameter.type, callParameterArg) + typeManager.isSupertypeOf(templateParameter.type, callParameter.type, callParameterArg) } else { false } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolver.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolver.kt index 2db61abddd..5381390552 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolver.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolver.kt @@ -25,8 +25,7 @@ */ package de.fraunhofer.aisec.cpg.passes -import de.fraunhofer.aisec.cpg.ScopeManager -import de.fraunhofer.aisec.cpg.TranslationConfiguration +import de.fraunhofer.aisec.cpg.TranslationContext import de.fraunhofer.aisec.cpg.frontends.HasComplexCallResolution import de.fraunhofer.aisec.cpg.frontends.HasDefaultArguments import de.fraunhofer.aisec.cpg.frontends.HasSuperClasses @@ -65,8 +64,7 @@ import org.slf4j.LoggerFactory * This pass should NOT use any DFG edges because they are computed / adjusted in a later stage. */ @DependsOn(VariableUsageResolver::class) -open class CallResolver(config: TranslationConfiguration, scopeManager: ScopeManager) : - SymbolResolverPass(config, scopeManager) { +open class CallResolver(ctx: TranslationContext) : SymbolResolverPass(ctx) { /** * This seems to be a map between function declarations (more likely method declarations) and * their parent record (more accurately their type). Seems to be only used by @@ -104,8 +102,7 @@ open class CallResolver(config: TranslationConfiguration, scopeManager: ScopeMan private fun registerMethods(currentClass: RecordDeclaration?, currentNode: Node?) { if (currentNode is MethodDeclaration && currentClass != null) { - containingType[currentNode] = - TypeParser.createFrom(currentClass.name, currentClass.language) + containingType[currentNode] = currentNode.parseType(currentClass.name) } } @@ -185,7 +182,7 @@ open class CallResolver(config: TranslationConfiguration, scopeManager: ScopeMan curClass, call, true, - scopeManager, + ctx, currentTU ) @@ -212,10 +209,8 @@ open class CallResolver(config: TranslationConfiguration, scopeManager: ScopeMan // unit. Nothing else is allowed (fow now) val func = when (val start = scope?.astNode) { - is TranslationUnitDeclaration -> - start.inferFunction(call, scopeManager = scopeManager) - is NamespaceDeclaration -> - start.inferFunction(call, scopeManager = scopeManager) + is TranslationUnitDeclaration -> start.inferFunction(call, ctx = ctx) + is NamespaceDeclaration -> start.inferFunction(call, ctx = ctx) else -> null } @@ -306,7 +301,7 @@ open class CallResolver(config: TranslationConfiguration, scopeManager: ScopeMan if (language is HasComplexCallResolution) { // Handle CXX normal call resolution externally, otherwise it leads to increased // complexity - language.refineNormalCallResolution(call, scopeManager, currentTU) + language.refineNormalCallResolution(call, ctx, currentTU) } else { scopeManager.resolveFunction(call).toMutableList() } @@ -385,7 +380,7 @@ open class CallResolver(config: TranslationConfiguration, scopeManager: ScopeMan curClass, possibleContainingTypes, call, - scopeManager, + ctx, currentTU, this ) @@ -409,13 +404,13 @@ open class CallResolver(config: TranslationConfiguration, scopeManager: ScopeMan .mapNotNull { var record = recordMap[it.root.name] if (record == null && config?.inferenceConfiguration?.inferRecords == true) { - record = it.startInference(scopeManager).inferRecordDeclaration(it, currentTU) + record = it.startInference(ctx).inferRecordDeclaration(it, currentTU) // update the record map if (record != null) it.root.name.let { name -> recordMap[name] = record } } record } - .map { record -> record.inferMethod(call, scopeManager = scopeManager) } + .map { record -> record.inferMethod(call, ctx = ctx) } } /** @@ -522,7 +517,8 @@ open class CallResolver(config: TranslationConfiguration, scopeManager: ScopeMan (call.language as HasComplexCallResolution).refineInvocationCandidatesFromRecord( recordDeclaration, call, - namePattern + namePattern, + ctx ) } else { recordDeclaration.methods.filter { @@ -612,12 +608,12 @@ open class CallResolver(config: TranslationConfiguration, scopeManager: ScopeMan // If we don't find any candidate and our current language is c/c++ we check if there is // a candidate with an implicit cast constructorCandidate = - resolveConstructorWithImplicitCast(constructExpression, recordDeclaration) + resolveConstructorWithImplicitCast(constructExpression, recordDeclaration, ctx) } return constructorCandidate ?: recordDeclaration - .startInference(scopeManager) + .startInference(ctx) .createInferredConstructor(constructExpression.signature) } @@ -626,7 +622,7 @@ open class CallResolver(config: TranslationConfiguration, scopeManager: ScopeMan recordDeclaration: RecordDeclaration ): ConstructorDeclaration { return recordDeclaration.constructors.firstOrNull { it.hasSignature(signature) } - ?: recordDeclaration.startInference(scopeManager).createInferredConstructor(signature) + ?: recordDeclaration.startInference(ctx).createInferredConstructor(signature) } companion object { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt index 945cef99c6..d800dbe8b8 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt @@ -25,8 +25,7 @@ */ package de.fraunhofer.aisec.cpg.passes -import de.fraunhofer.aisec.cpg.ScopeManager -import de.fraunhofer.aisec.cpg.TranslationConfiguration +import de.fraunhofer.aisec.cpg.TranslationContext import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.declarations.* import de.fraunhofer.aisec.cpg.graph.edge.Properties @@ -46,10 +45,8 @@ import de.fraunhofer.aisec.cpg.passes.order.DependsOn */ @DependsOn(EvaluationOrderGraphPass::class) @DependsOn(DFGPass::class) -open class ControlFlowSensitiveDFGPass( - config: TranslationConfiguration, - scopeManager: ScopeManager, -) : TranslationUnitPass(config, scopeManager) { +open class ControlFlowSensitiveDFGPass(ctx: TranslationContext) : TranslationUnitPass(ctx) { + override fun cleanup() { // Nothing to do } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/DFGPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/DFGPass.kt index 6a5a223be0..d1d3ec7202 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/DFGPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/DFGPass.kt @@ -25,8 +25,7 @@ */ package de.fraunhofer.aisec.cpg.passes -import de.fraunhofer.aisec.cpg.ScopeManager -import de.fraunhofer.aisec.cpg.TranslationConfiguration +import de.fraunhofer.aisec.cpg.TranslationContext import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.declarations.FieldDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration @@ -40,8 +39,7 @@ import de.fraunhofer.aisec.cpg.passes.order.DependsOn /** Adds the DFG edges for various types of nodes. */ @DependsOn(VariableUsageResolver::class) @DependsOn(CallResolver::class) -class DFGPass(config: TranslationConfiguration, scopeManager: ScopeManager) : - ComponentPass(config, scopeManager) { +class DFGPass(ctx: TranslationContext) : ComponentPass(ctx) { override fun accept(component: Component) { val inferDfgForUnresolvedCalls = config.inferenceConfiguration.inferDfgForUnresolvedSymbols val walker = IterativeGraphWalker() diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EdgeCachePass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EdgeCachePass.kt index daf12316d3..88ebf574b6 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EdgeCachePass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EdgeCachePass.kt @@ -25,8 +25,7 @@ */ package de.fraunhofer.aisec.cpg.passes -import de.fraunhofer.aisec.cpg.ScopeManager -import de.fraunhofer.aisec.cpg.TranslationConfiguration +import de.fraunhofer.aisec.cpg.TranslationContext import de.fraunhofer.aisec.cpg.graph.Component import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker @@ -83,8 +82,7 @@ object Edges { * * The cache itself is stored in the [Edges] object. */ -class EdgeCachePass(config: TranslationConfiguration, scopeManager: ScopeManager) : - ComponentPass(config, scopeManager) { +class EdgeCachePass(ctx: TranslationContext) : ComponentPass(ctx) { override fun accept(component: Component) { Edges.clear() diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt index 7b13a853a4..9c837fcb1c 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt @@ -25,13 +25,11 @@ */ package de.fraunhofer.aisec.cpg.passes -import de.fraunhofer.aisec.cpg.ScopeManager -import de.fraunhofer.aisec.cpg.TranslationConfiguration +import de.fraunhofer.aisec.cpg.TranslationContext import de.fraunhofer.aisec.cpg.frontends.HasShortCircuitOperators import de.fraunhofer.aisec.cpg.frontends.ProcessedListener import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.StatementHolder -import de.fraunhofer.aisec.cpg.graph.TypeManager import de.fraunhofer.aisec.cpg.graph.declarations.* import de.fraunhofer.aisec.cpg.graph.edge.Properties import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge @@ -71,8 +69,7 @@ import org.slf4j.LoggerFactory */ @Suppress("MemberVisibilityCanBePrivate") @DependsOn(CallResolver::class) -open class EvaluationOrderGraphPass(config: TranslationConfiguration, scopeManager: ScopeManager) : - TranslationUnitPass(config, scopeManager) { +open class EvaluationOrderGraphPass(ctx: TranslationContext) : TranslationUnitPass(ctx) { protected val map = mutableMapOf, (Node) -> Unit>() private var currentPredecessors = mutableListOf() private val nextEdgeProperties = EnumMap(Properties::class.java) @@ -580,9 +577,7 @@ open class EvaluationOrderGraphPass(config: TranslationConfiguration, scopeManag val catchParam = catchClause.parameter if (catchParam == null) { // e.g. catch (...) currentPredecessors.addAll(eogEdges) - } else if ( - TypeManager.getInstance().isSupertypeOf(catchParam.type, throwType, node) - ) { + } else if (typeManager.isSupertypeOf(catchParam.type, throwType, node)) { currentPredecessors.addAll(eogEdges) toRemove.add(throwType) } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/FilenameMapper.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/FilenameMapper.kt index d715c99467..03746a283b 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/FilenameMapper.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/FilenameMapper.kt @@ -25,8 +25,7 @@ */ package de.fraunhofer.aisec.cpg.passes -import de.fraunhofer.aisec.cpg.ScopeManager -import de.fraunhofer.aisec.cpg.TranslationConfiguration +import de.fraunhofer.aisec.cpg.TranslationContext import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration import de.fraunhofer.aisec.cpg.passes.order.ExecuteLast @@ -34,8 +33,7 @@ import de.fraunhofer.aisec.cpg.processing.IVisitor import de.fraunhofer.aisec.cpg.processing.strategy.Strategy @ExecuteLast -class FilenameMapper(config: TranslationConfiguration, scopeManager: ScopeManager) : - TranslationUnitPass(config, scopeManager) { +class FilenameMapper(ctx: TranslationContext) : TranslationUnitPass(ctx) { override fun accept(tu: TranslationUnitDeclaration) { val file = tu.name.toString() tu.file = file diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/FunctionPointerCallResolver.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/FunctionPointerCallResolver.kt index 4951f52ada..cc50cdff5b 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/FunctionPointerCallResolver.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/FunctionPointerCallResolver.kt @@ -25,8 +25,7 @@ */ package de.fraunhofer.aisec.cpg.passes -import de.fraunhofer.aisec.cpg.ScopeManager -import de.fraunhofer.aisec.cpg.TranslationConfiguration +import de.fraunhofer.aisec.cpg.TranslationContext import de.fraunhofer.aisec.cpg.frontends.cpp.CXXLanguageFrontend import de.fraunhofer.aisec.cpg.graph.Component import de.fraunhofer.aisec.cpg.graph.Node @@ -57,8 +56,7 @@ import java.util.function.Consumer @DependsOn(CallResolver::class) @DependsOn(DFGPass::class) @RequiredFrontend(CXXLanguageFrontend::class) -class FunctionPointerCallResolver(config: TranslationConfiguration, scopeManager: ScopeManager) : - ComponentPass(config, scopeManager) { +class FunctionPointerCallResolver(ctx: TranslationContext) : ComponentPass(ctx) { private lateinit var walker: ScopedWalker private var inferDfgForUnresolvedCalls = false @@ -152,7 +150,7 @@ class FunctionPointerCallResolver(config: TranslationConfiguration, scopeManager call.invokes = invocationCandidates // We have to update the dfg edges because this call could now be resolved (which was not // the case before). - DFGPass(config, scopeManager).handleCallExpression(call, inferDfgForUnresolvedCalls) + DFGPass(ctx).handleCallExpression(call, inferDfgForUnresolvedCalls) } override fun cleanup() { 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 fb3c4360ae..65ae374e2e 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 @@ -25,13 +25,9 @@ */ package de.fraunhofer.aisec.cpg.passes -import de.fraunhofer.aisec.cpg.ScopeManager -import de.fraunhofer.aisec.cpg.TranslationConfiguration -import de.fraunhofer.aisec.cpg.graph.Component -import de.fraunhofer.aisec.cpg.graph.Node +import de.fraunhofer.aisec.cpg.TranslationContext +import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.declarations.* -import de.fraunhofer.aisec.cpg.graph.newFieldDeclaration -import de.fraunhofer.aisec.cpg.graph.newMethodDeclaration import de.fraunhofer.aisec.cpg.graph.types.UnknownType import de.fraunhofer.aisec.cpg.passes.order.DependsOn import de.fraunhofer.aisec.cpg.processing.IVisitor @@ -40,8 +36,7 @@ import java.util.* import java.util.regex.Pattern @DependsOn(TypeHierarchyResolver::class) -open class ImportResolver(config: TranslationConfiguration, scopeManager: ScopeManager) : - ComponentPass(config, scopeManager) { +open class ImportResolver(ctx: TranslationContext) : ComponentPass(ctx) { protected val records: MutableList = ArrayList() protected val importables: MutableMap = HashMap() diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/Pass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/Pass.kt index bc54757691..5a466b8c40 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/Pass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/Pass.kt @@ -27,15 +27,14 @@ package de.fraunhofer.aisec.cpg.passes import de.fraunhofer.aisec.cpg.ScopeManager import de.fraunhofer.aisec.cpg.TranslationConfiguration +import de.fraunhofer.aisec.cpg.TranslationContext import de.fraunhofer.aisec.cpg.TranslationResult import de.fraunhofer.aisec.cpg.frontends.Language import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend import de.fraunhofer.aisec.cpg.frontends.TranslationException -import de.fraunhofer.aisec.cpg.graph.Component -import de.fraunhofer.aisec.cpg.graph.LanguageProvider -import de.fraunhofer.aisec.cpg.graph.Node +import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration -import de.fraunhofer.aisec.cpg.passes.order.* +import de.fraunhofer.aisec.cpg.passes.order.RequiredFrontend import java.util.function.Consumer import kotlin.reflect.KClass import kotlin.reflect.full.primaryConstructor @@ -46,23 +45,20 @@ import org.slf4j.LoggerFactory * A [TranslationResultPass] is a pass that operates on a [TranslationResult]. If used with * [executePassSequential], one [Pass] object is instantiated for the whole [TranslationResult]. */ -abstract class TranslationResultPass(config: TranslationConfiguration, scopeManager: ScopeManager) : - Pass(config, scopeManager) +abstract class TranslationResultPass(ctx: TranslationContext) : Pass(ctx) /** * A [ComponentPass] is a pass that operates on a [Component]. If used with [executePassSequential], * one [Pass] object is instantiated for each [Component] in a [TranslationResult]. */ -abstract class ComponentPass(config: TranslationConfiguration, scopeManager: ScopeManager) : - Pass(config, scopeManager) +abstract class ComponentPass(ctx: TranslationContext) : Pass(ctx) /** * A [TranslationUnitPass] is a pass that operates on a [TranslationUnitDeclaration]. If used with * [executePassSequential], one [Pass] object is instantiated for each [TranslationUnitDeclaration] * in a [Component]. */ -abstract class TranslationUnitPass(config: TranslationConfiguration, scopeManager: ScopeManager) : - Pass(config, scopeManager) +abstract class TranslationUnitPass(ctx: TranslationContext) : Pass(ctx) /** * A pass target is an interface for a [Node] on which a [Pass] can operate, it should only be @@ -81,13 +77,15 @@ interface PassTarget * passes. Instead of directly subclassing this type, one of the types [TranslationResultPass], * [ComponentPass] or [TranslationUnitPass] must be used. */ -sealed class Pass( - val config: TranslationConfiguration, - val scopeManager: ScopeManager -) : Consumer { +sealed class Pass(final override val ctx: TranslationContext) : + Consumer, ContextProvider { var name: String protected set + val config: TranslationConfiguration = ctx.config + val scopeManager: ScopeManager = ctx.scopeManager + val typeManager: TypeManager = ctx.typeManager + init { name = this.javaClass.name } @@ -123,6 +121,7 @@ sealed class Pass( */ fun executePassSequential( cls: KClass>, + ctx: TranslationContext, result: TranslationResult, executedFrontends: Collection ) { @@ -130,28 +129,16 @@ fun executePassSequential( // "prototype" instance of our pass class, so we can deduce certain type information more // easily. val prototype = - cls.primaryConstructor?.call(result.config, result.scopeManager) + cls.primaryConstructor?.call(ctx) ?: throw TranslationException("Could not create prototype pass") when (prototype) { is TranslationResultPass -> { - executePass( - (prototype as TranslationResultPass)::class, - result, - result.config, - result.scopeManager, - executedFrontends - ) + executePass((prototype as TranslationResultPass)::class, ctx, result, executedFrontends) } is ComponentPass -> { for (component in result.components) { - executePass( - (prototype as ComponentPass)::class, - component, - result.config, - result.scopeManager, - executedFrontends - ) + executePass((prototype as ComponentPass)::class, ctx, component, executedFrontends) } } is TranslationUnitPass -> { @@ -159,9 +146,8 @@ fun executePassSequential( for (tu in component.translationUnits) { executePass( (prototype as TranslationUnitPass)::class, + ctx, tu, - result.config, - result.scopeManager, executedFrontends ) } @@ -172,9 +158,8 @@ fun executePassSequential( inline fun executePass( cls: KClass>, + ctx: TranslationContext, target: T, - config: TranslationConfiguration, - scopeManager: ScopeManager, executedFrontends: Collection ): Pass? { val language = @@ -184,9 +169,9 @@ inline fun executePass( null } - val realClass = checkForReplacement(cls, language, config) + val realClass = checkForReplacement(cls, language, ctx.config) - val pass = realClass.primaryConstructor?.call(config, scopeManager) + val pass = realClass.primaryConstructor?.call(ctx) if (pass?.runsWithCurrentFrontend(executedFrontends) == true) { pass.accept(target) pass.cleanup() diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/StatisticsCollectionPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/StatisticsCollectionPass.kt index 452a5e1597..7606341d1f 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/StatisticsCollectionPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/StatisticsCollectionPass.kt @@ -25,8 +25,7 @@ */ package de.fraunhofer.aisec.cpg.passes -import de.fraunhofer.aisec.cpg.ScopeManager -import de.fraunhofer.aisec.cpg.TranslationConfiguration +import de.fraunhofer.aisec.cpg.TranslationContext import de.fraunhofer.aisec.cpg.TranslationResult import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.ProblemNode @@ -38,14 +37,13 @@ import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker.ScopedWalker * A [Pass] collecting statistics for the graph. Currently, it collects the number of nodes and the * number of problem nodes (i.e., nodes where the translation failed for some reason). */ -class StatisticsCollectionPass(config: TranslationConfiguration, scopeManager: ScopeManager) : - TranslationResultPass(config, scopeManager) { +class StatisticsCollectionPass(ctx: TranslationContext) : TranslationResultPass(ctx) { /** Iterates the nodes of the [result] to collect statistics. */ override fun accept(result: TranslationResult) { var problemNodes = 0 var nodes = 0 - val walker = ScopedWalker(result.scopeManager) + val walker = ScopedWalker(ctx.scopeManager) walker.registerHandler { _: RecordDeclaration?, _: Node?, currNode: Node? -> nodes++ if (currNode is ProblemNode) { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/SymbolResolverPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/SymbolResolverPass.kt index 5851eed767..4ca2deb0ee 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/SymbolResolverPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/SymbolResolverPass.kt @@ -25,8 +25,7 @@ */ package de.fraunhofer.aisec.cpg.passes -import de.fraunhofer.aisec.cpg.ScopeManager -import de.fraunhofer.aisec.cpg.TranslationConfiguration +import de.fraunhofer.aisec.cpg.TranslationContext import de.fraunhofer.aisec.cpg.frontends.HasSuperClasses import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.declarations.* @@ -34,8 +33,7 @@ import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExp import de.fraunhofer.aisec.cpg.graph.types.* import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker -abstract class SymbolResolverPass(config: TranslationConfiguration, scopeManager: ScopeManager) : - ComponentPass(config, scopeManager) { +abstract class SymbolResolverPass(ctx: TranslationContext) : ComponentPass(ctx) { protected lateinit var walker: SubgraphWalker.ScopedWalker lateinit var currentTU: TranslationUnitDeclaration @@ -55,7 +53,7 @@ abstract class SymbolResolverPass(config: TranslationConfiguration, scopeManager protected fun findEnums(node: Node?) { if (node is EnumDeclaration) { // TODO: Use the name instead of the type. - val type = TypeParser.createFrom(node.name, node.language) + val type = node.parseType(node.name) enumMap.putIfAbsent(type, node) } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TypeHierarchyResolver.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TypeHierarchyResolver.kt index 61684954ee..a8ce5ba3db 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TypeHierarchyResolver.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TypeHierarchyResolver.kt @@ -25,8 +25,7 @@ */ package de.fraunhofer.aisec.cpg.passes -import de.fraunhofer.aisec.cpg.ScopeManager -import de.fraunhofer.aisec.cpg.TranslationConfiguration +import de.fraunhofer.aisec.cpg.TranslationContext import de.fraunhofer.aisec.cpg.graph.Component import de.fraunhofer.aisec.cpg.graph.Name import de.fraunhofer.aisec.cpg.graph.Node @@ -56,8 +55,7 @@ import java.util.* * at places where it is crucial to have parsed all [RecordDeclaration]s. Otherwise, type * information in the graph might not be fully correct */ -open class TypeHierarchyResolver(config: TranslationConfiguration, scopeManager: ScopeManager) : - ComponentPass(config, scopeManager) { +open class TypeHierarchyResolver(ctx: TranslationContext) : ComponentPass(ctx) { protected val recordMap = mutableMapOf() protected val enums = mutableListOf() 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 15bc3c637c..5a0d702e97 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 @@ -25,21 +25,18 @@ */ package de.fraunhofer.aisec.cpg.passes -import de.fraunhofer.aisec.cpg.ScopeManager -import de.fraunhofer.aisec.cpg.TranslationConfiguration +import de.fraunhofer.aisec.cpg.TranslationContext import de.fraunhofer.aisec.cpg.graph.Component import de.fraunhofer.aisec.cpg.graph.HasType import de.fraunhofer.aisec.cpg.graph.HasType.SecondaryTypeEdge import de.fraunhofer.aisec.cpg.graph.Node -import de.fraunhofer.aisec.cpg.graph.TypeManager import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration import de.fraunhofer.aisec.cpg.graph.types.* import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker.IterativeGraphWalker import de.fraunhofer.aisec.cpg.passes.order.DependsOn @DependsOn(CallResolver::class) -open class TypeResolver(config: TranslationConfiguration, scopeManager: ScopeManager) : - ComponentPass(config, scopeManager) { +open class TypeResolver(ctx: TranslationContext) : ComponentPass(ctx) { protected val firstOrderTypes = mutableSetOf() protected val typeState = mutableMapOf>() @@ -108,7 +105,6 @@ open class TypeResolver(config: TranslationConfiguration, scopeManager: ScopeMan } protected fun removeDuplicateTypes() { - val typeManager = TypeManager.getInstance() // Remove duplicate firstOrderTypes firstOrderTypes.addAll(typeManager.firstOrderTypes) @@ -241,6 +237,5 @@ open class TypeResolver(config: TranslationConfiguration, scopeManager: ScopeMan override fun cleanup() { firstOrderTypes.clear() typeState.clear() - TypeManager.reset() } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/VariableUsageResolver.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/VariableUsageResolver.kt index 3a28492440..b9155175f5 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/VariableUsageResolver.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/VariableUsageResolver.kt @@ -25,8 +25,7 @@ */ package de.fraunhofer.aisec.cpg.passes -import de.fraunhofer.aisec.cpg.ScopeManager -import de.fraunhofer.aisec.cpg.TranslationConfiguration +import de.fraunhofer.aisec.cpg.TranslationContext import de.fraunhofer.aisec.cpg.frontends.HasStructs import de.fraunhofer.aisec.cpg.frontends.HasSuperClasses import de.fraunhofer.aisec.cpg.graph.* @@ -59,8 +58,7 @@ import org.slf4j.LoggerFactory * rather makes their "refersTo" point to the appropriate [ValueDeclaration]. */ @DependsOn(TypeHierarchyResolver::class) -open class VariableUsageResolver(config: TranslationConfiguration, scopeManager: ScopeManager) : - SymbolResolverPass(config, scopeManager) { +open class VariableUsageResolver(ctx: TranslationContext) : SymbolResolverPass(ctx) { override fun accept(component: Component) { walker = ScopedWalker(scopeManager) @@ -144,7 +142,7 @@ open class VariableUsageResolver(config: TranslationConfiguration, scopeManager: var recordDeclType: Type? = null if (currentClass != null) { - recordDeclType = TypeParser.createFrom(currentClass.name, currentClass.language) + recordDeclType = currentClass.parseType(currentClass.name) } if (current.type is FunctionPointerType && refersTo == null) { refersTo = resolveFunctionPtr(current) @@ -200,9 +198,9 @@ open class VariableUsageResolver(config: TranslationConfiguration, scopeManager: return if (language != null && language.namespaceDelimiter.isNotEmpty()) { val parentName = (current.name.parent ?: current.name).toString() - TypeParser.createFrom(parentName, language) + current.parseType(parentName) } else { - current.language.newUnknownType() + current.newUnknownType() } } @@ -236,7 +234,7 @@ open class VariableUsageResolver(config: TranslationConfiguration, scopeManager: "Could not find referring super type ${superType.typeName} for ${curClass.name} in the record map. Will set the super type to java.lang.Object" ) // TODO: Should be more generic! - base.type = TypeParser.createFrom(Any::class.java.name, current.language) + base.type = current.parseType(Any::class.java.name) } else { // We need to connect this super reference to the receiver of this // method @@ -256,7 +254,7 @@ open class VariableUsageResolver(config: TranslationConfiguration, scopeManager: } else { // no explicit super type -> java.lang.Object // TODO: Should be more generic - val objectType = TypeParser.createFrom(Any::class.java.name, current.language) + val objectType = current.parseType(Any::class.java.name) base.type = objectType } } else { @@ -271,13 +269,13 @@ open class VariableUsageResolver(config: TranslationConfiguration, scopeManager: return } } else if (baseTarget is RecordDeclaration) { - var baseType = TypeParser.createFrom(baseTarget.name, baseTarget.language) + var baseType = baseTarget.parseType(baseTarget.name) if (baseType.name !in recordMap) { val containingT = baseType val fqnResolvedType = recordMap.keys.firstOrNull { it.lastPartsMatch(containingT.name) } if (fqnResolvedType != null) { - baseType = TypeParser.createFrom(fqnResolvedType, baseTarget.language) + baseType = baseTarget.parseType(fqnResolvedType) } } current.refersTo = resolveMember(baseType, current) @@ -288,7 +286,7 @@ open class VariableUsageResolver(config: TranslationConfiguration, scopeManager: if (baseType.name !in recordMap) { val fqnResolvedType = recordMap.keys.firstOrNull { it.lastPartsMatch(baseType.name) } if (fqnResolvedType != null) { - baseType = TypeParser.createFrom(fqnResolvedType, baseType.language) + baseType = current.base.parseType(fqnResolvedType) } } current.refersTo = resolveMember(baseType, current) @@ -361,8 +359,7 @@ open class VariableUsageResolver(config: TranslationConfiguration, scopeManager: } else { "class" } - val record = - base.startInference(scopeManager).inferRecordDeclaration(base, currentTU, kind) + val record = base.startInference(ctx).inferRecordDeclaration(base, currentTU, kind) // update the record map if (record != null) recordMap[base.name] = record } @@ -421,7 +418,7 @@ open class VariableUsageResolver(config: TranslationConfiguration, scopeManager: // If we didn't find anything, we create a new function or method declaration return target ?: (declarationHolder ?: currentTU) - .startInference(scopeManager) + .startInference(ctx) .createInferredFunctionDeclaration( name, null, diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/Inference.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/Inference.kt index f1856e6606..4048ce0d7e 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/Inference.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/Inference.kt @@ -25,7 +25,7 @@ */ package de.fraunhofer.aisec.cpg.passes.inference -import de.fraunhofer.aisec.cpg.ScopeManager +import de.fraunhofer.aisec.cpg.TranslationContext import de.fraunhofer.aisec.cpg.frontends.HasClasses import de.fraunhofer.aisec.cpg.frontends.Language import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend @@ -50,8 +50,8 @@ import org.slf4j.LoggerFactory * Since this class implements [IsInferredProvider], all nodes that are created using the node * builder functions, will automatically have [Node.isInferred] set to true. */ -class Inference(val start: Node, val scopeManager: ScopeManager) : - LanguageProvider, ScopeProvider, IsInferredProvider { +class Inference(val start: Node, override val ctx: TranslationContext) : + LanguageProvider, ScopeProvider, IsInferredProvider, ContextProvider { val log: Logger = LoggerFactory.getLogger(Inference::class.java) override val language: Language? @@ -60,6 +60,9 @@ class Inference(val start: Node, val scopeManager: ScopeManager) : override val isInferred: Boolean get() = true + val scopeManager = ctx.scopeManager + val typeManager = ctx.typeManager + override val scope: Scope? get() = scopeManager.currentScope @@ -84,7 +87,9 @@ class Inference(val start: Node, val scopeManager: ScopeManager) : return inferInScopeOf(start) { log.debug( - "Inferring a new function declaration $name with parameter types ${signature.map { it?.name }}" + "Inferring a new function declaration {} with parameter types {}", + name, + signature.map { it?.name } ) // "upgrade" our struct to a class, if it was inferred by us, since we are calling @@ -229,8 +234,7 @@ class Inference(val start: Node, val scopeManager: ScopeManager) : name: String, ): TypeParamDeclaration { val parameterizedType = ParameterizedType(name, language) - TypeManager.getInstance() - .addTypeParameter(start as? FunctionTemplateDeclaration, parameterizedType) + typeManager.addTypeParameter(start as? FunctionTemplateDeclaration, parameterizedType) val decl = newTypeParamDeclaration(name, name) decl.type = parameterizedType @@ -265,10 +269,10 @@ class Inference(val start: Node, val scopeManager: ScopeManager) : val inferredRealization = if (record != null) { record.addDeclaration(inferred) - record.inferMethod(call, scopeManager = scopeManager) + record.inferMethod(call, ctx = ctx) } else { tu?.addDeclaration(inferred) - tu?.inferFunction(call, scopeManager = scopeManager) + tu?.inferFunction(call, ctx = ctx) } inferredRealization?.let { inferred.addRealization(it) } @@ -280,16 +284,14 @@ class Inference(val start: Node, val scopeManager: ScopeManager) : // Template Parameter val inferredTypeIdentifier = "T$typeCounter" val typeParamDeclaration = - inferred - .startInference(scopeManager) - .inferTemplateParameter(inferredTypeIdentifier) + inferred.startInference(ctx).inferTemplateParameter(inferredTypeIdentifier) typeCounter++ inferred.addParameter(typeParamDeclaration) } else if (node is Expression) { val inferredNonTypeIdentifier = "N$nonTypeCounter" val paramVariableDeclaration = node - .startInference(scopeManager) + .startInference(ctx) .inferNonTypeTemplateParameter(inferredNonTypeIdentifier) paramVariableDeclaration.addPrevDFG(node) @@ -339,13 +341,13 @@ class Inference(val start: Node, val scopeManager: ScopeManager) : // restore it. return inferInScopeOf(start) { log.debug( - "Inferring a new namespace declaration $name ${ - if (path != null) { - "with path '$path'" - } else { - "" - } - }" + "Inferring a new namespace declaration {} {}", + name, + if (path != null) { + "with path '$path'" + } else { + "" + } ) val inferred = newNamespaceDeclaration(name) @@ -367,15 +369,15 @@ interface IsInferredProvider : MetadataProvider { } /** Returns a new [Inference] object starting from this node. */ -fun Node.startInference(scopeManager: ScopeManager) = Inference(this, scopeManager) +fun Node.startInference(ctx: TranslationContext) = Inference(this, ctx) /** Tries to infer a [FunctionDeclaration] from a [CallExpression]. */ fun TranslationUnitDeclaration.inferFunction( call: CallExpression, isStatic: Boolean = false, - scopeManager: ScopeManager, + ctx: TranslationContext ): FunctionDeclaration { - return Inference(this, scopeManager) + return Inference(this, ctx) .createInferredFunctionDeclaration( call.name.localName, call.code, @@ -390,9 +392,9 @@ fun TranslationUnitDeclaration.inferFunction( fun NamespaceDeclaration.inferFunction( call: CallExpression, isStatic: Boolean = false, - scopeManager: ScopeManager, + ctx: TranslationContext ): FunctionDeclaration { - return Inference(this, scopeManager) + return Inference(this, ctx) .createInferredFunctionDeclaration( call.name, call.code, @@ -407,9 +409,9 @@ fun NamespaceDeclaration.inferFunction( fun RecordDeclaration.inferMethod( call: CallExpression, isStatic: Boolean = false, - scopeManager: ScopeManager + ctx: TranslationContext ): MethodDeclaration { - return Inference(this, scopeManager) + return Inference(this, ctx) .createInferredFunctionDeclaration( call.name.localName, call.code, diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/GraphExamples.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/GraphExamples.kt index 91ea8727d0..d1eb129f32 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/GraphExamples.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/GraphExamples.kt @@ -28,6 +28,7 @@ package de.fraunhofer.aisec.cpg import de.fraunhofer.aisec.cpg.frontends.StructTestLanguage import de.fraunhofer.aisec.cpg.frontends.TestLanguage import de.fraunhofer.aisec.cpg.frontends.TestLanguageFrontend +import de.fraunhofer.aisec.cpg.graph.TypeManager import de.fraunhofer.aisec.cpg.graph.builder.* import de.fraunhofer.aisec.cpg.graph.newVariableDeclaration import de.fraunhofer.aisec.cpg.sarif.PhysicalLocation @@ -37,6 +38,12 @@ import java.net.URI class GraphExamples { companion object { + fun testFrontend(config: TranslationConfiguration): TestLanguageFrontend { + val ctx = TranslationContext(config, ScopeManager(), TypeManager()) + val language = config.languages.filterIsInstance().first() + return TestLanguageFrontend(language.namespaceDelimiter, language, ctx) + } + fun getInferenceRecordPtr( config: TranslationConfiguration = TranslationConfiguration.builder() @@ -47,30 +54,25 @@ class GraphExamples { ) .build() ) = - TestLanguageFrontend( - ScopeManager(), - config.languages.first().namespaceDelimiter, - config.languages.first() - ) - .build { - translationResult(config) { - translationUnit("record.cpp") { - // The main method - function("main", t("int")) { - body { - declare { variable("node", t("T*")) } - member("value", ref("node"), "->") assign literal(42, t("int")) - member("next", ref("node"), "->") assign ref("node") - memberCall( - "dump", - ref("node") - ) // TODO: Do we have to encode the "->" here? - returnStmt { isImplicit = true } - } + testFrontend(config).build { + translationResult { + translationUnit("record.cpp") { + // The main method + function("main", t("int")) { + body { + declare { variable("node", t("T*")) } + member("value", ref("node"), "->") assign literal(42, t("int")) + member("next", ref("node"), "->") assign ref("node") + memberCall( + "dump", + ref("node") + ) // TODO: Do we have to encode the "->" here? + returnStmt { isImplicit = true } } } } } + } fun getInferenceRecord( config: TranslationConfiguration = @@ -82,26 +84,21 @@ class GraphExamples { ) .build() ) = - TestLanguageFrontend( - ScopeManager(), - config.languages.first().namespaceDelimiter, - config.languages.first() - ) - .build { - translationResult(config) { - translationUnit("record.cpp") { - // The main method - function("main", t("int")) { - body { - declare { variable("node", t("T")) } - member("value", ref("node")) assign literal(42, t("int")) - member("next", ref("node")) assign { reference(ref("node")) } - returnStmt { isImplicit = true } - } + testFrontend(config).build { + translationResult { + translationUnit("record.cpp") { + // The main method + function("main", t("int")) { + body { + declare { variable("node", t("T")) } + member("value", ref("node")) assign literal(42, t("int")) + member("next", ref("node")) assign { reference(ref("node")) } + returnStmt { isImplicit = true } } } } } + } fun getVariables( config: TranslationConfiguration = @@ -110,8 +107,8 @@ class GraphExamples { .registerLanguage(TestLanguage(".")) .build() ) = - TestLanguageFrontend(ScopeManager(), ".").build { - translationResult(config) { + testFrontend(config).build { + translationResult { translationUnit("Variables.java") { record("Variables") { field("field", t("int")) { @@ -161,8 +158,8 @@ class GraphExamples { .registerLanguage(TestLanguage(".")) .build() ) = - TestLanguageFrontend(ScopeManager(), ".").build { - translationResult(config) { + testFrontend(config).build { + translationResult { translationUnit("unaryoperator.cpp") { // The main method function("somefunc") { @@ -183,8 +180,8 @@ class GraphExamples { .registerLanguage(TestLanguage(".")) .build() ) = - TestLanguageFrontend(ScopeManager(), ".").build { - translationResult(config) { + testFrontend(config).build { + translationResult { translationUnit("compoundoperator.cpp") { // The main method function("somefunc") { @@ -205,8 +202,8 @@ class GraphExamples { .registerLanguage(TestLanguage(".")) .build() ) = - TestLanguageFrontend(ScopeManager(), ".").build { - translationResult(config) { + testFrontend(config).build { + translationResult { translationUnit("conditional_expression.cpp") { // The main method function("main", t("int")) { @@ -281,8 +278,8 @@ class GraphExamples { .registerLanguage(TestLanguage(".")) .build() ) = - TestLanguageFrontend(ScopeManager(), ".").build { - translationResult(config) { + testFrontend(config).build { + translationResult { translationUnit("BasicSlice.java") { record("BasicSlice") { // The main method @@ -367,8 +364,8 @@ class GraphExamples { .registerLanguage(TestLanguage(".")) .build() ) = - TestLanguageFrontend(ScopeManager(), ".").build { - translationResult(config) { + testFrontend(config).build { + translationResult { translationUnit("ControlFlowSensitiveDFGIfMerge.java") { record("ControlFlowSensitiveDFGIfMerge") { field("bla", t("int")) {} @@ -439,8 +436,8 @@ class GraphExamples { .registerLanguage(TestLanguage(".")) .build() ) = - TestLanguageFrontend(ScopeManager(), ".").build { - translationResult(config) { + testFrontend(config).build { + translationResult { translationUnit("ControlFlowSesitiveDFGSwitch.java") { record("ControlFlowSesitiveDFGSwitch") { // The main method @@ -513,8 +510,8 @@ class GraphExamples { .registerLanguage(TestLanguage(".")) .build() ) = - TestLanguageFrontend(ScopeManager(), ".").build { - translationResult(config) { + testFrontend(config).build { + translationResult { translationUnit("ControlFlowSensitiveDFGIfNoMerge.java") { record("ControlFlowSensitiveDFGIfNoMerge") { // The main method @@ -551,8 +548,8 @@ class GraphExamples { .registerLanguage(TestLanguage(".")) .build() ) = - TestLanguageFrontend(ScopeManager(), ".").build { - translationResult(config) { + testFrontend(config).build { + translationResult { translationUnit("LoopDFGs.java") { record("LoopDFGs") { // The main method @@ -632,8 +629,8 @@ class GraphExamples { .registerLanguage(TestLanguage(".")) .build() ) = - TestLanguageFrontend(ScopeManager(), ".").build { - translationResult(config) { + testFrontend(config).build { + translationResult { translationUnit("LoopDFGs.java") { record("LoopDFGs") { // The main method @@ -683,8 +680,8 @@ class GraphExamples { .registerLanguage(TestLanguage(".")) .build() ) = - TestLanguageFrontend(ScopeManager(), ".").build { - translationResult(config) { + testFrontend(config).build { + translationResult { translationUnit("DelayedAssignmentAfterRHS.java") { record("DelayedAssignmentAfterRHS") { // The main method @@ -709,8 +706,8 @@ class GraphExamples { .registerLanguage(TestLanguage(".")) .build() ) = - TestLanguageFrontend(ScopeManager(), ".").build { - translationResult(config) { + testFrontend(config).build { + translationResult { translationUnit("ReturnTest.java") { record("ReturnTest", "class") { method("testReturn", t("int")) { @@ -755,8 +752,8 @@ class GraphExamples { .registerLanguage(TestLanguage(".")) .build() ) = - TestLanguageFrontend(ScopeManager(), ".").build { - translationResult(config) { + testFrontend(config).build { + translationResult { translationUnit("RecordDeclaration.java") { namespace("compiling") { record("SimpleClass", "class") { @@ -801,8 +798,8 @@ class GraphExamples { .registerLanguage(TestLanguage(".")) .build() ) = - TestLanguageFrontend(ScopeManager(), ".").build { - translationResult(config) { + testFrontend(config).build { + translationResult { translationUnit("Dataflow.java") { record("Dataflow") { field("attr", t("String")) { literal("", t("String")) } @@ -864,8 +861,8 @@ class GraphExamples { .registerLanguage(TestLanguage(".")) .build() ) = - TestLanguageFrontend(ScopeManager(), ".").build { - translationResult(config) { + testFrontend(config).build { + translationResult { translationUnit("ShortcutClass.java") { record("ShortcutClass") { field("attr", t("int")) { literal(0, t("int")) } diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CXXLanguageFrontendTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CXXLanguageFrontendTest.kt index 24ae3164c3..3af1e05838 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CXXLanguageFrontendTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CXXLanguageFrontendTest.kt @@ -63,7 +63,7 @@ internal class CXXLanguageFrontendTest : BaseTest() { val decl = main.iterator().next() val ls = decl.variables["ls"] assertNotNull(ls) - assertEquals(createTypeFrom("std::vector", true), ls.type) + assertEquals(tu.parseType("std::vector", true), ls.type) assertLocalName("ls", ls) val forEachStatement = decl.getBodyStatementAs(1, ForEachStatement::class.java) @@ -132,7 +132,7 @@ internal class CXXLanguageFrontendTest : BaseTest() { val sizeof = i.initializer as? TypeIdExpression assertNotNull(sizeof) assertLocalName("sizeof", sizeof) - assertEquals(createTypeFrom("std::size_t", true), sizeof.type) + assertEquals(tu.parseType("std::size_t", true), sizeof.type) val typeInfo = funcDecl.variables["typeInfo"] assertNotNull(typeInfo) @@ -140,7 +140,7 @@ internal class CXXLanguageFrontendTest : BaseTest() { val typeid = typeInfo.initializer as? TypeIdExpression assertNotNull(typeid) assertLocalName("typeid", typeid) - assertEquals(createTypeFrom("const std::type_info&", true), typeid.type) + assertEquals(tu.parseType("const std::type_info&", true), typeid.type) val j = funcDecl.variables["j"] assertNotNull(j) @@ -149,7 +149,7 @@ internal class CXXLanguageFrontendTest : BaseTest() { assertNotNull(sizeof) assertNotNull(alignOf) assertLocalName("alignof", alignOf) - assertEquals(createTypeFrom("std::size_t", true), alignOf.type) + assertEquals(tu.parseType("std::size_t", true), alignOf.type) } @Test @@ -162,18 +162,18 @@ internal class CXXLanguageFrontendTest : BaseTest() { Objects.requireNonNull(main!!.getBodyStatementAs(0, DeclarationStatement::class.java)) ?.singleDeclaration as VariableDeclaration assertNotNull(e) - assertEquals(createTypeFrom("ExtendedClass*", true), e.type) + assertEquals(tu.parseType("ExtendedClass*", true), e.type) val b = Objects.requireNonNull(main.getBodyStatementAs(1, DeclarationStatement::class.java)) ?.singleDeclaration as VariableDeclaration assertNotNull(b) - assertEquals(createTypeFrom("BaseClass*", true), b.type) + assertEquals(tu.parseType("BaseClass*", true), b.type) // initializer var cast = b.initializer as? CastExpression assertNotNull(cast) - assertEquals(createTypeFrom("BaseClass*", true), cast.castType) + assertEquals(tu.parseType("BaseClass*", true), cast.castType) val staticCast = main.getBodyStatementAs(2, BinaryOperator::class.java) assertNotNull(staticCast) @@ -194,7 +194,7 @@ internal class CXXLanguageFrontendTest : BaseTest() { cast = d.initializer as? CastExpression assertNotNull(cast) - assertEquals(createTypeFrom("int", true), cast.castType) + assertEquals(tu.parseType("int", true), cast.castType) } @Test @@ -213,7 +213,7 @@ internal class CXXLanguageFrontendTest : BaseTest() { (statement.statements[0] as DeclarationStatement).singleDeclaration as VariableDeclaration assertNotNull(x) - assertEquals(createTypeFrom("int[]", true), x.type) + assertEquals(tu.parseType("int[]", true), x.type) // initializer is an initializer list expression val ile = x.initializer as? InitializerListExpression @@ -414,8 +414,8 @@ internal class CXXLanguageFrontendTest : BaseTest() { @Throws(Exception::class) fun testDeclarationStatement() { val file = File("src/test/resources/cxx/declstmt.cpp") - val declaration = analyzeAndGetFirstTU(listOf(file), file.parentFile.toPath(), true) - val function = declaration.getDeclarationAs(0, FunctionDeclaration::class.java) + val tu = analyzeAndGetFirstTU(listOf(file), file.parentFile.toPath(), true) + val function = tu.getDeclarationAs(0, FunctionDeclaration::class.java) val statements = function?.statements assertNotNull(statements) statements.forEach( @@ -432,7 +432,7 @@ internal class CXXLanguageFrontendTest : BaseTest() { (statements[0] as DeclarationStatement).getSingleDeclarationAs( VariableDeclaration::class.java ) - assertEquals(createTypeFrom("SSL_CTX*", true), declFromMultiplicateExpression.type) + assertEquals(tu.parseType("SSL_CTX*", true), declFromMultiplicateExpression.type) assertLocalName("ptr", declFromMultiplicateExpression) val withInitializer = @@ -450,19 +450,19 @@ internal class CXXLanguageFrontendTest : BaseTest() { val b = twoDeclarations[0] as VariableDeclaration assertNotNull(b) assertLocalName("b", b) - assertEquals(createTypeFrom("int*", false), b.type) + assertEquals(tu.parseType("int*", false), b.type) val c = twoDeclarations[1] as VariableDeclaration assertNotNull(c) assertLocalName("c", c) - assertEquals(createTypeFrom("int", false), c.type) + assertEquals(tu.parseType("int", false), c.type) val withoutInitializer = (statements[3] as DeclarationStatement).getSingleDeclarationAs( VariableDeclaration::class.java ) initializer = withoutInitializer.initializer - assertEquals(createTypeFrom("int*", true), withoutInitializer.type) + assertEquals(tu.parseType("int*", true), withoutInitializer.type) assertLocalName("d", withoutInitializer) assertNull(initializer) @@ -470,7 +470,7 @@ internal class CXXLanguageFrontendTest : BaseTest() { (statements[4] as DeclarationStatement).getSingleDeclarationAs( VariableDeclaration::class.java ) - assertEquals(createTypeFrom("std::string", true), qualifiedType.type) + assertEquals(tu.parseType("std::string", true), qualifiedType.type) assertLocalName("text", qualifiedType) assertTrue(qualifiedType.initializer is Literal<*>) assertEquals("some text", (qualifiedType.initializer as? Literal<*>)?.value) @@ -479,7 +479,7 @@ internal class CXXLanguageFrontendTest : BaseTest() { (statements[5] as DeclarationStatement).getSingleDeclarationAs( VariableDeclaration::class.java ) - assertEquals(createTypeFrom("void*", true), pointerWithAssign.type) + assertEquals(tu.parseType("void*", true), pointerWithAssign.type) assertLocalName("ptr2", pointerWithAssign) assertLocalName("NULL", pointerWithAssign.initializer) @@ -623,9 +623,8 @@ internal class CXXLanguageFrontendTest : BaseTest() { @Throws(Exception::class) fun testBinaryOperator() { val file = File("src/test/resources/binaryoperator.cpp") - val declaration = analyzeAndGetFirstTU(listOf(file), file.parentFile.toPath(), true) - val statements = - declaration.getDeclarationAs(0, FunctionDeclaration::class.java)?.statements + val tu = analyzeAndGetFirstTU(listOf(file), file.parentFile.toPath(), true) + val statements = tu.getDeclarationAs(0, FunctionDeclaration::class.java)?.statements assertNotNull(statements) // first two statements are just declarations @@ -658,7 +657,7 @@ internal class CXXLanguageFrontendTest : BaseTest() { // syntactically no different than the previous ones val stmt = statements[4] as DeclarationStatement val decl = stmt.singleDeclaration as VariableDeclaration - assertEquals(createTypeFrom("std::string*", true), decl.type) + assertEquals(tu.parseType("std::string*", true), decl.type) assertLocalName("notMultiplication", decl) assertTrue(decl.initializer is BinaryOperator) @@ -687,7 +686,7 @@ internal class CXXLanguageFrontendTest : BaseTest() { val constant = recordDeclaration.fields["CONSTANT"] assertNotNull(constant) - assertEquals(createTypeFrom("void*", true), field.type) + assertEquals(tu.parseType("void*", true), field.type) assertEquals(3, recordDeclaration.methods.size) val method = recordDeclaration.methods[0] @@ -705,12 +704,12 @@ internal class CXXLanguageFrontendTest : BaseTest() { val methodWithParam = recordDeclaration.methods[1] assertLocalName("method", methodWithParam) assertEquals(1, methodWithParam.parameters.size) - assertEquals(createTypeFrom("int", true), methodWithParam.parameters[0].type) + assertEquals(tu.parseType("int", true), methodWithParam.parameters[0].type) assertEquals( FunctionType( "(int)void*", - listOf(createTypeFrom("int", true)), - listOf(createTypeFrom("void*", true)), + listOf(tu.parseType("int", true)), + listOf(tu.parseType("void*", true)), CPPLanguage() ), methodWithParam.type @@ -726,7 +725,7 @@ internal class CXXLanguageFrontendTest : BaseTest() { val inlineMethod = recordDeclaration.methods[2] assertLocalName("inlineMethod", inlineMethod) assertEquals( - FunctionType("()void*", listOf(), listOf(createTypeFrom("void*", true)), CPPLanguage()), + FunctionType("()void*", listOf(), listOf(tu.parseType("void*", true)), CPPLanguage()), inlineMethod.type ) assertTrue(inlineMethod.hasBody()) @@ -737,7 +736,7 @@ internal class CXXLanguageFrontendTest : BaseTest() { FunctionType( "()SomeClass", listOf(), - listOf(createTypeFrom("SomeClass", true)), + listOf(tu.parseType("SomeClass", true)), CPPLanguage() ), inlineConstructor.type @@ -747,12 +746,12 @@ internal class CXXLanguageFrontendTest : BaseTest() { val constructorDefinition = tu.getDeclarationAs(3, ConstructorDeclaration::class.java) assertNotNull(constructorDefinition) assertEquals(1, constructorDefinition.parameters.size) - assertEquals(createTypeFrom("int", true), constructorDefinition.parameters[0].type) + assertEquals(tu.parseType("int", true), constructorDefinition.parameters[0].type) assertEquals( FunctionType( "(int)SomeClass", - listOf(createTypeFrom("int", false)), - listOf(createTypeFrom("SomeClass", true)), + listOf(tu.parseType("int", false)), + listOf(tu.parseType("SomeClass", true)), CPPLanguage() ), constructorDefinition.type @@ -857,10 +856,10 @@ internal class CXXLanguageFrontendTest : BaseTest() { @Throws(Exception::class) fun testInitListExpression() { val file = File("src/test/resources/initlistexpression.cpp") - val declaration = analyzeAndGetFirstTU(listOf(file), file.parentFile.toPath(), true) + val tu = analyzeAndGetFirstTU(listOf(file), file.parentFile.toPath(), true) // x y = { 1, 2 }; - val y = declaration.getDeclarationAs(1, VariableDeclaration::class.java) + val y = tu.getDeclarationAs(1, VariableDeclaration::class.java) assertNotNull(y) assertLocalName("y", y) @@ -877,8 +876,8 @@ internal class CXXLanguageFrontendTest : BaseTest() { assertEquals(2, b.value) // int z[] = { 2, 3, 4 }; - val z = declaration.getDeclarationAs(2, VariableDeclaration::class.java) - assertEquals(createTypeFrom("int[]", true), z!!.type) + val z = tu.getDeclarationAs(2, VariableDeclaration::class.java) + assertEquals(tu.parseType("int[]", true), z!!.type) initializer = z.initializer assertNotNull(initializer) @@ -892,11 +891,11 @@ internal class CXXLanguageFrontendTest : BaseTest() { @Throws(Exception::class) fun testObjectCreation() { val file = File("src/test/resources/cxx/objcreation.cpp") - val declaration = analyzeAndGetFirstTU(listOf(file), file.parentFile.toPath(), true) - assertNotNull(declaration) + val tu = analyzeAndGetFirstTU(listOf(file), file.parentFile.toPath(), true) + assertNotNull(tu) // get the main method - val main = declaration.getDeclarationAs(3, FunctionDeclaration::class.java) + val main = tu.getDeclarationAs(3, FunctionDeclaration::class.java) val statement = main!!.body as CompoundStatement // Integer i @@ -904,20 +903,20 @@ internal class CXXLanguageFrontendTest : BaseTest() { (statement.statements[0] as DeclarationStatement).singleDeclaration as VariableDeclaration // type should be Integer - assertEquals(createTypeFrom("Integer", true), i.type) + assertEquals(tu.parseType("Integer", true), i.type) // initializer should be a construct expression var constructExpression = i.initializer as? ConstructExpression assertNotNull(constructExpression) // type of the construct expression should also be Integer - assertEquals(createTypeFrom("Integer", true), constructExpression.type) + assertEquals(tu.parseType("Integer", true), constructExpression.type) // auto (Integer) m val m = (statement.statements[6] as DeclarationStatement).singleDeclaration as VariableDeclaration // type should be Integer* - assertEquals(createTypeFrom("Integer*", true), m.type) + assertEquals(tu.parseType("Integer*", true), m.type) val constructor = constructExpression.constructor assertNotNull(constructor) @@ -928,19 +927,19 @@ internal class CXXLanguageFrontendTest : BaseTest() { val newExpression = m.initializer as? NewExpression assertNotNull(newExpression) // type of the new expression should also be Integer* - assertEquals(createTypeFrom("Integer*", true), newExpression.type) + assertEquals(tu.parseType("Integer*", true), newExpression.type) // initializer should be a construct expression constructExpression = newExpression.initializer as? ConstructExpression assertNotNull(constructExpression) // type of the construct expression should be Integer - assertEquals(createTypeFrom("Integer", true), constructExpression.type) + assertEquals(tu.parseType("Integer", true), constructExpression.type) // argument should be named k and of type m val k = constructExpression.arguments[0] as DeclaredReferenceExpression assertLocalName("k", k) // type of the construct expression should also be Integer - assertEquals(createTypeFrom("int", true), k.type) + assertEquals(tu.parseType("int", true), k.type) } private val FunctionDeclaration.statements: List? @@ -1330,7 +1329,7 @@ internal class CXXLanguageFrontendTest : BaseTest() { val file = File("src/test/resources/c/typedef_in_header/main.c") val result = analyze(listOf(file), file.parentFile.toPath(), true) - val typedefs = result.scopeManager.currentTypedefs + val typedefs = result.finalCtx.scopeManager.currentTypedefs assertNotNull(typedefs) assertTrue(typedefs.isNotEmpty()) @@ -1548,7 +1547,4 @@ internal class CXXLanguageFrontendTest : BaseTest() { val tu = analyzeAndGetFirstTU(listOf(file), file.parentFile.toPath(), true) assertNotNull(tu) } - - private fun createTypeFrom(typename: String, resolveAlias: Boolean) = - TypeParser.createFrom(typename, CPPLanguage(), resolveAlias, null) } diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CXXLiteralTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CXXLiteralTest.kt index 74090fff97..f5580e1259 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CXXLiteralTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CXXLiteralTest.kt @@ -33,7 +33,6 @@ import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration import de.fraunhofer.aisec.cpg.graph.statements.expressions.Literal import de.fraunhofer.aisec.cpg.graph.statements.expressions.UnaryOperator import de.fraunhofer.aisec.cpg.graph.types.Type -import de.fraunhofer.aisec.cpg.graph.types.TypeParser import java.io.File import java.math.BigInteger import kotlin.test.Test @@ -54,12 +53,12 @@ internal class CXXLiteralTest : BaseTest() { val funcDecl = zero.iterator().next() assertLocalName("zero", funcDecl) - assertLiteral(0, createTypeFrom("int"), funcDecl, "i") - assertLiteral(0L, createTypeFrom("long"), funcDecl, "l_with_suffix") - assertLiteral(0L, createTypeFrom("long long"), funcDecl, "l_long_long_with_suffix") + assertLiteral(0, tu.parseType("int"), funcDecl, "i") + assertLiteral(0L, tu.parseType("long"), funcDecl, "l_with_suffix") + assertLiteral(0L, tu.parseType("long long"), funcDecl, "l_long_long_with_suffix") assertLiteral( BigInteger.valueOf(0), - createTypeFrom("unsigned long long"), + tu.parseType("unsigned long long"), funcDecl, "l_unsigned_long_long_with_suffix" ) @@ -74,31 +73,31 @@ internal class CXXLiteralTest : BaseTest() { assertFalse(decimal.isEmpty()) val funcDecl = decimal.iterator().next() assertLocalName("decimal", funcDecl) - assertLiteral(42, createTypeFrom("int"), funcDecl, "i") - assertLiteral(1000, createTypeFrom("int"), funcDecl, "i_with_literal") - assertLiteral(9223372036854775807L, createTypeFrom("long"), funcDecl, "l") - assertLiteral(9223372036854775807L, createTypeFrom("long"), funcDecl, "l_with_suffix") + assertLiteral(42, tu.parseType("int"), funcDecl, "i") + assertLiteral(1000, tu.parseType("int"), funcDecl, "i_with_literal") + assertLiteral(9223372036854775807L, tu.parseType("long"), funcDecl, "l") + assertLiteral(9223372036854775807L, tu.parseType("long"), funcDecl, "l_with_suffix") assertLiteral( 9223372036854775807L, - createTypeFrom("long long"), + tu.parseType("long long"), funcDecl, "l_long_long_with_suffix" ) assertLiteral( BigInteger("9223372036854775809"), - createTypeFrom("unsigned long"), + tu.parseType("unsigned long"), funcDecl, "l_unsigned_long_with_suffix" ) assertLiteral( BigInteger("9223372036854775808"), - createTypeFrom("unsigned long long"), + tu.parseType("unsigned long long"), funcDecl, "l_long_long_implicit" ) assertLiteral( BigInteger("9223372036854775809"), - createTypeFrom("unsigned long long"), + tu.parseType("unsigned long long"), funcDecl, "l_unsigned_long_long_with_suffix" ) @@ -113,11 +112,11 @@ internal class CXXLiteralTest : BaseTest() { assertFalse(octal.isEmpty()) val funcDecl = octal.iterator().next() assertLocalName("octal", funcDecl) - assertLiteral(42, createTypeFrom("int"), funcDecl, "i") - assertLiteral(42L, createTypeFrom("long"), funcDecl, "l_with_suffix") + assertLiteral(42, tu.parseType("int"), funcDecl, "i") + assertLiteral(42L, tu.parseType("long"), funcDecl, "l_with_suffix") assertLiteral( BigInteger.valueOf(42), - createTypeFrom("unsigned long long"), + tu.parseType("unsigned long long"), funcDecl, "l_unsigned_long_long_with_suffix" ) @@ -133,11 +132,11 @@ internal class CXXLiteralTest : BaseTest() { assertFalse(hex.isEmpty()) val funcDecl = hex.iterator().next() assertLocalName("hex", funcDecl) - assertLiteral(42, createTypeFrom("int"), funcDecl, "i") - assertLiteral(42L, createTypeFrom("long"), funcDecl, "l_with_suffix") + assertLiteral(42, tu.parseType("int"), funcDecl, "i") + assertLiteral(42L, tu.parseType("long"), funcDecl, "l_with_suffix") assertLiteral( BigInteger.valueOf(42), - createTypeFrom("unsigned long long"), + tu.parseType("unsigned long long"), funcDecl, "l_unsigned_long_long_with_suffix" ) @@ -197,6 +196,4 @@ internal class CXXLiteralTest : BaseTest() { assertEquals(expectedType, literal.type) assertEquals(expectedValue, literal.value) } - - private fun createTypeFrom(typename: String) = TypeParser.createFrom(typename, CPPLanguage()) } diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CXXSymbolConfigurationTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CXXSymbolConfigurationTest.kt index 15b3afff40..39a4cb7465 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CXXSymbolConfigurationTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CXXSymbolConfigurationTest.kt @@ -25,11 +25,9 @@ */ package de.fraunhofer.aisec.cpg.frontends.cpp -import de.fraunhofer.aisec.cpg.BaseTest -import de.fraunhofer.aisec.cpg.ScopeManager -import de.fraunhofer.aisec.cpg.TranslationConfiguration -import de.fraunhofer.aisec.cpg.assertLocalName +import de.fraunhofer.aisec.cpg.* import de.fraunhofer.aisec.cpg.frontends.TranslationException +import de.fraunhofer.aisec.cpg.graph.TypeManager import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration import de.fraunhofer.aisec.cpg.graph.statements.expressions.BinaryOperator import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression @@ -49,8 +47,11 @@ internal class CXXSymbolConfigurationTest : BaseTest() { val tu = CXXLanguageFrontend( CPPLanguage(), - TranslationConfiguration.builder().defaultPasses().build(), - ScopeManager(), + TranslationContext( + TranslationConfiguration.builder().build(), + ScopeManager(), + TypeManager() + ) ) .parse(File("src/test/resources/symbols.cpp")) val main = tu.getDeclarationsByName("main", FunctionDeclaration::class.java) @@ -79,20 +80,17 @@ internal class CXXSymbolConfigurationTest : BaseTest() { @Test @Throws(TranslationException::class) fun testWithSymbols() { + val config = + TranslationConfiguration.builder() + .symbols(mapOf(Pair("HELLO_WORLD", "\"Hello World\""), Pair("INCREASE(X)", "X+1"))) + .defaultPasses() + .build() + // let's try with symbol definitions val tu = CXXLanguageFrontend( CPPLanguage(), - TranslationConfiguration.builder() - .symbols( - mapOf( - Pair("HELLO_WORLD", "\"Hello World\""), - Pair("INCREASE(X)", "X+1") - ) - ) - .defaultPasses() - .build(), - ScopeManager(), + TranslationContext(config, ScopeManager(), TypeManager()) ) .parse(File("src/test/resources/symbols.cpp")) val main = tu.getDeclarationsByName("main", FunctionDeclaration::class.java) diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/FluentTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/FluentTest.kt index 0c08fddd10..470dbdde28 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/FluentTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/FluentTest.kt @@ -44,10 +44,9 @@ import kotlin.test.* class FluentTest { @Test fun test() { - val scopeManager = ScopeManager() val result = - TestLanguageFrontend(scopeManager).build { - translationResult(TranslationConfiguration.builder().build()) { + TestLanguageFrontend().build { + translationResult { translationUnit("file.cpp") { function("main", t("int")) { param("argc", t("int")) @@ -170,8 +169,7 @@ class FluentTest { assertNotNull(lit2.scope) assertEquals(2, lit2.value) - VariableUsageResolver(TranslationConfiguration.builder().build(), scopeManager) - .accept(result.components.first()) + VariableUsageResolver(result.finalCtx).accept(result.components.first()) // Now the reference should be resolved assertRefersTo(ref, variable) diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/AssignExpressionTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/AssignExpressionTest.kt index 8fe39f93b3..c9d96d6df7 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/AssignExpressionTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/AssignExpressionTest.kt @@ -25,9 +25,7 @@ */ package de.fraunhofer.aisec.cpg.graph.statements.expressions -import de.fraunhofer.aisec.cpg.TranslationConfiguration import de.fraunhofer.aisec.cpg.assertLocalName -import de.fraunhofer.aisec.cpg.frontends.TestLanguage import de.fraunhofer.aisec.cpg.frontends.TestLanguageFrontend import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.builder.function @@ -41,7 +39,7 @@ import kotlin.test.* class AssignExpressionTest { @Test fun propagateSimple() { - with(TestLanguage()) { + with(TestLanguageFrontend()) { val refA = newDeclaredReferenceExpression("a") val refB = newDeclaredReferenceExpression("b") @@ -65,7 +63,7 @@ class AssignExpressionTest { fun propagateTuple() { with(TestLanguageFrontend()) { val result = build { - translationResult(TranslationConfiguration.builder().build()) { + translationResult { translationUnit { val func = function( @@ -109,7 +107,7 @@ class AssignExpressionTest { assertLocalName("error", refErr.type) // Invoke the DFG pass - DFGPass(result.config, result.scopeManager).accept(result.components.first()) + DFGPass(ctx).accept(result.components.first()) assertTrue(refA.prevDFG.contains(call)) assertTrue(refErr.prevDFG.contains(call)) diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/types/TypePropagationTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/types/TypePropagationTest.kt index cf08b107ea..2faaebd0ab 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/types/TypePropagationTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/types/TypePropagationTest.kt @@ -25,8 +25,6 @@ */ package de.fraunhofer.aisec.cpg.graph.types -import de.fraunhofer.aisec.cpg.ScopeManager -import de.fraunhofer.aisec.cpg.TranslationConfiguration import de.fraunhofer.aisec.cpg.frontends.TestLanguageFrontend import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.builder.* @@ -43,7 +41,7 @@ class TypePropagationTest { fun testBinopTypePropagation() { val result = TestLanguageFrontend().build { - translationResult(TranslationConfiguration.builder().build()) { + translationResult { translationUnit("test") { function("main", t("int")) { body { @@ -76,10 +74,9 @@ class TypePropagationTest { @Test fun testAssignTypePropagation() { // TODO: This test is related to issue 1071 (it models case 2). - val scopeManager = ScopeManager() val result = - TestLanguageFrontend(scopeManager).build { - translationResult(TranslationConfiguration.builder().build()) { + TestLanguageFrontend().build { + translationResult { translationUnit("test") { function("main", t("int")) { body { @@ -92,7 +89,7 @@ class TypePropagationTest { } } } - VariableUsageResolver(result.config, result.scopeManager).accept(result.components.first()) + VariableUsageResolver(result.finalCtx).accept(result.components.first()) val binaryOp = (result.functions["main"]?.body as? CompoundStatement)?.statements?.get(2) diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/types/TypeTests.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/types/TypeTests.kt index 96cb16f5e3..cc8da1792c 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/types/TypeTests.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/types/TypeTests.kt @@ -25,13 +25,12 @@ */ package de.fraunhofer.aisec.cpg.graph.types -import de.fraunhofer.aisec.cpg.BaseTest +import de.fraunhofer.aisec.cpg.* import de.fraunhofer.aisec.cpg.TestUtils.analyze import de.fraunhofer.aisec.cpg.TestUtils.analyzeAndGetFirstTU -import de.fraunhofer.aisec.cpg.TestUtils.disableTypeManagerCleanup import de.fraunhofer.aisec.cpg.TestUtils.findByUniqueName -import de.fraunhofer.aisec.cpg.TranslationResult import de.fraunhofer.aisec.cpg.frontends.cpp.CPPLanguage +import de.fraunhofer.aisec.cpg.frontends.cpp.CXXLanguageFrontend import de.fraunhofer.aisec.cpg.graph.* import java.nio.file.Path import java.util.* @@ -110,186 +109,208 @@ internal class TypeTests : BaseTest() { fun createFromCPP() { var result: Type - // Test 1: Function pointer - var typeString = "void (*single_param)(int)" - result = TypeParser.createFrom(typeString, CPPLanguage()) - val parameterList = - listOf(IntegerType("int", 32, CPPLanguage(), NumericType.Modifier.SIGNED)) - var expected: Type = FunctionPointerType(parameterList, CPPLanguage(), IncompleteType()) - assertEquals(expected, result) - - // Test 1.1: interleaved brackets in function pointer - typeString = "void ((*single_param)(int))" - result = TypeParser.createFrom(typeString, CPPLanguage()) - assertEquals(result, expected) - - // Test 2: Stronger binding of brackets and pointer - typeString = "char (* const a)[]" - result = TypeParser.createFrom(typeString, CPPLanguage()) - expected = - PointerType( + with( + CXXLanguageFrontend( + CPPLanguage(), + TranslationContext( + TranslationConfiguration.builder().build(), + ScopeManager(), + TypeManager() + ) + ) + ) { + // Test 1: Function pointer + var typeString = "void (*single_param)(int)" + result = parseType(typeString) + val parameterList = + listOf(IntegerType("int", 32, CPPLanguage(), NumericType.Modifier.SIGNED)) + var expected: Type = FunctionPointerType(parameterList, CPPLanguage(), IncompleteType()) + assertEquals(expected, result) + + // Test 1.1: interleaved brackets in function pointer + typeString = "void ((*single_param)(int))" + result = parseType(typeString) + assertEquals(result, expected) + + // Test 2: Stronger binding of brackets and pointer + typeString = "char (* const a)[]" + result = parseType(typeString) + expected = + PointerType( + PointerType( + IntegerType("char", 8, CPPLanguage(), NumericType.Modifier.NOT_APPLICABLE), + PointerType.PointerOrigin.ARRAY + ), + PointerType.PointerOrigin.POINTER + ) + assertEquals(expected, result) + + // Test 3: Mutable pointer to a mutable char + typeString = "char *p" + result = parseType(typeString) + expected = PointerType( IntegerType("char", 8, CPPLanguage(), NumericType.Modifier.NOT_APPLICABLE), - PointerType.PointerOrigin.ARRAY - ), - PointerType.PointerOrigin.POINTER - ) - assertEquals(expected, result) - - // Test 3: Mutable pointer to a mutable char - typeString = "char *p" - result = TypeParser.createFrom(typeString, CPPLanguage()) - expected = - PointerType( - IntegerType("char", 8, CPPLanguage(), NumericType.Modifier.NOT_APPLICABLE), - PointerType.PointerOrigin.POINTER - ) - assertEquals(expected, result) - - // Test 3.1: Different Whitespaces - typeString = "char* p" - result = TypeParser.createFrom(typeString, CPPLanguage()) - assertEquals(expected, result) - - // Test 3.2: Different Whitespaces - typeString = "char * p" - result = TypeParser.createFrom(typeString, CPPLanguage()) - assertEquals(expected, result) - - // Test 4: Mutable pointer to a constant char - typeString = "const char *p;" - result = TypeParser.createFrom(typeString, CPPLanguage()) - expected = - PointerType( - IntegerType("char", 8, CPPLanguage(), NumericType.Modifier.NOT_APPLICABLE), - PointerType.PointerOrigin.POINTER - ) - assertEquals(expected, result) - - // Test 5: Constant pointer to a mutable char - typeString = "char * const p;" - result = TypeParser.createFrom(typeString, CPPLanguage()) - expected = - PointerType( - IntegerType("char", 8, CPPLanguage(), NumericType.Modifier.NOT_APPLICABLE), - PointerType.PointerOrigin.POINTER - ) - assertEquals(expected, result) - - // Test 6: Constant pointer to a constant char - typeString = "const char * const p;" - result = TypeParser.createFrom(typeString, CPPLanguage()) - expected = - PointerType( - IntegerType("char", 8, CPPLanguage(), NumericType.Modifier.NOT_APPLICABLE), - PointerType.PointerOrigin.POINTER - ) - assertEquals(expected, result) + PointerType.PointerOrigin.POINTER + ) + assertEquals(expected, result) + + // Test 3.1: Different Whitespaces + typeString = "char* p" + result = parseType(typeString) + assertEquals(expected, result) + + // Test 3.2: Different Whitespaces + typeString = "char * p" + result = parseType(typeString) + assertEquals(expected, result) + + // Test 4: Mutable pointer to a constant char + typeString = "const char *p;" + result = parseType(typeString) + expected = + PointerType( + IntegerType("char", 8, CPPLanguage(), NumericType.Modifier.NOT_APPLICABLE), + PointerType.PointerOrigin.POINTER + ) + assertEquals(expected, result) - // Test 7: Array of const pointer to static const char - typeString = "static const char * const somearray []" - result = TypeParser.createFrom(typeString, CPPLanguage()) - expected = - PointerType( + // Test 5: Constant pointer to a mutable char + typeString = "char * const p;" + result = parseType(typeString) + expected = PointerType( IntegerType("char", 8, CPPLanguage(), NumericType.Modifier.NOT_APPLICABLE), PointerType.PointerOrigin.POINTER - ), - PointerType.PointerOrigin.ARRAY - ) - assertEquals(expected, result) + ) + assertEquals(expected, result) + + // Test 6: Constant pointer to a constant char + typeString = "const char * const p;" + result = parseType(typeString) + expected = + PointerType( + IntegerType("char", 8, CPPLanguage(), NumericType.Modifier.NOT_APPLICABLE), + PointerType.PointerOrigin.POINTER + ) + assertEquals(expected, result) - // Test 7.1: Array of array of pointer to static const char - typeString = "static const char * somearray[][]" - result = TypeParser.createFrom(typeString, CPPLanguage()) - expected = - PointerType( + // Test 7: Array of const pointer to static const char + typeString = "static const char * const somearray []" + result = parseType(typeString) + expected = PointerType( PointerType( IntegerType("char", 8, CPPLanguage(), NumericType.Modifier.NOT_APPLICABLE), PointerType.PointerOrigin.POINTER ), PointerType.PointerOrigin.ARRAY - ), - PointerType.PointerOrigin.ARRAY - ) - assertEquals(expected, result) - - // Test 8: Generics - typeString = "Array array" - result = TypeParser.createFrom(typeString, CPPLanguage()) - var generics = mutableListOf() - generics.add(IntegerType("int", 32, CPPLanguage(), NumericType.Modifier.SIGNED)) - expected = ObjectType("Array", generics, false, CPPLanguage()) - assertEquals(expected, result) - - // Test 9: Compound Primitive Types - typeString = "long long int" - result = TypeParser.createFrom(typeString, CPPLanguage()) - expected = IntegerType("long long int", 64, CPPLanguage(), NumericType.Modifier.SIGNED) - assertEquals(expected, result) - - // Test 10: Unsigned/Signed Types - typeString = "unsigned int" - result = TypeParser.createFrom(typeString, CPPLanguage()) - expected = IntegerType("unsigned int", 32, CPPLanguage(), NumericType.Modifier.UNSIGNED) - assertEquals(expected, result) - typeString = "signed int" - result = TypeParser.createFrom(typeString, CPPLanguage()) - expected = IntegerType("int", 32, CPPLanguage(), NumericType.Modifier.SIGNED) - assertEquals(expected, result) - typeString = "A a" - result = TypeParser.createFrom(typeString, CPPLanguage()) - expected = ObjectType("A", ArrayList(), false, CPPLanguage()) - assertEquals(expected, result) - - // Test 11: Unsigned + const + compound primitive Types - expected = - IntegerType("unsigned long long int", 64, CPPLanguage(), NumericType.Modifier.UNSIGNED) - typeString = "const unsigned long long int a = 1" - result = TypeParser.createFrom(typeString, CPPLanguage()) - assertEquals(expected, result) - - typeString = "unsigned const long long int b = 1" - result = TypeParser.createFrom(typeString, CPPLanguage()) - assertEquals(expected, result) - - typeString = "unsigned long const long int c = 1" - result = TypeParser.createFrom(typeString, CPPLanguage()) - assertEquals(expected, result) - - typeString = "unsigned long long const int d = 1" - result = TypeParser.createFrom(typeString, CPPLanguage()) - assertEquals(expected, result) - - typeString = "unsigned long long int const e = 1" - result = TypeParser.createFrom(typeString, CPPLanguage()) - assertEquals(expected, result) - - // Test 12: C++ Reference Types - typeString = "const int& ref = a" - result = TypeParser.createFrom(typeString, CPPLanguage()) - expected = ReferenceType(IntegerType("int", 32, CPPLanguage(), NumericType.Modifier.SIGNED)) - assertEquals(expected, result) - - typeString = "int const &ref2 = a" - result = TypeParser.createFrom(typeString, CPPLanguage()) - assertEquals(expected, result) - - // Test 13: Elaborated Type in Generics - result = TypeParser.createFrom("Array", CPPLanguage()) - generics = ArrayList() - var generic = ObjectType("Node", ArrayList(), false, CPPLanguage()) - generics.add(generic) - expected = ObjectType("Array", generics, false, CPPLanguage()) - assertEquals(expected, result) - - result = TypeParser.createFrom("Array", CPPLanguage()) - generics = ArrayList() - generic = ObjectType("myclass", ArrayList(), false, CPPLanguage()) - generics.add(generic) - expected = ObjectType("Array", generics, false, CPPLanguage()) - assertEquals(expected, result) + ) + assertEquals(expected, result) + + // Test 7.1: Array of array of pointer to static const char + typeString = "static const char * somearray[][]" + result = parseType(typeString) + expected = + PointerType( + PointerType( + PointerType( + IntegerType( + "char", + 8, + CPPLanguage(), + NumericType.Modifier.NOT_APPLICABLE + ), + PointerType.PointerOrigin.POINTER + ), + PointerType.PointerOrigin.ARRAY + ), + PointerType.PointerOrigin.ARRAY + ) + assertEquals(expected, result) + + // Test 8: Generics + typeString = "Array array" + result = parseType(typeString) + var generics = mutableListOf() + generics.add(IntegerType("int", 32, CPPLanguage(), NumericType.Modifier.SIGNED)) + expected = ObjectType("Array", generics, false, CPPLanguage()) + assertEquals(expected, result) + + // Test 9: Compound Primitive Types + typeString = "long long int" + result = parseType(typeString) + expected = IntegerType("long long int", 64, CPPLanguage(), NumericType.Modifier.SIGNED) + assertEquals(expected, result) + + // Test 10: Unsigned/Signed Types + typeString = "unsigned int" + result = parseType(typeString) + expected = IntegerType("unsigned int", 32, CPPLanguage(), NumericType.Modifier.UNSIGNED) + assertEquals(expected, result) + typeString = "signed int" + result = parseType(typeString) + expected = IntegerType("int", 32, CPPLanguage(), NumericType.Modifier.SIGNED) + assertEquals(expected, result) + typeString = "A a" + result = parseType(typeString) + expected = ObjectType("A", ArrayList(), false, CPPLanguage()) + assertEquals(expected, result) + + // Test 11: Unsigned + const + compound primitive Types + expected = + IntegerType( + "unsigned long long int", + 64, + CPPLanguage(), + NumericType.Modifier.UNSIGNED + ) + typeString = "const unsigned long long int a = 1" + result = parseType(typeString) + assertEquals(expected, result) + + typeString = "unsigned const long long int b = 1" + result = parseType(typeString) + assertEquals(expected, result) + + typeString = "unsigned long const long int c = 1" + result = parseType(typeString) + assertEquals(expected, result) + + typeString = "unsigned long long const int d = 1" + result = parseType(typeString) + assertEquals(expected, result) + + typeString = "unsigned long long int const e = 1" + result = parseType(typeString) + assertEquals(expected, result) + + // Test 12: C++ Reference Types + typeString = "const int& ref = a" + result = parseType(typeString) + expected = + ReferenceType(IntegerType("int", 32, CPPLanguage(), NumericType.Modifier.SIGNED)) + assertEquals(expected, result) + + typeString = "int const &ref2 = a" + result = parseType(typeString) + assertEquals(expected, result) + + // Test 13: Elaborated Type in Generics + result = parseType("Array") + generics = ArrayList() + var generic = ObjectType("Node", ArrayList(), false, CPPLanguage()) + generics.add(generic) + expected = ObjectType("Array", generics, false, CPPLanguage()) + assertEquals(expected, result) + + result = parseType("Array") + generics = ArrayList() + generic = ObjectType("myclass", ArrayList(), false, CPPLanguage()) + generics.add(generic) + expected = ObjectType("Array", generics, false, CPPLanguage()) + assertEquals(expected, result) + } } /** @@ -348,82 +369,100 @@ internal class TypeTests : BaseTest() { @Throws(Exception::class) @Test fun testCommonTypeTestCpp() { - disableTypeManagerCleanup() - val topLevel = Path.of("src", "test", "resources", "compiling", "hierarchy", "multistep") - val result = analyze("simple_inheritance.cpp", topLevel, true) - val root = TypeParser.createFrom("Root", CPPLanguage()) - val level0 = TypeParser.createFrom("Level0", CPPLanguage()) - val level1 = TypeParser.createFrom("Level1", CPPLanguage()) - val level1b = TypeParser.createFrom("Level1B", CPPLanguage()) - val level2 = TypeParser.createFrom("Level2", CPPLanguage()) - val unrelated = TypeParser.createFrom("Unrelated", CPPLanguage()) - getCommonTypeTestGeneral(root, level0, level1, level1b, level2, unrelated, result) + with( + CXXLanguageFrontend( + CPPLanguage(), + TranslationContext( + TranslationConfiguration.builder().build(), + ScopeManager(), + TypeManager() + ) + ) + ) { + val topLevel = + Path.of("src", "test", "resources", "compiling", "hierarchy", "multistep") + val result = analyze("simple_inheritance.cpp", topLevel, true) + val root = parseType("Root") + val level0 = parseType("Level0") + val level1 = parseType("Level1") + val level1b = parseType("Level1B") + val level2 = parseType("Level2") + val unrelated = parseType("Unrelated") + getCommonTypeTestGeneral(root, level0, level1, level1b, level2, unrelated, result) + } } // level2 and level2b have two intersections, both root and level0 -> level0 is lower @Throws(Exception::class) @Test fun testCommonTypeTestCppMultiInheritance() { - disableTypeManagerCleanup() - val topLevel = Path.of("src", "test", "resources", "compiling", "hierarchy", "multistep") - val result = analyze("multi_inheritance.cpp", topLevel, true) - - val root = TypeParser.createFrom("Root", CPPLanguage()) - val level0 = TypeParser.createFrom("Level0", CPPLanguage()) - val level0b = TypeParser.createFrom("Level0B", CPPLanguage()) - val level1 = TypeParser.createFrom("Level1", CPPLanguage()) - val level1b = TypeParser.createFrom("Level1B", CPPLanguage()) - val level1c = TypeParser.createFrom("Level1C", CPPLanguage()) - val level2 = TypeParser.createFrom("Level2", CPPLanguage()) - val level2b = TypeParser.createFrom("Level2B", CPPLanguage()) - - val provider = result.scopeManager - /* - Type hierarchy: - Root------------ - | | - Level0 Level0B | - / \ / \ | - Level1 Level1B Level1C - | \ / - Level2 Level2B - */ - // Root is the top, but unrelated to Level0B - for (t in listOf(root, level0, level1, level1b, level1c, level2, level2b)) { - assertEquals( - Optional.of(t), - TypeManager.getInstance().getCommonType(listOf(t), provider) + with( + CXXLanguageFrontend( + CPPLanguage(), + TranslationContext( + TranslationConfiguration.builder().build(), + ScopeManager(), + TypeManager() + ) ) - } - assertEquals( - Optional.empty(), - TypeManager.getInstance().getCommonType(listOf(root, level0b), provider) - ) - for (t in listOf(level0, level1, level2)) { + ) { + val topLevel = + Path.of("src", "test", "resources", "compiling", "hierarchy", "multistep") + val result = analyze("multi_inheritance.cpp", topLevel, true) + + val root = parseType("Root") + val level0 = parseType("Level0") + val level0b = parseType("Level0B") + val level1 = parseType("Level1") + val level1b = parseType("Level1B") + val level1c = parseType("Level1C") + val level2 = parseType("Level2") + val level2b = parseType("Level2B") + + val typeManager = result.finalCtx.typeManager + /* + Type hierarchy: + Root------------ + | | + Level0 Level0B | + / \ / \ | + Level1 Level1B Level1C + | \ / + Level2 Level2B + */ + // Root is the top, but unrelated to Level0B + for (t in listOf(root, level0, level1, level1b, level1c, level2, level2b)) { + assertEquals(Optional.of(t), typeManager.getCommonType(listOf(t), result.finalCtx)) + } assertEquals( Optional.empty(), - TypeManager.getInstance().getCommonType(listOf(t, level0b), provider) + typeManager.getCommonType(listOf(root, level0b), result.finalCtx) + ) + for (t in listOf(level0, level1, level2)) { + assertEquals( + Optional.empty(), + typeManager.getCommonType(listOf(t, level0b), result.finalCtx) + ) + } + assertEquals( + Optional.of(level0b), + typeManager.getCommonType(listOf(level1b, level1c), result.finalCtx) + ) + assertEquals( + Optional.of(level0), + typeManager.getCommonType(listOf(level1, level1b, level2, level2b), result.finalCtx) + ) + assertEquals( + Optional.of(root), + typeManager.getCommonType(listOf(level1, level1c), result.finalCtx) ) - } - assertEquals( - Optional.of(level0b), - TypeManager.getInstance().getCommonType(listOf(level1b, level1c), provider) - ) - assertEquals( - Optional.of(level0), - TypeManager.getInstance() - .getCommonType(listOf(level1, level1b, level2, level2b), provider) - ) - assertEquals( - Optional.of(root), - TypeManager.getInstance().getCommonType(listOf(level1, level1c), provider) - ) - // level2 and level2b have two intersections, both root and level0 -> level0 is lower - assertEquals( - Optional.of(level0), - TypeManager.getInstance().getCommonType(listOf(level2, level2b), provider) - ) + // level2 and level2b have two intersections, both root and level0 -> level0 is lower + assertEquals( + Optional.of(level0), + typeManager.getCommonType(listOf(level2, level2b), result.finalCtx) + ) + } } @Test @@ -467,21 +506,19 @@ internal class TypeTests : BaseTest() { | Level2 */ - val provider = result.scopeManager + val provider = result.finalCtx.scopeManager + val typeManager = result.finalCtx.typeManager // A single type is its own least common ancestor for (t in listOf(root, level0, level1, level1b, level2)) { - assertEquals( - Optional.of(t), - TypeManager.getInstance().getCommonType(listOf(t), provider) - ) + assertEquals(Optional.of(t), typeManager.getCommonType(listOf(t), result.finalCtx)) } // Root is the root of all types for (t in listOf(level0, level1, level1b, level2)) { assertEquals( Optional.of(root), - TypeManager.getInstance().getCommonType(listOf(t, root), provider) + typeManager.getCommonType(listOf(t, root), result.finalCtx) ) } @@ -489,33 +526,33 @@ internal class TypeTests : BaseTest() { for (t in listOf(level1, level1b, level2)) { assertEquals( Optional.of(level0), - TypeManager.getInstance().getCommonType(listOf(t, level0), provider) + typeManager.getCommonType(listOf(t, level0), result.finalCtx) ) } // Level1 and Level1B have Level0 as common ancestor assertEquals( Optional.of(level0), - TypeManager.getInstance().getCommonType(listOf(level1, level1b), provider) + typeManager.getCommonType(listOf(level1, level1b), result.finalCtx) ) // Level2 and Level1B have Level0 as common ancestor assertEquals( Optional.of(level0), - TypeManager.getInstance().getCommonType(listOf(level2, level1b), provider) + typeManager.getCommonType(listOf(level2, level1b), result.finalCtx) ) // Level1 and Level2 have Level1 as common ancestor assertEquals( Optional.of(level1), - TypeManager.getInstance().getCommonType(listOf(level1, level2), provider) + typeManager.getCommonType(listOf(level1, level2), result.finalCtx) ) // Check unrelated type behavior: No common root class for (t in listOf(root, level0, level1, level1b, level2)) { assertEquals( Optional.empty(), - TypeManager.getInstance().getCommonType(listOf(unrelated, t), provider) + typeManager.getCommonType(listOf(unrelated, t), result.finalCtx) ) } } diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/types/TypedefTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/types/TypedefTest.kt index 4b1acdd0de..c4d8e52cc1 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/types/TypedefTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/types/TypedefTest.kt @@ -29,7 +29,6 @@ import de.fraunhofer.aisec.cpg.BaseTest import de.fraunhofer.aisec.cpg.TestUtils.analyze import de.fraunhofer.aisec.cpg.TestUtils.findByUniqueName import de.fraunhofer.aisec.cpg.TestUtils.findByUniquePredicate -import de.fraunhofer.aisec.cpg.graph.TypeManager import de.fraunhofer.aisec.cpg.graph.declarations.ValueDeclaration import de.fraunhofer.aisec.cpg.graph.records import de.fraunhofer.aisec.cpg.graph.variables @@ -72,7 +71,7 @@ internal class TypedefTest : BaseTest() { assertEquals(NumericType.Modifier.UNSIGNED, returnType.modifier) assertEquals(uintfp1.type, uintfp2.type) - val typedefs = result.scopeManager.currentTypedefs + val typedefs = result.finalCtx.scopeManager.currentTypedefs val def = typedefs.stream().filter { it.alias.name.localName == "test" }.findAny().orElse(null) assertNotNull(def) @@ -83,6 +82,7 @@ internal class TypedefTest : BaseTest() { fun testWithModifier() { val result = analyze("cpp", topLevel, true) val variables = result.variables + val typeManager = result.finalCtx.typeManager // pointer val l1ptr = findByUniqueName(variables, "l1ptr") @@ -98,9 +98,9 @@ internal class TypedefTest : BaseTest() { val l2arr = findByUniqueName(variables, "l2arr") val l3arr = findByUniqueName(variables, "l3arr") val l4arr = findByUniqueName(variables, "l4arr") - assertTrue(TypeManager.getInstance().checkArrayAndPointer(l1arr.type, l2arr.type)) - assertTrue(TypeManager.getInstance().checkArrayAndPointer(l1arr.type, l3arr.type)) - assertTrue(TypeManager.getInstance().checkArrayAndPointer(l1arr.type, l4arr.type)) + assertTrue(typeManager.checkArrayAndPointer(l1arr.type, l2arr.type)) + assertTrue(typeManager.checkArrayAndPointer(l1arr.type, l3arr.type)) + assertTrue(typeManager.checkArrayAndPointer(l1arr.type, l4arr.type)) } @Test diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolverTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolverTest.kt index 13faeffb4a..2a5c0cdb0e 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolverTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolverTest.kt @@ -43,7 +43,6 @@ import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.CastExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.Literal import de.fraunhofer.aisec.cpg.graph.types.Type -import de.fraunhofer.aisec.cpg.graph.types.TypeParser import de.fraunhofer.aisec.cpg.graph.types.UnknownType import java.io.File import java.nio.file.Path @@ -154,9 +153,13 @@ class CallResolverTest : BaseTest() { topLevel, true ) + val tu = result.translationUnits.firstOrNull() + assertNotNull(tu) + val records = result.records - val intType = TypeParser.createFrom("int", CPPLanguage()) - val stringType = TypeParser.createFrom("char*", CPPLanguage()) + + val intType = tu.parseType("int") + val stringType = tu.parseType("char*") testMethods(records, intType, stringType) testOverriding(records) diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ReplaceTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ReplaceTest.kt index ddf9e1fee8..8270b09cf3 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ReplaceTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ReplaceTest.kt @@ -25,8 +25,8 @@ */ package de.fraunhofer.aisec.cpg.passes -import de.fraunhofer.aisec.cpg.ScopeManager import de.fraunhofer.aisec.cpg.TranslationConfiguration +import de.fraunhofer.aisec.cpg.TranslationContext import de.fraunhofer.aisec.cpg.frontends.StructTestLanguage import de.fraunhofer.aisec.cpg.frontends.TestLanguage import de.fraunhofer.aisec.cpg.frontends.TestLanguageFrontend @@ -43,16 +43,12 @@ class ReplaceTest { override val frontend: KClass get() = ReplaceTestLanguageFrontend::class - override fun newFrontend( - config: TranslationConfiguration, - scopeManager: ScopeManager - ): TestLanguageFrontend { + override fun newFrontend(ctx: TranslationContext): TestLanguageFrontend { return ReplaceTestLanguageFrontend() } } - class ReplacedPass(config: TranslationConfiguration, scopeManager: ScopeManager) : - EvaluationOrderGraphPass(config, scopeManager) + class ReplacedPass(ctx: TranslationContext) : EvaluationOrderGraphPass(ctx) @Test fun testReplaceAnnotation() { diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/UnresolvedDFGPassTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/UnresolvedDFGPassTest.kt index 4d27c86679..72eb1dfee4 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/UnresolvedDFGPassTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/UnresolvedDFGPassTest.kt @@ -25,11 +25,11 @@ */ package de.fraunhofer.aisec.cpg.passes +import de.fraunhofer.aisec.cpg.GraphExamples.Companion.testFrontend import de.fraunhofer.aisec.cpg.InferenceConfiguration -import de.fraunhofer.aisec.cpg.ScopeManager import de.fraunhofer.aisec.cpg.TranslationConfiguration +import de.fraunhofer.aisec.cpg.TranslationResult import de.fraunhofer.aisec.cpg.frontends.TestLanguage -import de.fraunhofer.aisec.cpg.frontends.TestLanguageFrontend import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.builder.* import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration @@ -97,19 +97,19 @@ class UnresolvedDFGPassTest { companion object { - fun getDfgUnresolvedCalls(inferUnresolved: Boolean) = - TestLanguageFrontend(ScopeManager(), ".").build { - translationResult( - TranslationConfiguration.builder() - .defaultPasses() - .registerLanguage(TestLanguage(".")) - .inferenceConfiguration( - InferenceConfiguration.builder() - .inferDfgForUnresolvedCalls(inferUnresolved) - .build() - ) - .build() - ) { + fun getDfgUnresolvedCalls(inferUnresolved: Boolean): TranslationResult { + val config = + TranslationConfiguration.builder() + .defaultPasses() + .registerLanguage(TestLanguage(".")) + .inferenceConfiguration( + InferenceConfiguration.builder() + .inferDfgForUnresolvedCalls(inferUnresolved) + .build() + ) + .build() + return testFrontend(config).build { + translationResult { translationUnit("DfgUnresolvedCalls.java") { record("DfgUnresolvedCalls") { field("i", t("int")) { modifiers = listOf("private") } @@ -176,5 +176,6 @@ class UnresolvedDFGPassTest { } } } + } } } diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/scopes/ScopeManagerTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/scopes/ScopeManagerTest.kt index 6068a24d66..d6dbeb8ca7 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/scopes/ScopeManagerTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/scopes/ScopeManagerTest.kt @@ -25,10 +25,7 @@ */ package de.fraunhofer.aisec.cpg.passes.scopes -import de.fraunhofer.aisec.cpg.BaseTest -import de.fraunhofer.aisec.cpg.ScopeManager -import de.fraunhofer.aisec.cpg.TranslationConfiguration -import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend +import de.fraunhofer.aisec.cpg.* import de.fraunhofer.aisec.cpg.frontends.TranslationException import de.fraunhofer.aisec.cpg.frontends.cpp.CPPLanguage import de.fraunhofer.aisec.cpg.frontends.cpp.CXXLanguageFrontend @@ -48,21 +45,15 @@ internal class ScopeManagerTest : BaseTest() { config = TranslationConfiguration.builder().defaultPasses().build() } - @Test - @Throws(TranslationException::class) - fun testSetScope() { - val frontend: LanguageFrontend = CXXLanguageFrontend(CPPLanguage(), config, ScopeManager()) - assertEquals(frontend, frontend.scopeManager.lang) - - frontend.scopeManager = ScopeManager() - assertEquals(frontend, frontend.scopeManager.lang) - } - @Test @Throws(TranslationException::class) fun testReplaceNode() { val scopeManager = ScopeManager() - val frontend = CXXLanguageFrontend(CPPLanguage(), config, scopeManager) + val frontend = + CXXLanguageFrontend( + CPPLanguage(), + TranslationContext(config, scopeManager, TypeManager()) + ) val tu = frontend.parse(File("src/test/resources/cxx/recordstmt.cpp")) val methods = tu.allChildren().filter { it !is ConstructorDeclaration } assertFalse(methods.isEmpty()) @@ -86,13 +77,9 @@ internal class ScopeManagerTest : BaseTest() { @Test fun testMerge() { + val tm = TypeManager() val s1 = ScopeManager() - val frontend1 = - CXXLanguageFrontend( - CPPLanguage(), - TranslationConfiguration.builder().build(), - s1, - ) + val frontend1 = CXXLanguageFrontend(CPPLanguage(), TranslationContext(config, s1, tm)) s1.resetToGlobal(frontend1.newTranslationUnitDeclaration("f1.cpp", null)) // build a namespace declaration in f1.cpp with the namespace A @@ -103,12 +90,7 @@ internal class ScopeManagerTest : BaseTest() { s1.leaveScope(namespaceA1) val s2 = ScopeManager() - val frontend2 = - CXXLanguageFrontend( - CPPLanguage(), - TranslationConfiguration.builder().build(), - s2, - ) + val frontend2 = CXXLanguageFrontend(CPPLanguage(), TranslationContext(config, s2, tm)) s2.resetToGlobal(frontend2.newTranslationUnitDeclaration("f1.cpp", null)) // and do the same in the other file @@ -120,12 +102,7 @@ internal class ScopeManagerTest : BaseTest() { // merge the two scopes. this replicates the behaviour of parseParallel val final = ScopeManager() - val frontend = - CXXLanguageFrontend( - CPPLanguage(), - TranslationConfiguration.builder().build(), - final, - ) + val frontend = CXXLanguageFrontend(CPPLanguage(), TranslationContext(config, final, tm)) final.mergeFrom(listOf(s1, s2)) // in the final scope manager, there should only be one NameScope "A" @@ -163,11 +140,7 @@ internal class ScopeManagerTest : BaseTest() { fun testScopeFQN() { val s = ScopeManager() val frontend = - CXXLanguageFrontend( - CPPLanguage(), - TranslationConfiguration.builder().build(), - s, - ) + CXXLanguageFrontend(CPPLanguage(), TranslationContext(config, s, TypeManager())) s.resetToGlobal(frontend.newTranslationUnitDeclaration("file.cpp", null)) assertNull(s.currentNamespace) diff --git a/cpg-core/src/testFixtures/kotlin/de/fraunhofer/aisec/cpg/BaseTest.kt b/cpg-core/src/testFixtures/kotlin/de/fraunhofer/aisec/cpg/BaseTest.kt index 012b445b00..7baf5c8e67 100644 --- a/cpg-core/src/testFixtures/kotlin/de/fraunhofer/aisec/cpg/BaseTest.kt +++ b/cpg-core/src/testFixtures/kotlin/de/fraunhofer/aisec/cpg/BaseTest.kt @@ -25,21 +25,9 @@ */ package de.fraunhofer.aisec.cpg -import de.fraunhofer.aisec.cpg.graph.TypeManager -import de.fraunhofer.aisec.cpg.graph.types.TypeParser -import kotlin.test.BeforeTest import org.slf4j.Logger import org.slf4j.LoggerFactory abstract class BaseTest { protected var log: Logger = LoggerFactory.getLogger(this.javaClass) - - /** - * [TypeParser] and [TypeManager] hold static state. This needs to be cleared before all tests - * in order to avoid strange errors - */ - @BeforeTest - protected fun resetPersistentState() { - TypeManager.reset() - } } diff --git a/cpg-core/src/testFixtures/kotlin/de/fraunhofer/aisec/cpg/TestUtils.kt b/cpg-core/src/testFixtures/kotlin/de/fraunhofer/aisec/cpg/TestUtils.kt index 0e2907a606..d36e20e5b1 100644 --- a/cpg-core/src/testFixtures/kotlin/de/fraunhofer/aisec/cpg/TestUtils.kt +++ b/cpg-core/src/testFixtures/kotlin/de/fraunhofer/aisec/cpg/TestUtils.kt @@ -27,7 +27,6 @@ package de.fraunhofer.aisec.cpg import de.fraunhofer.aisec.cpg.frontends.CompilationDatabase import de.fraunhofer.aisec.cpg.graph.Node -import de.fraunhofer.aisec.cpg.graph.TypeManager import de.fraunhofer.aisec.cpg.graph.declarations.Declaration import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration @@ -39,8 +38,6 @@ import java.util.function.Consumer import java.util.function.Predicate import java.util.stream.Collectors import kotlin.test.* -import org.apache.commons.lang3.reflect.FieldUtils -import org.mockito.Mockito object TestUtils { @@ -191,13 +188,6 @@ object TestUtils { } } - @Throws(IllegalAccessException::class) - fun disableTypeManagerCleanup() { - val spy = Mockito.spy(TypeManager.getInstance()) - Mockito.doNothing().`when`(spy).cleanup() - FieldUtils.writeStaticField(TypeManager::class.java, "instance", spy, true) - } - /** * Compare the given parameter `toCompare` to the start- or end-line of the given node. If the * node has no location `false` is returned. `startLine` is used to specify if the start-line or diff --git a/cpg-core/src/testFixtures/kotlin/de/fraunhofer/aisec/cpg/frontends/TestLanguage.kt b/cpg-core/src/testFixtures/kotlin/de/fraunhofer/aisec/cpg/frontends/TestLanguage.kt index ce9ba9c91e..86d8459301 100644 --- a/cpg-core/src/testFixtures/kotlin/de/fraunhofer/aisec/cpg/frontends/TestLanguage.kt +++ b/cpg-core/src/testFixtures/kotlin/de/fraunhofer/aisec/cpg/frontends/TestLanguage.kt @@ -25,9 +25,9 @@ */ package de.fraunhofer.aisec.cpg.frontends -import de.fraunhofer.aisec.cpg.ScopeManager -import de.fraunhofer.aisec.cpg.TranslationConfiguration +import de.fraunhofer.aisec.cpg.* import de.fraunhofer.aisec.cpg.graph.Node +import de.fraunhofer.aisec.cpg.graph.TypeManager import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration import de.fraunhofer.aisec.cpg.graph.statements.expressions.ProblemExpression import de.fraunhofer.aisec.cpg.graph.types.FloatingPointType @@ -45,7 +45,7 @@ import kotlin.reflect.KClass */ open class TestLanguage(namespaceDelimiter: String = "::") : Language() { override val fileExtensions: List = listOf() - override val namespaceDelimiter: String + final override val namespaceDelimiter: String override val frontend: KClass = TestLanguageFrontend::class override val compoundAssignmentOperators = setOf("+=", "-=", "*=", "/=", "%=", "<<=", ">>=", "&=", "|=", "^=") @@ -66,11 +66,8 @@ open class TestLanguage(namespaceDelimiter: String = "::") : Language = TestLanguage(namespaceDelimiter) -) : - LanguageFrontend( - language, - TranslationConfiguration.builder().build(), - scopeManager, - ) { + language: Language = TestLanguage(namespaceDelimiter), + ctx: TranslationContext = + TranslationContext( + TranslationConfiguration.builder().build(), + ScopeManager(), + TypeManager() + ), +) : LanguageFrontend(language, ctx) { override fun parse(file: File): TranslationUnitDeclaration { TODO("Not yet implemented") } @@ -104,8 +101,5 @@ open class TestLanguageFrontend( } } -class TestHandler : - Handler( - Supplier { ProblemExpression() }, - TestLanguageFrontend() - ) +class TestHandler(frontend: TestLanguageFrontend) : + Handler(Supplier { ProblemExpression() }, frontend) diff --git a/cpg-language-go/src/main/golang/frontend/frontend.go b/cpg-language-go/src/main/golang/frontend/frontend.go index 4dbe97468f..f6d788f34a 100644 --- a/cpg-language-go/src/main/golang/frontend/frontend.go +++ b/cpg-language-go/src/main/golang/frontend/frontend.go @@ -133,6 +133,16 @@ func (g *GoLanguageFrontend) GetLanguage() (l *cpg.Language, err error) { return } +func (g *GoLanguageFrontend) GetCtx() (ctx *cpg.TranslationContext) { + ctx = new(cpg.TranslationContext) + err := g.ObjectRef.CallMethod(env, "getCtx", ctx) + if err != nil { + panic(err) + } + + return +} + func updateCode(fset *token.FileSet, node *cpg.Node, astNode ast.Node) { node.SetCode(code(fset, astNode)) } diff --git a/cpg-language-go/src/main/golang/frontend/handler.go b/cpg-language-go/src/main/golang/frontend/handler.go index 923ced3e45..163cea3897 100644 --- a/cpg-language-go/src/main/golang/frontend/handler.go +++ b/cpg-language-go/src/main/golang/frontend/handler.go @@ -1235,22 +1235,22 @@ func (this *GoLanguageFrontend) handleBasicLit(fset *token.FileSet, lit *ast.Bas case token.STRING: // strip the " value = cpg.NewString(lit.Value[1 : len(lit.Value)-1]) - t = cpg.TypeParser_createFrom("string", lang) + t = cpg.TypeParser_createFrom("string", lang, this.GetCtx()) case token.INT: i, _ := strconv.ParseInt(lit.Value, 10, 64) value = cpg.NewInteger(int(i)) - t = cpg.TypeParser_createFrom("int", lang) + t = cpg.TypeParser_createFrom("int", lang, this.GetCtx()) case token.FLOAT: // default seems to be float64 f, _ := strconv.ParseFloat(lit.Value, 64) value = cpg.NewDouble(f) - t = cpg.TypeParser_createFrom("float64", lang) + t = cpg.TypeParser_createFrom("float64", lang, this.GetCtx()) case token.IMAG: // TODO t = &cpg.UnknownType_getUnknown(lang).Type case token.CHAR: value = cpg.NewString(lit.Value) - t = cpg.TypeParser_createFrom("rune", lang) + t = cpg.TypeParser_createFrom("rune", lang, this.GetCtx()) break } @@ -1384,12 +1384,12 @@ func (this *GoLanguageFrontend) handleType(fset *token.FileSet, typeExpr ast.Exp this.LogTrace("fqn type: %s", name) } - return cpg.TypeParser_createFrom(name, lang) + return cpg.TypeParser_createFrom(name, lang, this.GetCtx()) case *ast.SelectorExpr: // small shortcut fqn := fmt.Sprintf("%s.%s", v.X.(*ast.Ident).Name, v.Sel.Name) this.LogTrace("FQN type: %s", fqn) - return cpg.TypeParser_createFrom(fqn, lang) + return cpg.TypeParser_createFrom(fqn, lang, this.GetCtx()) case *ast.StarExpr: t := this.handleType(fset, v.X) @@ -1417,7 +1417,7 @@ func (this *GoLanguageFrontend) handleType(fset *token.FileSet, typeExpr ast.Exp case *ast.MapType: // we cannot properly represent Golangs built-in map types, yet so we have // to make a shortcut here and represent it as a Java-like map type. - t := cpg.TypeParser_createFrom("map", lang) + t := cpg.TypeParser_createFrom("map", lang, this.GetCtx()) keyType := this.handleType(fset, v.Key) valueType := this.handleType(fset, v.Value) @@ -1428,7 +1428,7 @@ func (this *GoLanguageFrontend) handleType(fset *token.FileSet, typeExpr ast.Exp return t case *ast.ChanType: // handle them similar to maps - t := cpg.TypeParser_createFrom("chan", lang) + t := cpg.TypeParser_createFrom("chan", lang, this.GetCtx()) chanType := this.handleType(fset, v.Value) (*cpg.ObjectType)(t).AddGeneric(chanType) @@ -1484,7 +1484,7 @@ func (this *GoLanguageFrontend) handleType(fset *token.FileSet, typeExpr ast.Exp name += "}" - return cpg.TypeParser_createFrom(name, lang) + return cpg.TypeParser_createFrom(name, lang, this.GetCtx()) case *ast.IndexExpr: // This is a type with one type parameter. First we need to parse the "X" expression as a type var t = this.handleType(fset, v.X) diff --git a/cpg-language-go/src/main/golang/language.go b/cpg-language-go/src/main/golang/language.go index b0cb95bf1b..36d0d504a5 100644 --- a/cpg-language-go/src/main/golang/language.go +++ b/cpg-language-go/src/main/golang/language.go @@ -31,6 +31,8 @@ import ( type Language Node +const TranslationContextClass = CPGPackage + "/TranslationContext" + const FrontendsPackage = CPGPackage + "/frontends" const GolangPackage = FrontendsPackage + "/golang" const LanguageClass = FrontendsPackage + "/Language" @@ -53,3 +55,20 @@ func (l *Language) GetClassName() string { func (l *Language) IsArray() bool { return false } + +func (ctx *TranslationContext) ConvertToGo(o *jnigi.ObjectRef) error { + *ctx = (TranslationContext)(*o) + return nil +} + +func (ctx *TranslationContext) ConvertToJava() (obj *jnigi.ObjectRef, err error) { + return (*jnigi.ObjectRef)(ctx), nil +} + +func (*TranslationContext) GetClassName() string { + return TranslationContextClass +} + +func (*TranslationContext) IsArray() bool { + return false +} diff --git a/cpg-language-go/src/main/golang/types.go b/cpg-language-go/src/main/golang/types.go index 0c03ef5abd..fe1485c284 100644 --- a/cpg-language-go/src/main/golang/types.go +++ b/cpg-language-go/src/main/golang/types.go @@ -39,6 +39,8 @@ var env *jnigi.Env type Type struct{ *jnigi.ObjectRef } type ObjectType Type +type TranslationContext jnigi.ObjectRef + const TypesPackage = GraphPackage + "/types" const TypeClass = TypesPackage + "/Type" const ObjectTypeClass = TypesPackage + "/ObjectType" @@ -83,9 +85,9 @@ func InitEnv(e *jnigi.Env) { env = e } -func TypeParser_createFrom(s string, l *Language) *Type { +func TypeParser_createFrom(s string, l *Language, ctx *TranslationContext) *Type { var t Type - err := env.CallStaticMethod(TypeParserClass, "createFrom", &t, NewCharSequence(s), l) + err := env.CallStaticMethod(TypeParserClass, "createFrom", &t, NewString(s), l, false, ctx) if err != nil { log.Fatal(err) diff --git a/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoLanguage.kt b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoLanguage.kt index 15af1a3147..eaa4dc6863 100644 --- a/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoLanguage.kt +++ b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoLanguage.kt @@ -25,8 +25,6 @@ */ package de.fraunhofer.aisec.cpg.frontends.golang -import de.fraunhofer.aisec.cpg.ScopeManager -import de.fraunhofer.aisec.cpg.TranslationConfiguration import de.fraunhofer.aisec.cpg.frontends.HasGenerics import de.fraunhofer.aisec.cpg.frontends.HasShortCircuitOperators import de.fraunhofer.aisec.cpg.frontends.HasStructs @@ -110,11 +108,4 @@ class GoLanguage : // https://pkg.go.dev/builtin#string "string" to StringType("string", this) ) - - override fun newFrontend( - config: TranslationConfiguration, - scopeManager: ScopeManager, - ): GoLanguageFrontend { - return GoLanguageFrontend(this, config, scopeManager) - } } 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 82e8ebafb3..a615d99d00 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 @@ -25,8 +25,7 @@ */ package de.fraunhofer.aisec.cpg.frontends.golang -import de.fraunhofer.aisec.cpg.ScopeManager -import de.fraunhofer.aisec.cpg.TranslationConfiguration +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.SupportsParallelParsing @@ -40,11 +39,8 @@ import java.io.FileOutputStream @SupportsParallelParsing(false) @RegisterExtraPass(GoExtraPass::class) -class GoLanguageFrontend( - language: Language, - config: TranslationConfiguration, - scopeManager: ScopeManager, -) : LanguageFrontend(language, config, scopeManager) { +class GoLanguageFrontend(language: Language, ctx: TranslationContext) : + LanguageFrontend(language, ctx) { companion object { init { diff --git a/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/GoExtraPass.kt b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/GoExtraPass.kt index 4709f809cd..c422085675 100644 --- a/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/GoExtraPass.kt +++ b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/GoExtraPass.kt @@ -25,10 +25,7 @@ */ package de.fraunhofer.aisec.cpg.passes -import de.fraunhofer.aisec.cpg.ScopeManager -import de.fraunhofer.aisec.cpg.TranslationConfiguration -import de.fraunhofer.aisec.cpg.frontends.Language -import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend +import de.fraunhofer.aisec.cpg.TranslationContext import de.fraunhofer.aisec.cpg.frontends.golang.GoLanguage import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.declarations.IncludeDeclaration @@ -43,7 +40,6 @@ import de.fraunhofer.aisec.cpg.graph.statements.expressions.CastExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression import de.fraunhofer.aisec.cpg.graph.types.PointerType import de.fraunhofer.aisec.cpg.graph.types.Type -import de.fraunhofer.aisec.cpg.graph.types.TypeParser import de.fraunhofer.aisec.cpg.graph.types.UnknownType import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker import de.fraunhofer.aisec.cpg.passes.inference.startInference @@ -108,8 +104,7 @@ import de.fraunhofer.aisec.cpg.passes.order.ExecuteBefore @ExecuteBefore(VariableUsageResolver::class) @ExecuteBefore(CallResolver::class) @ExecuteBefore(DFGPass::class) -class GoExtraPass(config: TranslationConfiguration, scopeManager: ScopeManager) : - ComponentPass(config, scopeManager), ScopeProvider { +class GoExtraPass(ctx: TranslationContext) : ComponentPass(ctx), ScopeProvider { override val scope: Scope? get() = scopeManager.currentScope @@ -148,7 +143,7 @@ class GoExtraPass(config: TranslationConfiguration, scopeManager: ScopeManager) // The key is the first variable. It is always an int val keyVariable = variable.declarations.firstOrNull() as? VariableDeclaration - keyVariable?.type = TypeParser.createFrom("int", forEach.language) + keyVariable?.type = forEach.parseType("int") // The value is the second one. Its type depends on the array type val valueVariable = @@ -215,7 +210,7 @@ class GoExtraPass(config: TranslationConfiguration, scopeManager: ScopeManager) if (namespace.isEmpty()) { scopeManager.globalScope ?.astNode - ?.startInference(scopeManager) + ?.startInference(ctx) ?.createInferredNamespaceDeclaration(include.name, include.filename) } } @@ -234,7 +229,7 @@ class GoExtraPass(config: TranslationConfiguration, scopeManager: ScopeManager) // First, check if this is a built-in type if (language.builtInTypes.contains(callee.name.toString())) { - replaceCallWithCast(callee.name.toString(), language, parent, call) + replaceCallWithCast(callee.name.toString(), parent, call) } else { // If not, then this could still refer to an existing type. We need to make sure // that we take the current namespace into account @@ -245,8 +240,8 @@ class GoExtraPass(config: TranslationConfiguration, scopeManager: ScopeManager) callee.name } - if (TypeManager.getInstance().typeExists(fqn.toString())) { - replaceCallWithCast(fqn, language, parent, call) + if (typeManager.typeExists(fqn.toString())) { + replaceCallWithCast(fqn, parent, call) } } } @@ -254,13 +249,12 @@ class GoExtraPass(config: TranslationConfiguration, scopeManager: ScopeManager) private fun replaceCallWithCast( typeName: CharSequence, - language: Language, parent: Node, call: CallExpression, ) { val cast = parent.newCastExpression(call.code) cast.location = call.location - cast.castType = TypeParser.createFrom(typeName, false, language) + cast.castType = call.parseType(typeName.toString()) cast.expression = call.arguments.single() if (parent !is ArgumentHolder) { diff --git a/cpg-language-go/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoLanguageFrontendTest.kt b/cpg-language-go/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoLanguageFrontendTest.kt index d9dd581779..c2dab38819 100644 --- a/cpg-language-go/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoLanguageFrontendTest.kt +++ b/cpg-language-go/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoLanguageFrontendTest.kt @@ -38,7 +38,6 @@ import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration import de.fraunhofer.aisec.cpg.graph.statements.* import de.fraunhofer.aisec.cpg.graph.statements.expressions.* import de.fraunhofer.aisec.cpg.graph.types.FunctionType -import de.fraunhofer.aisec.cpg.graph.types.TypeParser import java.nio.file.Path import kotlin.test.* @@ -129,7 +128,7 @@ class GoLanguageFrontendTest : BaseTest() { assertNotNull(decl) val new = assertIs(decl.firstAssignment) - assertEquals(TypeParser.createFrom("p.MyStruct*", GoLanguage()), new.type) + assertEquals(tu.parseType("p.MyStruct*"), new.type) val construct = new.initializer as? ConstructExpression assertNotNull(construct) @@ -142,7 +141,7 @@ class GoLanguageFrontendTest : BaseTest() { var make = assertIs(decl.firstAssignment) assertNotNull(make) - assertEquals(TypeParser.createFrom("int[]", GoLanguage()), make.type) + assertEquals(tu.parseType("int[]"), make.type) assertTrue(make is ArrayCreationExpression) @@ -160,7 +159,7 @@ class GoLanguageFrontendTest : BaseTest() { assertTrue(make is ConstructExpression) // TODO: Maps can have dedicated types and parsing them as a generic here is only a // temporary solution. This should be fixed in the future. - assertEquals(TypeParser.createFrom("map[string,string]", GoLanguage()), make.type) + assertEquals(tu.parseType("map[string,string]"), make.type) // make channel @@ -170,7 +169,7 @@ class GoLanguageFrontendTest : BaseTest() { make = assertIs(decl.firstAssignment) assertNotNull(make) assertTrue(make is ConstructExpression) - assertEquals(TypeParser.createFrom("chan[int]", GoLanguage()), make.type) + assertEquals(tu.parseType("chan[int]"), make.type) } @Test @@ -191,26 +190,26 @@ class GoLanguageFrontendTest : BaseTest() { assertNotNull(a.location) assertLocalName("a", a) - assertEquals(TypeParser.createFrom("int", GoLanguage()), a.type) + assertEquals(tu.parseType("int"), a.type) val s = p.variables["s"] assertNotNull(s) assertLocalName("s", s) - assertEquals(TypeParser.createFrom("string", GoLanguage()), s.type) + assertEquals(tu.parseType("string"), s.type) val f = p.variables["f"] assertNotNull(f) assertLocalName("f", f) - assertEquals(TypeParser.createFrom("float64", GoLanguage()), f.type) + assertEquals(tu.parseType("float64"), f.type) val f32 = p.variables["f32"] assertNotNull(f32) assertLocalName("f32", f32) - assertEquals(TypeParser.createFrom("float32", GoLanguage()), f32.type) + assertEquals(tu.parseType("float32"), f32.type) val n = p.variables["n"] assertNotNull(n) - assertEquals(TypeParser.createFrom("int*", GoLanguage()), n.type) + assertEquals(tu.parseType("int*"), n.type) val nil = n.initializer as? Literal<*> assertNotNull(nil) @@ -276,7 +275,7 @@ class GoLanguageFrontendTest : BaseTest() { val s = myTest.parameters.first() assertNotNull(s) assertLocalName("s", s) - assertEquals(TypeParser.createFrom("string", GoLanguage()), s.type) + assertEquals(tu.parseType("string"), s.type) assertLocalName("myTest", myTest) @@ -293,7 +292,7 @@ class GoLanguageFrontendTest : BaseTest() { assertNotNull(literal) assertEquals("%s", literal.value) - assertEquals(TypeParser.createFrom("string", GoLanguage()), literal.type) + assertEquals(tu.parseType("string"), literal.type) val ref = callExpression.arguments[1] as? DeclaredReferenceExpression assertNotNull(ref) @@ -360,7 +359,7 @@ class GoLanguageFrontendTest : BaseTest() { val myField = fields.first() assertLocalName("MyField", myField) - assertEquals(TypeParser.createFrom("int", GoLanguage()), myField.type) + assertEquals(tu.parseType("int"), myField.type) val myInterface = p.getDeclarationsByName("p.MyInterface", RecordDeclaration::class.java) @@ -461,7 +460,7 @@ class GoLanguageFrontendTest : BaseTest() { assertNotNull(lhs) assertEquals(myFunc.receiver, (lhs.base as? DeclaredReferenceExpression)?.refersTo) assertLocalName("Field", lhs) - assertEquals(TypeParser.createFrom("int", GoLanguage()), lhs.type) + assertEquals(tu.parseType("int"), lhs.type) val rhs = assign.rhs.firstOrNull() as? DeclaredReferenceExpression assertNotNull(rhs) @@ -489,7 +488,7 @@ class GoLanguageFrontendTest : BaseTest() { assertNotNull(b) assertLocalName("b", b) - assertEquals(TypeParser.createFrom("bool", GoLanguage()), b.type) + assertEquals(tu.parseType("bool"), b.type) // true, false are builtin variables, NOT literals in Golang // we might need to parse this special case differently @@ -588,7 +587,7 @@ class GoLanguageFrontendTest : BaseTest() { assertNotNull(c) // type will be inferred from the function declaration - assertEquals(TypeParser.createFrom("p.MyStruct*", GoLanguage()), c.type) + assertEquals(tu.parseType("p.MyStruct*"), c.type) val newMyStruct = assertIs(c.firstAssignment) diff --git a/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/DeclarationHandler.kt b/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/DeclarationHandler.kt index 934d03b3b8..59900c62db 100644 --- a/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/DeclarationHandler.kt +++ b/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/DeclarationHandler.kt @@ -125,11 +125,10 @@ open class DeclarationHandler(lang: JavaLanguageFrontend) : ) for (parameter in methodDecl.parameters) { var resolvedType: Type? = - TypeManager.getInstance() - .getTypeParameter( - functionDeclaration.recordDeclaration, - parameter.type.toString() - ) + frontend.typeManager.getTypeParameter( + functionDeclaration.recordDeclaration, + parameter.type.toString() + ) if (resolvedType == null) { resolvedType = frontend.getTypeAsGoodAsPossible(parameter, parameter.resolve()) } @@ -196,11 +195,10 @@ open class DeclarationHandler(lang: JavaLanguageFrontend) : recordDeclaration.implementedInterfaces = classInterDecl.implementedTypes.map { type -> frontend.getTypeAsGoodAsPossible(type) } - TypeManager.getInstance() - .addTypeParameter( - recordDeclaration, - classInterDecl.typeParameters.map { ParameterizedType(it.nameAsString, language) } - ) + frontend.typeManager.addTypeParameter( + recordDeclaration, + classInterDecl.typeParameters.map { ParameterizedType(it.nameAsString, language) } + ) // TODO: I cannot replicate the old partionedBy logic val staticImports = @@ -334,11 +332,10 @@ open class DeclarationHandler(lang: JavaLanguageFrontend) : try { // Resolve type first with ParameterizedType type = - TypeManager.getInstance() - .getTypeParameter( - frontend.scopeManager.currentRecord, - variable.resolve().type.describe() - ) + frontend.typeManager.getTypeParameter( + frontend.scopeManager.currentRecord, + variable.resolve().type.describe() + ) ?: this.parseType(joinedModifiers + variable.resolve().type.describe()) } catch (e: UnsolvedSymbolException) { val t = frontend.recoverTypeFromUnsolvedException(e) 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 42bed2495a..cacbab7f16 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 @@ -379,11 +379,10 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : try { val symbol = fieldAccessExpr.resolve() fieldType = - TypeManager.getInstance() - .getTypeParameter( - frontend.scopeManager.currentRecord, - symbol.asField().type.describe() - ) + frontend.typeManager.getTypeParameter( + frontend.scopeManager.currentRecord, + symbol.asField().type.describe() + ) if (fieldType == null) { fieldType = this.parseType(symbol.asField().type.describe()) } @@ -583,11 +582,10 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : } else { // Resolve type first with ParameterizedType var type: Type? = - TypeManager.getInstance() - .getTypeParameter( - frontend.scopeManager.currentRecord, - symbol.type.describe() - ) + frontend.typeManager.getTypeParameter( + frontend.scopeManager.currentRecord, + symbol.type.describe() + ) if (type == null) { type = this.parseType(symbol.type.describe()) } @@ -850,7 +848,7 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : frontend.scopeManager.enterScope(anonymousRecord) - anonymousRecord.addSuperClass(TypeParser.createFrom(constructorName, language)) + anonymousRecord.addSuperClass(parseType(constructorName)) val anonymousClassBody = objectCreationExpr.anonymousClassBody.get() for (classBody in anonymousClassBody) { // Whatever is implemented in the anonymous class has to be added to the record diff --git a/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/JavaLanguage.kt b/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/JavaLanguage.kt index 8e5de553e8..e932fdbc63 100644 --- a/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/JavaLanguage.kt +++ b/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/JavaLanguage.kt @@ -27,7 +27,6 @@ package de.fraunhofer.aisec.cpg.frontends.java import com.fasterxml.jackson.annotation.JsonIgnore import de.fraunhofer.aisec.cpg.ScopeManager -import de.fraunhofer.aisec.cpg.TranslationConfiguration import de.fraunhofer.aisec.cpg.frontends.* import de.fraunhofer.aisec.cpg.graph.Name import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration @@ -107,13 +106,6 @@ open class JavaLanguage : } else super.propagateTypeOfBinaryOperation(operation) } - override fun newFrontend( - config: TranslationConfiguration, - scopeManager: ScopeManager, - ): JavaLanguageFrontend { - return JavaLanguageFrontend(this, config, scopeManager) - } - override fun handleSuperCall( callee: MemberExpression, curClass: RecordDeclaration, 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 1aa4ea007d..049f92382e 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 @@ -45,8 +45,7 @@ import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFacade import com.github.javaparser.symbolsolver.resolution.typesolvers.CombinedTypeSolver import com.github.javaparser.symbolsolver.resolution.typesolvers.JavaParserTypeSolver import com.github.javaparser.symbolsolver.resolution.typesolvers.ReflectionTypeSolver -import de.fraunhofer.aisec.cpg.ScopeManager -import de.fraunhofer.aisec.cpg.TranslationConfiguration +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.TranslationException @@ -71,11 +70,8 @@ import java.util.function.Consumer @RegisterExtraPass( JavaExternalTypeHierarchyResolver::class ) // this pass is always required for Java -open class JavaLanguageFrontend( - language: Language, - config: TranslationConfiguration, - scopeManager: ScopeManager, -) : LanguageFrontend(language, config, scopeManager) { +open class JavaLanguageFrontend(language: Language, ctx: TranslationContext) : + LanguageFrontend(language, ctx) { var context: CompilationUnit? = null var javaSymbolResolver: JavaSymbolSolver? @@ -351,8 +347,10 @@ open class JavaLanguageFrontend( return try { // Resolve type first with ParameterizedType var type: de.fraunhofer.aisec.cpg.graph.types.Type? = - TypeManager.getInstance() - .getTypeParameter(scopeManager.currentRecord, resolved.returnType.describe()) + typeManager.getTypeParameter( + scopeManager.currentRecord, + resolved.returnType.describe() + ) if (type == null) { type = parseType(resolved.returnType.describe()) } diff --git a/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/JavaCallResolverHelper.kt b/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/JavaCallResolverHelper.kt index 9ef45aa1a5..13a334c34c 100644 --- a/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/JavaCallResolverHelper.kt +++ b/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/JavaCallResolverHelper.kt @@ -30,10 +30,10 @@ import de.fraunhofer.aisec.cpg.frontends.java.JavaLanguage import de.fraunhofer.aisec.cpg.graph.Name import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration +import de.fraunhofer.aisec.cpg.graph.parseType import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberCallExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberExpression -import de.fraunhofer.aisec.cpg.graph.types.TypeParser import de.fraunhofer.aisec.cpg.helpers.Util import de.fraunhofer.aisec.cpg.passes.CallResolver.Companion.LOGGER @@ -111,9 +111,7 @@ class JavaCallResolverHelper { ): RecordDeclaration? { val baseName = callee.base.name.parent ?: return null - if ( - TypeParser.createFrom(baseName, curClass.language) in curClass.implementedInterfaces - ) { + if (curClass.parseType(baseName) in curClass.implementedInterfaces) { // Basename is an interface -> BaseName.super refers to BaseName itself return recordMap[baseName] } else { diff --git a/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/JavaExternalTypeHierarchyResolver.kt b/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/JavaExternalTypeHierarchyResolver.kt index 3d85b4b1d8..55834f49d1 100644 --- a/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/JavaExternalTypeHierarchyResolver.kt +++ b/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/JavaExternalTypeHierarchyResolver.kt @@ -29,13 +29,11 @@ import com.github.javaparser.resolution.UnsolvedSymbolException import com.github.javaparser.symbolsolver.resolution.typesolvers.CombinedTypeSolver import com.github.javaparser.symbolsolver.resolution.typesolvers.JavaParserTypeSolver import com.github.javaparser.symbolsolver.resolution.typesolvers.ReflectionTypeSolver -import de.fraunhofer.aisec.cpg.ScopeManager -import de.fraunhofer.aisec.cpg.TranslationConfiguration +import de.fraunhofer.aisec.cpg.TranslationContext +import de.fraunhofer.aisec.cpg.frontends.java.JavaLanguage import de.fraunhofer.aisec.cpg.frontends.java.JavaLanguageFrontend -import de.fraunhofer.aisec.cpg.graph.Component -import de.fraunhofer.aisec.cpg.graph.TypeManager +import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.types.Type -import de.fraunhofer.aisec.cpg.graph.types.TypeParser import de.fraunhofer.aisec.cpg.helpers.CommonPath import de.fraunhofer.aisec.cpg.passes.order.DependsOn import de.fraunhofer.aisec.cpg.passes.order.ExecuteBefore @@ -45,11 +43,13 @@ import org.slf4j.LoggerFactory @DependsOn(TypeHierarchyResolver::class) @ExecuteBefore(ImportResolver::class) @RequiredFrontend(JavaLanguageFrontend::class) -class JavaExternalTypeHierarchyResolver( - config: TranslationConfiguration, - scopeManager: ScopeManager -) : ComponentPass(config, scopeManager) { +class JavaExternalTypeHierarchyResolver(ctx: TranslationContext) : ComponentPass(ctx) { override fun accept(component: Component) { + val provider = + object : ContextProvider, LanguageProvider { + override val language = JavaLanguage() + override val ctx: TranslationContext = this@JavaExternalTypeHierarchyResolver.ctx + } val resolver = CombinedTypeSolver() resolver.add(ReflectionTypeSolver()) @@ -67,10 +67,8 @@ class JavaExternalTypeHierarchyResolver( resolver.add(JavaParserTypeSolver(root)) } - val tm = TypeManager.getInstance() - // Iterate over all known types and add their (direct) supertypes. - for (t in HashSet(tm.firstOrderTypes)) { + for (t in HashSet(typeManager.firstOrderTypes)) { // TODO: Do we have to check if the type's language is JavaLanguage? val symbol = resolver.tryToSolveType(t.typeName) if (symbol.isSolved) { @@ -78,7 +76,7 @@ class JavaExternalTypeHierarchyResolver( val resolvedSuperTypes = symbol.correspondingDeclaration.getAncestors(true) for (anc in resolvedSuperTypes) { // Add all resolved supertypes to the type. - val superType = TypeParser.createFrom(anc.qualifiedName, t.language) + val superType = provider.parseType(anc.qualifiedName) superType.typeOrigin = Type.Origin.RESOLVED t.superTypes.add(superType) } diff --git a/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/java/JavaLanguageFrontendTest.kt b/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/java/JavaLanguageFrontendTest.kt index 6ecb9932d2..5f9a5b5810 100644 --- a/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/java/JavaLanguageFrontendTest.kt +++ b/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/java/JavaLanguageFrontendTest.kt @@ -27,7 +27,6 @@ package de.fraunhofer.aisec.cpg.frontends.java import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration import de.fraunhofer.aisec.cpg.* -import de.fraunhofer.aisec.cpg.ScopeManager import de.fraunhofer.aisec.cpg.TestUtils.analyzeAndGetFirstTU import de.fraunhofer.aisec.cpg.TestUtils.analyzeWithBuilder import de.fraunhofer.aisec.cpg.TestUtils.findByUniqueName @@ -38,7 +37,6 @@ import de.fraunhofer.aisec.cpg.graph.declarations.* import de.fraunhofer.aisec.cpg.graph.statements.* import de.fraunhofer.aisec.cpg.graph.statements.expressions.* import de.fraunhofer.aisec.cpg.graph.types.FunctionType -import de.fraunhofer.aisec.cpg.graph.types.TypeParser import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker import de.fraunhofer.aisec.cpg.sarif.Region import java.io.File @@ -144,7 +142,7 @@ internal class JavaLanguageFrontendTest : BaseTest() { val sDecl = s.singleDeclaration as? VariableDeclaration assertNotNull(sDecl) assertLocalName("s", sDecl) - assertEquals(createTypeFrom("java.lang.String"), sDecl.type) + assertEquals(tu.parseType("java.lang.String"), sDecl.type) // should contain a single statement val sce = forEachStatement.statement as? MemberCallExpression @@ -191,11 +189,11 @@ internal class JavaLanguageFrontendTest : BaseTest() { assertNotNull(scope) // first exception type was? resolved, so we can expect a FQN - assertEquals(createTypeFrom("java.lang.NumberFormatException"), firstCatch.parameter?.type) + assertEquals(tu.parseType("java.lang.NumberFormatException"), firstCatch.parameter?.type) // second one could not be resolved so we do not have an FQN - assertEquals(createTypeFrom("NotResolvableTypeException"), catchClauses[1].parameter?.type) + assertEquals(tu.parseType("NotResolvableTypeException"), catchClauses[1].parameter?.type) // third type should have been resolved through the import - assertEquals(createTypeFrom("some.ImportedException"), (catchClauses[2].parameter)?.type) + assertEquals(tu.parseType("some.ImportedException"), (catchClauses[2].parameter)?.type) // and 1 finally val finallyBlock = tryStatement.finallyBlock @@ -285,14 +283,14 @@ internal class JavaLanguageFrontendTest : BaseTest() { @Test fun testRecordDeclaration() { val file = File("src/test/resources/compiling/RecordDeclaration.java") - val declaration = + val tu = analyzeAndGetFirstTU(listOf(file), file.parentFile.toPath(), true) { it.registerLanguage(JavaLanguage()) } // TODO: Use GraphExamples here as well. - assertNotNull(declaration) + assertNotNull(tu) - val namespaceDeclaration = declaration.getDeclarationAs(0, NamespaceDeclaration::class.java) + val namespaceDeclaration = tu.getDeclarationAs(0, NamespaceDeclaration::class.java) val recordDeclaration = namespaceDeclaration?.getDeclarationAs(0, RecordDeclaration::class.java) @@ -305,7 +303,7 @@ internal class JavaLanguageFrontendTest : BaseTest() { assertNotNull(method) assertEquals(recordDeclaration, method.recordDeclaration) assertLocalName("method", method) - assertEquals(createTypeFrom("java.lang.Integer"), method.returnTypes.firstOrNull()) + assertEquals(tu.parseType("java.lang.Integer"), method.returnTypes.firstOrNull()) val functionType = method.type as? FunctionType assertNotNull(functionType) @@ -427,7 +425,7 @@ internal class JavaLanguageFrontendTest : BaseTest() { assertNotNull(a) // type should be Integer[] - assertEquals(createTypeFrom("int[]"), a.type) + assertEquals(tu.parseType("int[]"), a.type) // it has an array creation initializer val ace = a.initializer as? ArrayCreationExpression @@ -635,17 +633,14 @@ internal class JavaLanguageFrontendTest : BaseTest() { (lhs?.base as? DeclaredReferenceExpression)?.refersTo as? VariableDeclaration? assertNotNull(receiver) assertLocalName("this", receiver) - assertEquals(createTypeFrom("my.Animal"), receiver.type) + assertEquals(tu.parseType("my.Animal"), receiver.type) } @Test fun testOverrideHandler() { /** A simple extension of the [JavaLanguageFrontend] to demonstrate handler overriding. */ - class MyJavaLanguageFrontend( - language: JavaLanguage, - config: TranslationConfiguration, - scopeManager: ScopeManager, - ) : JavaLanguageFrontend(language, config, scopeManager) { + class MyJavaLanguageFrontend(language: JavaLanguage, ctx: TranslationContext) : + JavaLanguageFrontend(language, ctx) { init { this.declarationHandler = object : DeclarationHandler(this@MyJavaLanguageFrontend) { @@ -673,13 +668,6 @@ internal class JavaLanguageFrontendTest : BaseTest() { override val namespaceDelimiter = "." override val superClassKeyword = "super" override val frontend = MyJavaLanguageFrontend::class - - override fun newFrontend( - config: TranslationConfiguration, - scopeManager: ScopeManager, - ): MyJavaLanguageFrontend { - return MyJavaLanguageFrontend(this, config, scopeManager) - } } val file = File("src/test/resources/compiling/RecordDeclaration.java") @@ -786,8 +774,6 @@ internal class JavaLanguageFrontendTest : BaseTest() { assertSame(ref, thisOuterClass) } - private fun createTypeFrom(typename: String) = TypeParser.createFrom(typename, JavaLanguage()) - @Test fun testForEach() { val file = File("src/test/resources/compiling/ForEach.java") diff --git a/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/types/TypeTests.kt b/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/types/TypeTests.kt index 97d6e6aff2..65ebf1fbf3 100644 --- a/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/types/TypeTests.kt +++ b/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/types/TypeTests.kt @@ -25,14 +25,12 @@ */ package de.fraunhofer.aisec.cpg.graph.types -import de.fraunhofer.aisec.cpg.BaseTest +import de.fraunhofer.aisec.cpg.* import de.fraunhofer.aisec.cpg.TestUtils.analyze -import de.fraunhofer.aisec.cpg.TestUtils.disableTypeManagerCleanup import de.fraunhofer.aisec.cpg.TestUtils.findByName import de.fraunhofer.aisec.cpg.TestUtils.findByUniqueName -import de.fraunhofer.aisec.cpg.TranslationResult -import de.fraunhofer.aisec.cpg.assertLocalName import de.fraunhofer.aisec.cpg.frontends.java.JavaLanguage +import de.fraunhofer.aisec.cpg.frontends.java.JavaLanguageFrontend import de.fraunhofer.aisec.cpg.graph.* import java.nio.file.Path import java.util.* @@ -45,78 +43,89 @@ internal class TypeTests : BaseTest() { var result: Type var expected: Type - // Test 1: Ignore Access Modifier Keyword (public, private, protected) - var typeString = "private int a" - result = TypeParser.createFrom(typeString, JavaLanguage()) - expected = IntegerType("int", 32, JavaLanguage(), NumericType.Modifier.SIGNED) - assertEquals(expected, result) - - // Test 2: constant type using final - typeString = "final int a" - result = TypeParser.createFrom(typeString, JavaLanguage()) - expected = IntegerType("int", 32, JavaLanguage(), NumericType.Modifier.SIGNED) - assertEquals(expected, result) - - // Test 3: static type - typeString = "static int a" - result = TypeParser.createFrom(typeString, JavaLanguage()) - expected = IntegerType("int", 32, JavaLanguage(), NumericType.Modifier.SIGNED) - assertEquals(expected, result) - - // Test 4: volatile type - typeString = "public volatile int a" - result = TypeParser.createFrom(typeString, JavaLanguage()) - expected = IntegerType("int", 32, JavaLanguage(), NumericType.Modifier.SIGNED) - assertEquals(expected, result) - - // Test 5: combining a storage type and a qualifier - typeString = "private static final String a" - result = TypeParser.createFrom(typeString, JavaLanguage()) - expected = StringType("java.lang.String", JavaLanguage()) - assertEquals(expected, result) - - // Test 6: using two different qualifiers - typeString = "public final volatile int a" - result = TypeParser.createFrom(typeString, JavaLanguage()) - expected = IntegerType("int", 32, JavaLanguage(), NumericType.Modifier.SIGNED) - assertEquals(expected, result) - - // Test 7: Reference level using arrays - typeString = "int[] a" - result = TypeParser.createFrom(typeString, JavaLanguage()) - expected = - PointerType( - IntegerType("int", 32, JavaLanguage(), NumericType.Modifier.SIGNED), - PointerType.PointerOrigin.ARRAY + with( + JavaLanguageFrontend( + JavaLanguage(), + TranslationContext( + TranslationConfiguration.builder().build(), + ScopeManager(), + TypeManager() + ) ) - assertEquals(expected, result) - - // Test 8: generics - typeString = "List list" - result = TypeParser.createFrom(typeString, JavaLanguage()) - var generics = mutableListOf() - generics.add(StringType("java.lang.String", JavaLanguage())) - expected = ObjectType("List", generics, false, JavaLanguage()) - assertEquals(expected, result) - - // Test 9: more generics - typeString = "List>, List> data" - result = TypeParser.createFrom(typeString, JavaLanguage()) - val genericStringType = StringType("java.lang.String", JavaLanguage()) - val generics3: MutableList = ArrayList() - generics3.add(genericStringType) - val genericElement3 = ObjectType("List", generics3, false, JavaLanguage()) - val generics2a: MutableList = ArrayList() - generics2a.add(genericElement3) - val generics2b: MutableList = ArrayList() - generics2b.add(genericStringType) - val genericElement1 = ObjectType("List", generics2a, false, JavaLanguage()) - val genericElement2 = ObjectType("List", generics2b, false, JavaLanguage()) - generics = ArrayList() - generics.add(genericElement1) - generics.add(genericElement2) - expected = ObjectType("List", generics, false, JavaLanguage()) - assertEquals(expected, result) + ) { + // Test 1: Ignore Access Modifier Keyword (public, private, protected) + var typeString = "private int a" + result = parseType(typeString) + expected = IntegerType("int", 32, JavaLanguage(), NumericType.Modifier.SIGNED) + assertEquals(expected, result) + + // Test 2: constant type using final + typeString = "final int a" + result = parseType(typeString) + expected = IntegerType("int", 32, JavaLanguage(), NumericType.Modifier.SIGNED) + assertEquals(expected, result) + + // Test 3: static type + typeString = "static int a" + result = parseType(typeString) + expected = IntegerType("int", 32, JavaLanguage(), NumericType.Modifier.SIGNED) + assertEquals(expected, result) + + // Test 4: volatile type + typeString = "public volatile int a" + result = parseType(typeString) + expected = IntegerType("int", 32, JavaLanguage(), NumericType.Modifier.SIGNED) + assertEquals(expected, result) + + // Test 5: combining a storage type and a qualifier + typeString = "private static final String a" + result = parseType(typeString) + expected = StringType("java.lang.String", JavaLanguage()) + assertEquals(expected, result) + + // Test 6: using two different qualifiers + typeString = "public final volatile int a" + result = parseType(typeString) + expected = IntegerType("int", 32, JavaLanguage(), NumericType.Modifier.SIGNED) + assertEquals(expected, result) + + // Test 7: Reference level using arrays + typeString = "int[] a" + result = parseType(typeString) + expected = + PointerType( + IntegerType("int", 32, JavaLanguage(), NumericType.Modifier.SIGNED), + PointerType.PointerOrigin.ARRAY + ) + assertEquals(expected, result) + + // Test 8: generics + typeString = "List list" + result = parseType(typeString) + var generics = mutableListOf() + generics.add(StringType("java.lang.String", JavaLanguage())) + expected = ObjectType("List", generics, false, JavaLanguage()) + assertEquals(expected, result) + + // Test 9: more generics + typeString = "List>, List> data" + result = parseType(typeString) + val genericStringType = StringType("java.lang.String", JavaLanguage()) + val generics3: MutableList = ArrayList() + generics3.add(genericStringType) + val genericElement3 = ObjectType("List", generics3, false, JavaLanguage()) + val generics2a: MutableList = ArrayList() + generics2a.add(genericElement3) + val generics2b: MutableList = ArrayList() + generics2b.add(genericStringType) + val genericElement1 = ObjectType("List", generics2a, false, JavaLanguage()) + val genericElement2 = ObjectType("List", generics2b, false, JavaLanguage()) + generics = ArrayList() + generics.add(genericElement1) + generics.add(genericElement2) + expected = ObjectType("List", generics, false, JavaLanguage()) + assertEquals(expected, result) + } } // Tests on the resulting graph @@ -127,12 +136,17 @@ internal class TypeTests : BaseTest() { val result = analyze("java", topLevel, true) { it.registerLanguage(JavaLanguage()) } // Check Parameterized + val recordDeclarations = result.records + val recordDeclarationBox = findByUniqueName(recordDeclarations, "Box") + val typeT = result.finalCtx.typeManager.getTypeParameter(recordDeclarationBox, "T") + assertNotNull(typeT) + assertIs(typeT) + assertLocalName("T", typeT) + assertEquals(typeT, result.finalCtx.typeManager.getTypeParameter(recordDeclarationBox, "T")) + // Type of field t val fieldDeclarations = result.fields val fieldDeclarationT = findByUniqueName(fieldDeclarations, "t") - val typeT = fieldDeclarationT.type - assertIs(typeT) - assertLocalName("T", typeT) assertTrue(fieldDeclarationT.possibleSubTypes.contains(typeT)) // Parameter of set Method @@ -198,16 +212,34 @@ internal class TypeTests : BaseTest() { @Throws(Exception::class) @Test fun testCommonTypeTestJava() { - disableTypeManagerCleanup() - val topLevel = Path.of("src", "test", "resources", "compiling", "hierarchy") - val result = analyze("java", topLevel, true) { it.registerLanguage(JavaLanguage()) } - val root = TypeParser.createFrom("multistep.Root", JavaLanguage()) - val level0 = TypeParser.createFrom("multistep.Level0", JavaLanguage()) - val level1 = TypeParser.createFrom("multistep.Level1", JavaLanguage()) - val level1b = TypeParser.createFrom("multistep.Level1B", JavaLanguage()) - val level2 = TypeParser.createFrom("multistep.Level2", JavaLanguage()) - val unrelated = TypeParser.createFrom("multistep.Unrelated", JavaLanguage()) - getCommonTypeTestGeneral(root, level0, level1, level1b, level2, unrelated, result) + with( + JavaLanguageFrontend( + JavaLanguage(), + TranslationContext( + TranslationConfiguration.builder().build(), + ScopeManager(), + TypeManager() + ) + ) + ) { + val topLevel = Path.of("src", "test", "resources", "compiling", "hierarchy") + val result = analyze("java", topLevel, true) { it.registerLanguage(JavaLanguage()) } + val root = parseType("multistep.Root") + val level0 = parseType("multistep.Level0") + val level1 = parseType("multistep.Level1") + val level1b = parseType("multistep.Level1B") + val level2 = parseType("multistep.Level2") + val unrelated = parseType("multistep.Unrelated") + getCommonTypeTestGeneral( + root, + level0, + level1, + level1b, + level2, + unrelated, + result.finalCtx + ) + } } private fun getCommonTypeTestGeneral( @@ -217,7 +249,7 @@ internal class TypeTests : BaseTest() { level1b: Type, level2: Type, unrelated: Type, - result: TranslationResult + ctx: TranslationContext ) { /* Type hierarchy: @@ -229,56 +261,44 @@ internal class TypeTests : BaseTest() { | Level2 */ - val provider = result.scopeManager + val provider = ctx.scopeManager // A single type is its own least common ancestor for (t in listOf(root, level0, level1, level1b, level2)) { - assertEquals( - Optional.of(t), - TypeManager.getInstance().getCommonType(listOf(t), provider) - ) + assertEquals(Optional.of(t), ctx.typeManager.getCommonType(listOf(t), ctx)) } // Root is the root of all types for (t in listOf(level0, level1, level1b, level2)) { - assertEquals( - Optional.of(root), - TypeManager.getInstance().getCommonType(listOf(t, root), provider) - ) + assertEquals(Optional.of(root), ctx.typeManager.getCommonType(listOf(t, root), ctx)) } // Level0 is above all types but Root for (t in listOf(level1, level1b, level2)) { - assertEquals( - Optional.of(level0), - TypeManager.getInstance().getCommonType(listOf(t, level0), provider) - ) + assertEquals(Optional.of(level0), ctx.typeManager.getCommonType(listOf(t, level0), ctx)) } // Level1 and Level1B have Level0 as common ancestor assertEquals( Optional.of(level0), - TypeManager.getInstance().getCommonType(listOf(level1, level1b), provider) + ctx.typeManager.getCommonType(listOf(level1, level1b), ctx) ) // Level2 and Level1B have Level0 as common ancestor assertEquals( Optional.of(level0), - TypeManager.getInstance().getCommonType(listOf(level2, level1b), provider) + ctx.typeManager.getCommonType(listOf(level2, level1b), ctx) ) // Level1 and Level2 have Level1 as common ancestor assertEquals( Optional.of(level1), - TypeManager.getInstance().getCommonType(listOf(level1, level2), provider) + ctx.typeManager.getCommonType(listOf(level1, level2), ctx) ) // Check unrelated type behavior: No common root class for (t in listOf(root, level0, level1, level1b, level2)) { - assertEquals( - Optional.empty(), - TypeManager.getInstance().getCommonType(listOf(unrelated, t), provider) - ) + assertEquals(Optional.empty(), ctx.typeManager.getCommonType(listOf(unrelated, t), ctx)) } } } diff --git a/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolverTest.kt b/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolverTest.kt index ec5fbbb98a..8bd719e38c 100644 --- a/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolverTest.kt +++ b/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolverTest.kt @@ -36,7 +36,6 @@ import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression import de.fraunhofer.aisec.cpg.graph.types.Type -import de.fraunhofer.aisec.cpg.graph.types.TypeParser import java.nio.file.Path import kotlin.test.* @@ -121,9 +120,12 @@ class CallResolverTest : BaseTest() { fun testJava() { val result = TestUtils.analyze("java", topLevel, true) { it.registerLanguage(JavaLanguage()) } + val tu = result.translationUnits.firstOrNull() + assertNotNull(tu) + val records = result.records - val intType = TypeParser.createFrom("int", JavaLanguage()) - val stringType = TypeParser.createFrom("java.lang.String", JavaLanguage()) + val intType = tu.parseType("int") + val stringType = tu.parseType(("java.lang.String")) testMethods(records, intType, stringType) testOverriding(records) ensureNoUnknownClassDummies(records) diff --git a/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/LLVMIRLanguage.kt b/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/LLVMIRLanguage.kt index 0106a8f49d..abb40b6366 100644 --- a/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/LLVMIRLanguage.kt +++ b/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/LLVMIRLanguage.kt @@ -25,8 +25,6 @@ */ package de.fraunhofer.aisec.cpg.frontends.llvm -import de.fraunhofer.aisec.cpg.ScopeManager -import de.fraunhofer.aisec.cpg.TranslationConfiguration import de.fraunhofer.aisec.cpg.frontends.Language import de.fraunhofer.aisec.cpg.graph.types.FloatingPointType import de.fraunhofer.aisec.cpg.graph.types.IntegerType @@ -60,11 +58,4 @@ class LLVMIRLanguage : Language() { "x86_fp80" to FloatingPointType("x86_fp80", 80, this, NumericType.Modifier.SIGNED), "ppc_fp128" to FloatingPointType("ppc_fp128", 128, this, NumericType.Modifier.SIGNED), ) - - override fun newFrontend( - config: TranslationConfiguration, - scopeManager: ScopeManager, - ): LLVMIRLanguageFrontend { - return LLVMIRLanguageFrontend(this, config, scopeManager) - } } diff --git a/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/LLVMIRLanguageFrontend.kt b/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/LLVMIRLanguageFrontend.kt index ea3591d8c9..9eb7bf5080 100644 --- a/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/LLVMIRLanguageFrontend.kt +++ b/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/LLVMIRLanguageFrontend.kt @@ -25,14 +25,14 @@ */ package de.fraunhofer.aisec.cpg.frontends.llvm -import de.fraunhofer.aisec.cpg.ScopeManager -import de.fraunhofer.aisec.cpg.TranslationConfiguration +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.TranslationException import de.fraunhofer.aisec.cpg.graph.declarations.Declaration import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration +import de.fraunhofer.aisec.cpg.graph.newTranslationUnitDeclaration import de.fraunhofer.aisec.cpg.graph.newUnknownType import de.fraunhofer.aisec.cpg.graph.parseType import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression @@ -51,11 +51,8 @@ import org.bytedeco.llvm.LLVM.* import org.bytedeco.llvm.global.LLVM.* @RegisterExtraPass(CompressLLVMPass::class) -class LLVMIRLanguageFrontend( - language: Language, - config: TranslationConfiguration, - scopeManager: ScopeManager, -) : LanguageFrontend(language, config, scopeManager) { +class LLVMIRLanguageFrontend(language: Language, ctx: TranslationContext) : + LanguageFrontend(language, ctx) { val statementHandler = StatementHandler(this) val declarationHandler = DeclarationHandler(this) @@ -64,7 +61,7 @@ class LLVMIRLanguageFrontend( val phiList = mutableListOf() - var ctx: LLVMContextRef? = null + var ctxRef: LLVMContextRef? = null /** * This contains a cache binding between an LLVMValueRef (representing a variable) and its @@ -90,11 +87,11 @@ class LLVMIRLanguageFrontend( val buf = LLVMMemoryBufferRef() // create a new LLVM context - ctx = LLVMContextCreate() + ctxRef = LLVMContextCreate() // disable opaque pointers, until all necessary new functions are available in the C API. // See https://llvm.org/docs/OpaquePointers.html - LLVMContextSetOpaquePointers(ctx, 0) + LLVMContextSetOpaquePointers(ctxRef, 0) // allocate a buffer for a possible error message val errorMessage = ByteBuffer.allocate(10000) @@ -108,22 +105,21 @@ class LLVMIRLanguageFrontend( if (result != 0) { // something went wrong val errorMsg = String(errorMessage.array()) - LLVMContextDispose(ctx) + LLVMContextDispose(ctxRef) throw TranslationException("Could not create memory buffer: $errorMsg") } - result = LLVMParseIRInContext(ctx, buf, mod, errorMessage) + result = LLVMParseIRInContext(ctxRef, buf, mod, errorMessage) if (result != 0) { // something went wrong val errorMsg = String(errorMessage.array()) - LLVMContextDispose(ctx) + LLVMContextDispose(ctxRef) throw TranslationException("Could not parse IR: $errorMsg") } bench.addMeasurement() bench = Benchmark(this.javaClass, "Transform to CPG") - val tu = TranslationUnitDeclaration() - tu.language = language + val tu = newTranslationUnitDeclaration(file.name) // we need to set our translation unit as the global scope scopeManager.resetToGlobal(tu) @@ -157,7 +153,7 @@ class LLVMIRLanguageFrontend( counter++ } - LLVMContextDispose(ctx) + LLVMContextDispose(ctxRef) bench.addMeasurement() return tu diff --git a/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CompressLLVMPass.kt b/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CompressLLVMPass.kt index 5984b08c16..e15b3f5bac 100644 --- a/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CompressLLVMPass.kt +++ b/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CompressLLVMPass.kt @@ -25,13 +25,9 @@ */ package de.fraunhofer.aisec.cpg.passes -import de.fraunhofer.aisec.cpg.ScopeManager -import de.fraunhofer.aisec.cpg.TranslationConfiguration +import de.fraunhofer.aisec.cpg.TranslationContext import de.fraunhofer.aisec.cpg.frontends.llvm.LLVMIRLanguageFrontend -import de.fraunhofer.aisec.cpg.graph.Component -import de.fraunhofer.aisec.cpg.graph.Node -import de.fraunhofer.aisec.cpg.graph.newDeclaredReferenceExpression -import de.fraunhofer.aisec.cpg.graph.newVariableDeclaration +import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.statements.* import de.fraunhofer.aisec.cpg.graph.statements.expressions.ProblemExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.UnaryOperator @@ -43,8 +39,7 @@ import java.util.* @ExecuteFirst @RequiredFrontend(LLVMIRLanguageFrontend::class) -class CompressLLVMPass(config: TranslationConfiguration, scopeManager: ScopeManager) : - ComponentPass(config, scopeManager) { +class CompressLLVMPass(ctx: TranslationContext) : ComponentPass(ctx) { override fun accept(component: Component) { val flatAST = SubgraphWalker.flattenAST(component) // Get all goto statements diff --git a/cpg-language-llvm/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/LLVMIRLanguageFrontendTest.kt b/cpg-language-llvm/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/LLVMIRLanguageFrontendTest.kt index ae05556baf..8f2c0448ab 100644 --- a/cpg-language-llvm/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/LLVMIRLanguageFrontendTest.kt +++ b/cpg-language-llvm/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/LLVMIRLanguageFrontendTest.kt @@ -25,20 +25,17 @@ */ package de.fraunhofer.aisec.cpg.frontends.llvm -import de.fraunhofer.aisec.cpg.ScopeManager -import de.fraunhofer.aisec.cpg.TestUtils -import de.fraunhofer.aisec.cpg.TranslationConfiguration -import de.fraunhofer.aisec.cpg.assertFullName -import de.fraunhofer.aisec.cpg.assertLocalName +import de.fraunhofer.aisec.cpg.* +import de.fraunhofer.aisec.cpg.graph.TypeManager import de.fraunhofer.aisec.cpg.graph.bodyOrNull import de.fraunhofer.aisec.cpg.graph.byNameOrNull import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration +import de.fraunhofer.aisec.cpg.graph.parseType import de.fraunhofer.aisec.cpg.graph.statements.* import de.fraunhofer.aisec.cpg.graph.statements.expressions.* import de.fraunhofer.aisec.cpg.graph.types.ObjectType -import de.fraunhofer.aisec.cpg.graph.types.TypeParser import java.nio.file.Path import kotlin.test.* import kotlin.test.Test @@ -51,8 +48,11 @@ class LLVMIRLanguageFrontendTest { val frontend = LLVMIRLanguageFrontend( LLVMIRLanguage(), - TranslationConfiguration.builder().build(), - ScopeManager(), + TranslationContext( + TranslationConfiguration.builder().build(), + ScopeManager(), + TypeManager() + ) ) frontend.parse(topLevel.resolve("main.ll").toFile()) } @@ -312,10 +312,10 @@ class LLVMIRLanguageFrontendTest { val rhs = (comparison.rhs as Literal<*>) val lhs = (comparison.lhs as DeclaredReferenceExpression).refersTo as VariableDeclaration assertEquals(10L, (rhs.value as Long)) - assertEquals(TypeParser.createFrom("i32", LLVMIRLanguage()), rhs.type) + assertEquals(tu.parseType("i32"), rhs.type) assertLocalName("x", comparison.lhs as DeclaredReferenceExpression) assertLocalName("x", lhs) - assertEquals(TypeParser.createFrom("i32", LLVMIRLanguage()), lhs.type) + assertEquals(tu.parseType("i32"), lhs.type) // Check that the jump targets are set correctly val ifStatement = main.bodyOrNull(0) @@ -346,11 +346,11 @@ class LLVMIRLanguageFrontendTest { assertTrue(ifBranchComp.lhs is CastExpression) val ifBranchCompRhs = ifBranchComp.rhs as CastExpression - assertEquals(TypeParser.createFrom("ui32", LLVMIRLanguage()), ifBranchCompRhs.castType) - assertEquals(TypeParser.createFrom("ui32", LLVMIRLanguage()), ifBranchCompRhs.type) + assertEquals(tu.parseType("ui32"), ifBranchCompRhs.castType) + assertEquals(tu.parseType("ui32"), ifBranchCompRhs.type) val ifBranchCompLhs = ifBranchComp.lhs as CastExpression - assertEquals(TypeParser.createFrom("ui32", LLVMIRLanguage()), ifBranchCompLhs.castType) - assertEquals(TypeParser.createFrom("ui32", LLVMIRLanguage()), ifBranchCompLhs.type) + assertEquals(tu.parseType("ui32"), ifBranchCompLhs.castType) + assertEquals(tu.parseType("ui32"), ifBranchCompLhs.type) val declRefExpr = ifBranchCompLhs.expression as DeclaredReferenceExpression assertEquals(-3, ((ifBranchCompRhs.expression as Literal<*>).value as Long)) diff --git a/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonLanguage.kt b/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonLanguage.kt index d69d9a27f6..15a48176d8 100644 --- a/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonLanguage.kt +++ b/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonLanguage.kt @@ -25,8 +25,6 @@ */ package de.fraunhofer.aisec.cpg.frontends.python -import de.fraunhofer.aisec.cpg.ScopeManager -import de.fraunhofer.aisec.cpg.TranslationConfiguration import de.fraunhofer.aisec.cpg.frontends.HasShortCircuitOperators import de.fraunhofer.aisec.cpg.frontends.Language import de.fraunhofer.aisec.cpg.graph.statements.expressions.BinaryOperator @@ -79,13 +77,6 @@ class PythonLanguage : Language(), HasShortCircuitOperat "str" to StringType("str", this, listOf()) ) - override fun newFrontend( - config: TranslationConfiguration, - scopeManager: ScopeManager, - ): PythonLanguageFrontend { - return PythonLanguageFrontend(this, config, scopeManager) - } - override fun propagateTypeOfBinaryOperation(operation: BinaryOperator): Type { val unknownType = UnknownType.getUnknownType(this) if ( diff --git a/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonLanguageFrontend.kt b/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonLanguageFrontend.kt index 9392392fa3..f4db007664 100644 --- a/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonLanguageFrontend.kt +++ b/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonLanguageFrontend.kt @@ -25,8 +25,7 @@ */ package de.fraunhofer.aisec.cpg.frontends.python -import de.fraunhofer.aisec.cpg.ScopeManager -import de.fraunhofer.aisec.cpg.TranslationConfiguration +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.TranslationException @@ -37,11 +36,8 @@ import java.nio.file.Paths import jep.JepException import kotlin.io.path.absolutePathString -class PythonLanguageFrontend( - language: Language, - config: TranslationConfiguration, - scopeManager: ScopeManager, -) : LanguageFrontend(language, config, scopeManager) { +class PythonLanguageFrontend(language: Language, ctx: TranslationContext) : + LanguageFrontend(language, ctx) { private val jep = JepSingleton // configure Jep @Throws(TranslationException::class) diff --git a/cpg-language-python/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonFrontendTest.kt b/cpg-language-python/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonFrontendTest.kt index adad8e12d3..b5f808b10f 100644 --- a/cpg-language-python/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonFrontendTest.kt +++ b/cpg-language-python/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonFrontendTest.kt @@ -37,7 +37,6 @@ import de.fraunhofer.aisec.cpg.graph.statements.* import de.fraunhofer.aisec.cpg.graph.statements.expressions.* import de.fraunhofer.aisec.cpg.graph.types.NumericType import de.fraunhofer.aisec.cpg.graph.types.ObjectType -import de.fraunhofer.aisec.cpg.graph.types.TypeParser import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker import de.fraunhofer.aisec.cpg.sarif.PhysicalLocation import de.fraunhofer.aisec.cpg.sarif.Region @@ -70,19 +69,19 @@ class PythonFrontendTest : BaseTest() { val b = p.variables["b"] assertNotNull(b) assertLocalName("b", b) - assertEquals(TypeParser.createFrom("bool", PythonLanguage()), b.type) + assertEquals(tu.parseType("bool"), b.type) assertEquals(true, (b.initializer as? Literal<*>)?.value) val i = p.variables["i"] assertNotNull(i) assertLocalName("i", i) - assertEquals(TypeParser.createFrom("int", PythonLanguage()), i.type) + assertEquals(tu.parseType("int"), i.type) assertEquals(42L, (i.initializer as? Literal<*>)?.value) val f = p.variables["f"] assertNotNull(f) assertLocalName("f", f) - assertEquals(TypeParser.createFrom("float", PythonLanguage()), f.type) + assertEquals(tu.parseType("float"), f.type) assertEquals(1.0, (f.initializer as? Literal<*>)?.value) val c = p.variables["c"] @@ -97,13 +96,13 @@ class PythonFrontendTest : BaseTest() { val t = p.variables["t"] assertNotNull(t) assertLocalName("t", t) - assertEquals(TypeParser.createFrom("str", PythonLanguage()), t.type) + assertEquals(tu.parseType("str"), t.type) assertEquals("Hello", (t.initializer as? Literal<*>)?.value) val n = p.variables["n"] assertNotNull(n) assertLocalName("n", n) - assertEquals(TypeParser.createFrom("None", PythonLanguage()), n.type) + assertEquals(tu.parseType("None"), n.type) assertEquals(null, (n.initializer as? Literal<*>)?.value) } @@ -143,7 +142,7 @@ class PythonFrontendTest : BaseTest() { val s = bar.parameters.first() assertNotNull(s) assertLocalName("s", s) - assertEquals(TypeParser.createFrom("str", PythonLanguage()), s.type) + assertEquals(tu.parseType("str"), s.type) assertLocalName("bar", bar) @@ -160,7 +159,7 @@ class PythonFrontendTest : BaseTest() { assertNotNull(literal) assertEquals("bar(s) here: ", literal.value) - assertEquals(TypeParser.createFrom("str", PythonLanguage()), literal.type) + assertEquals(tu.parseType("str"), literal.type) val ref = callExpression.arguments[1] as? DeclaredReferenceExpression assertNotNull(ref) @@ -220,11 +219,11 @@ class PythonFrontendTest : BaseTest() { as? VariableDeclaration assertNotNull(sel) assertLocalName("sel", sel) - assertEquals(TypeParser.createFrom("bool", PythonLanguage()), sel.type) + assertEquals(tu.parseType("bool"), sel.type) val initializer = sel.initializer as? Literal<*> assertNotNull(initializer) - assertEquals(TypeParser.createFrom("bool", PythonLanguage()), initializer.type) + assertEquals(tu.parseType("bool"), initializer.type) assertEquals("True", initializer.code) val `if` = body.statements[1] as? IfStatement @@ -309,11 +308,11 @@ class PythonFrontendTest : BaseTest() { val foo = body.singleDeclaration as? VariableDeclaration assertNotNull(foo) assertLocalName("foo", foo) - assertEquals(TypeParser.createFrom("int", PythonLanguage()), foo.type) + assertEquals(tu.parseType("int"), foo.type) val initializer = foo.initializer as? ConditionalExpression assertNotNull(initializer) - assertEquals(TypeParser.createFrom("int", PythonLanguage()), initializer.type) + assertEquals(tu.parseType("int"), initializer.type) val ifCond = initializer.condition as? Literal<*> assertNotNull(ifCond) @@ -322,13 +321,13 @@ class PythonFrontendTest : BaseTest() { val elseExpr = initializer.elseExpr as? Literal<*> assertNotNull(elseExpr) - assertEquals(TypeParser.createFrom("bool", PythonLanguage()), ifCond.type) + assertEquals(tu.parseType("bool"), ifCond.type) assertEquals(false, ifCond.value) - assertEquals(TypeParser.createFrom("int", PythonLanguage()), thenExpr.type) + assertEquals(tu.parseType("int"), thenExpr.type) assertEquals(21, (thenExpr.value as? Long)?.toInt()) - assertEquals(TypeParser.createFrom("int", PythonLanguage()), elseExpr.type) + assertEquals(tu.parseType("int"), elseExpr.type) assertEquals(42, (elseExpr.value as? Long)?.toInt()) } @@ -412,7 +411,7 @@ class PythonFrontendTest : BaseTest() { val somevar = recordFoo.fields[0] assertNotNull(somevar) assertLocalName("somevar", somevar) - // assertEquals(TypeParser.createFrom("int", false), somevar.type) TODO fix type deduction + // assertEquals(tu.parseType("int", false), somevar.type) TODO fix type deduction assertEquals(2, recordFoo.methods.size) val bar = recordFoo.methods[0] @@ -434,7 +433,7 @@ class PythonFrontendTest : BaseTest() { assertNotNull(i) assertLocalName("i", i) - assertEquals(TypeParser.createFrom("int", PythonLanguage()), i.type) + assertEquals(tu.parseType("int"), i.type) // self.somevar = i val someVarDeclaration = diff --git a/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/JavaScriptLanguage.kt b/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/JavaScriptLanguage.kt index e3a727a716..b0e7e0eb0a 100644 --- a/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/JavaScriptLanguage.kt +++ b/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/JavaScriptLanguage.kt @@ -25,8 +25,6 @@ */ package de.fraunhofer.aisec.cpg.frontends.typescript -import de.fraunhofer.aisec.cpg.ScopeManager -import de.fraunhofer.aisec.cpg.TranslationConfiguration import de.fraunhofer.aisec.cpg.frontends.HasShortCircuitOperators import de.fraunhofer.aisec.cpg.frontends.Language import de.fraunhofer.aisec.cpg.graph.types.* @@ -78,11 +76,4 @@ open class JavaScriptLanguage : Language(), HasShort "bigint" to IntegerType("bigint", null, this, NumericType.Modifier.SIGNED), "string" to StringType("string", this), ) - - override fun newFrontend( - config: TranslationConfiguration, - scopeManager: ScopeManager, - ): TypeScriptLanguageFrontend { - return TypeScriptLanguageFrontend(this, config, scopeManager) - } } diff --git a/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/TypeScriptLanguageFrontend.kt b/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/TypeScriptLanguageFrontend.kt index f814cad6ff..edaf51eb6c 100644 --- a/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/TypeScriptLanguageFrontend.kt +++ b/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/TypeScriptLanguageFrontend.kt @@ -26,8 +26,7 @@ package de.fraunhofer.aisec.cpg.frontends.typescript import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper -import de.fraunhofer.aisec.cpg.ScopeManager -import de.fraunhofer.aisec.cpg.TranslationConfiguration +import de.fraunhofer.aisec.cpg.TranslationContext import de.fraunhofer.aisec.cpg.frontends.FrontendUtils import de.fraunhofer.aisec.cpg.frontends.Language import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend @@ -58,24 +57,19 @@ import java.nio.file.StandardCopyOption */ class TypeScriptLanguageFrontend( language: Language, - config: TranslationConfiguration, - scopeManager: ScopeManager, -) : LanguageFrontend(language, config, scopeManager) { + ctx: TranslationContext +) : LanguageFrontend(language, ctx) { val declarationHandler = DeclarationHandler(this) val statementHandler = StatementHandler(this) val expressionHandler = ExpressionHandler(this) val typeHandler = TypeHandler(this) - var currentFileContent: String? = null + private var currentFileContent: String? = null - val mapper = jacksonObjectMapper() + private val mapper = jacksonObjectMapper() companion object { - @JvmField var TYPESCRIPT_EXTENSIONS: List = listOf(".ts", ".tsx") - - @JvmField var JAVASCRIPT_EXTENSIONS: List = listOf(".js", ".jsx") - private val parserFile: File = createTempFile("parser", ".js") init { diff --git a/cpg-language-typescript/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/TypescriptLanguageFrontendTest.kt b/cpg-language-typescript/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/TypescriptLanguageFrontendTest.kt index 7f3467d698..65d8bf2726 100644 --- a/cpg-language-typescript/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/TypescriptLanguageFrontendTest.kt +++ b/cpg-language-typescript/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/TypescriptLanguageFrontendTest.kt @@ -32,10 +32,10 @@ import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration import de.fraunhofer.aisec.cpg.graph.get +import de.fraunhofer.aisec.cpg.graph.parseType import de.fraunhofer.aisec.cpg.graph.statements.DeclarationStatement import de.fraunhofer.aisec.cpg.graph.statements.ReturnStatement import de.fraunhofer.aisec.cpg.graph.statements.expressions.* -import de.fraunhofer.aisec.cpg.graph.types.TypeParser import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker import java.nio.file.Path import kotlin.test.Test @@ -66,11 +66,11 @@ class TypeScriptLanguageFrontendTest { val someFunction = functions.first() assertLocalName("someFunction", someFunction) - assertEquals(TypeParser.createFrom("Number", TypeScriptLanguage()), someFunction.type) + assertEquals(tu.parseType("Number"), someFunction.type) val someOtherFunction = functions.last() assertLocalName("someOtherFunction", someOtherFunction) - assertEquals(TypeParser.createFrom("Number", TypeScriptLanguage()), someOtherFunction.type) + assertEquals(tu.parseType("Number"), someOtherFunction.type) val parameters = someOtherFunction.parameters assertNotNull(parameters) @@ -79,7 +79,7 @@ class TypeScriptLanguageFrontendTest { val parameter = parameters.first() assertLocalName("s", parameter) - assertEquals(TypeParser.createFrom("String", TypeScriptLanguage()), parameter.type) + assertEquals(tu.parseType("String"), parameter.type) } @Test @@ -278,7 +278,7 @@ class TypeScriptLanguageFrontendTest { val lastName = user.fields.lastOrNull() assertNotNull(lastName) assertLocalName("lastName", lastName) - assertEquals(TypeParser.createFrom("string", TypeScriptLanguage()), lastName.type) + assertEquals(tu.parseType("string"), lastName.type) val usersState = tu.getDeclarationsByName("UsersState", RecordDeclaration::class.java).iterator().next() @@ -291,7 +291,7 @@ class TypeScriptLanguageFrontendTest { val users = usersState.fields.firstOrNull() assertNotNull(users) assertLocalName("users", users) - assertEquals(TypeParser.createFrom("User[]", TypeScriptLanguage()), users.type) + assertEquals(tu.parseType("User[]"), users.type) val usersComponent = tu.getDeclarationsByName("Users", RecordDeclaration::class.java).iterator().next() From 2b89676a6f6f4e8894e040b9a658ffa78735e614 Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Wed, 31 May 2023 16:39:33 +0200 Subject: [PATCH 054/143] Extract `CXXLanguageFrontend` into its own module (#1186) * Extract `CXXLanguageFrontend` into its own module Similar to what we did previously with Java, this PR extracts the C/C++ language frontend into its own module. * cpp to cxx package * enable C/C++ frontend * Added missing files * Fixed frontend dependency resolution * Cleanup of some code smells --- .github/workflows/build.yml | 4 +- build.gradle.kts | 6 + ...frontend-dependency-conventions.gradle.kts | 2 + configure_frontends.sh | 2 + cpg-analysis/build.gradle.kts | 1 + .../aisec/cpg/graph/TypeManager.java | 9 +- .../aisec/cpg/InferenceConfiguration.kt | 3 +- .../aisec/cpg/TranslationConfiguration.kt | 12 +- .../aisec/cpg/TranslationManager.kt | 4 +- .../statements/expressions/CallExpression.kt | 5 +- .../aisec/cpg/passes/CallResolver.kt | 19 +- .../cpg/frontends/LanguageFrontendTest.kt | 53 ----- .../cpg/frontends/cpp/BotanExampleTest.kt | 64 ----- .../aisec/cpg/helpers/BenchmarkTest.kt | 33 --- .../cpg/passes/scopes/ScopeManagerTest.kt | 55 +---- .../src/test/resources/botan/CMakeLists.txt | 10 - cpg-core/src/test/resources/botan/simple.cpp | 64 ----- cpg-core/src/test/resources/botan/simple2.cpp | 38 --- .../resources/botan/symm_block_cipher.cpp | 87 ------- cpg-language-cxx/build.gradle.kts | 52 ++++ .../aisec/cpg/frontends/cxx}/CLanguage.kt | 2 +- .../aisec/cpg/frontends/cxx}/CPPLanguage.kt | 2 +- .../aisec/cpg/frontends/cxx}/CXXHandler.kt | 2 +- .../cpg/frontends/cxx}/CXXLanguageFrontend.kt | 9 +- .../cpg/frontends/cxx}/DeclarationHandler.kt | 2 +- .../cpg/frontends/cxx}/DeclaratorHandler.kt | 2 +- .../cpg/frontends/cxx}/ExpressionHandler.kt | 222 ++++++++++-------- .../cpg/frontends/cxx}/InitializerHandler.kt | 2 +- .../cxx}/ParameterDeclarationHandler.kt | 2 +- .../cpg/frontends/cxx}/StatementHandler.kt | 2 +- .../aisec/cpg/frontends/cxx}/package-info.kt | 2 +- .../cpg/passes/FunctionPointerCallResolver.kt | 2 +- .../aisec/cpg/PerformanceRegressionTest.kt | 4 +- .../aisec/cpg/ScopeManagerCXXTest.kt | 78 ++++++ .../aisec/cpg/enhancements/EOGTest.kt | 0 .../enhancements/calls/ConstructorsTest.kt | 0 .../enhancements/calls/FunctionPointerTest.kt | 0 .../templates/ClassTemplateTest.kt | 0 .../templates/FunctionTemplateTest.kt | 2 +- .../cpg/enhancements}/types/TypeTests.kt | 7 +- .../cpg/enhancements}/types/TypedefTest.kt | 4 +- .../VariableResolverCppTest.kt | 0 .../cpg/frontends/cxx}/CXXAmbiguitiesTest.kt | 2 +- .../cxx}/CXXCompilationDatabaseTest.kt | 2 +- .../cpg/frontends/cxx}/CXXIncludeTest.kt | 8 +- .../frontends/cxx}/CXXLanguageFrontendTest.kt | 2 +- .../cpg/frontends/cxx}/CXXLiteralTest.kt | 2 +- .../cpg/frontends/cxx}/CXXResolveTest.kt | 2 +- .../cxx}/CXXSymbolConfigurationTest.kt | 2 +- .../aisec/cpg/frontends/cxx}/LambdaTest.kt | 2 +- .../aisec/cpg/helpers/BenchmarkCXXTest.kt | 67 ++++++ .../aisec/cpg/passes/CallResolverTest.kt | 2 +- .../src/test/resources/another-include.h | 0 .../test/resources/assignmentexpression.cpp | 0 .../src/test/resources/attributes.cpp | 0 .../src/test/resources/binaryoperator.cpp | 0 .../bindings/replace_declaration.cpp | 0 .../resources/bindings/use_then_declare.cpp | 0 .../src/test/resources/c/enum.c | 0 .../src/test/resources/c/func_ptr_call.c | 0 .../src/test/resources/c/struct.c | 0 .../resources/c/typedef_in_header/_header.h | 0 .../resources/c/typedef_in_header/header.h | 0 .../test/resources/c/typedef_in_header/main.c | 0 .../src/test/resources/call_me_crazy.h | 0 .../src/test/resources/calls/calls.cpp | 0 .../calls/cxxprioresolution/defined.cpp | 0 .../methodresolution/overloadedresolution.cpp | 0 .../methodresolution/overloadnoresolution.cpp | 0 .../scopedResolutionWithDefaults.cpp | 0 .../calls/cxxprioresolution/undefined.cpp | 0 .../defaultargs/defaultInDeclaration.cpp | 0 .../calls/defaultargs/defaultInDefinition.cpp | 0 .../calls/defaultargs/defaultInMethod.cpp | 0 .../calls/defaultargs/partialDefaults.cpp | 0 .../test/resources/calls/ignore-return.cpp | 0 .../calls/implicitcast/ambiguouscall.cpp | 0 .../implicitcast/implicitCastInMethod.cpp | 0 .../calls/implicitcast/implicitcast.cpp | 0 .../src/test/resources/cfg.cpp | 0 .../src/test/resources/cfg/break_continue.cpp | 0 .../src/test/resources/cfg/forloop.cpp | 0 .../src/test/resources/cfg/goto.cpp | 0 .../src/test/resources/cfg/if.cpp | 0 .../src/test/resources/cfg/ifextra.cpp | 0 .../src/test/resources/cfg/loops.cpp | 0 .../src/test/resources/cfg/loopscfg.cpp | 0 .../src/test/resources/cfg/switch.cpp | 0 .../src/test/resources/cg.cpp | 0 .../hierarchy/multistep/multi_inheritance.cpp | 0 .../multistep/simple_inheritance.cpp | 0 .../test/resources/components/castexpr.cpp | 0 .../components/designatedInitializer.cpp | 0 .../test/resources/components/foreachstmt.cpp | 0 .../src/test/resources/components/trystmt.cpp | 0 .../src/test/resources/compoundstmt.cpp | 0 .../resources/constructors/constructors.cpp | 0 .../defaultarg/constructorDefault.cpp | 0 .../implicitcastarg/constructorImplicit.cpp | 0 .../src/test/resources/cpp-this-field.cpp | 0 .../src/test/resources/cxx/arrays.cpp | 0 .../src/test/resources/cxx/declstmt.cpp | 0 .../resources/cxx/funcptr_class_simple.cpp | 0 .../src/test/resources/cxx/functiondecl.cpp | 0 .../src/test/resources/cxx/lambdas.cpp | 0 .../src/test/resources/cxx/literals.cpp | 0 .../resources/cxx/namespaced_function.cpp | 0 .../src/test/resources/cxx/objcreation.cpp | 0 .../src/test/resources/cxx/parenthesis.cpp | 0 .../src/test/resources/cxx/recordstmt.cpp | 0 .../compile_commands_arch.json | 0 .../compile_commands_arguments.json | 0 .../compile_commands_commands.json | 0 .../compile_commands_multi_tus.json | 0 .../compile_commands_simple.json | 0 .../includes_1/config.h | 0 .../includes_1/header_1.h | 0 .../includes_2/config.h | 0 .../includes_2/header_2.h | 0 .../resources/cxxCompilationDatabase/main.c | 0 .../cxxCompilationDatabase/main_arm64.c | 0 .../cxxCompilationDatabase/main_simple.c | 0 .../cxxCompilationDatabase/main_tu_1.c | 0 .../cxxCompilationDatabase/main_tu_2.c | 0 .../sys_includes/sys_header.h | 0 .../src/test/resources/fix-455/main.cpp | 0 .../src/test/resources/foo.cpp | 0 .../src/test/resources/foo2.cpp | 0 .../resources/functionPointers/func_ptr.c | 0 .../resources/functionPointers/func_ptr.cpp | 0 .../resources/function_ptr_or_type_cast.c | 0 .../src/test/resources/if.cpp | 0 .../src/test/resources/include.cpp | 0 .../src/test/resources/include.h | 0 .../src/test/resources/initlistexpression.cpp | 0 .../src/test/resources/integer_literals.cpp | 0 .../test/resources/largenegativenumber.cpp | 0 .../src/test/resources/log4j2.xml | 14 ++ .../resources/method_or_function_call.cpp | 0 .../src/test/resources/namespaces.cpp | 0 .../src/test/resources/postfixexpression.cpp | 0 .../src/test/resources/shiftexpression.cpp | 0 .../src/test/resources/symbols.cpp | 0 .../templates/classtemplates/array.cpp | 0 .../templates/classtemplates/array2.cpp | 0 .../templates/classtemplates/pair.cpp | 0 .../templates/classtemplates/pair2.cpp | 0 .../templates/classtemplates/pair3-1.cpp | 0 .../templates/classtemplates/pair3-2.cpp | 0 .../templates/classtemplates/pair3.cpp | 0 .../functiontemplates/functionTemplate.cpp | 0 .../functionTemplateInvocation1.cpp | 0 .../functionTemplateInvocation2.cpp | 0 .../functionTemplateInvocation3.cpp | 0 .../functionTemplateInvocation4.cpp | 0 .../functionTemplateInvocation5.cpp | 0 .../functionTemplateInvocation6.cpp | 0 .../functionTemplateInvocation7.cpp | 0 .../functionTemplateInvocation8.cpp | 0 .../functionTemplateMethod.cpp | 0 .../src/test/resources/typedefs/typedefs.cpp | 0 .../src/test/resources/typeidexpr.cpp | 0 .../src/test/resources/types.cpp | 0 .../src/test/resources/types/fptr_type.cpp | 0 .../src/test/resources/types/type.cpp | 0 .../src/test/resources/unaryoperator.cpp | 0 .../src/test/resources/unity/a.cpp | 0 .../src/test/resources/unity/b.cpp | 0 .../src/test/resources/unity/common.h | 0 .../resources/variables/local_variables.cpp | 0 .../variables_extended/cpp/external_class.cpp | 0 .../variables_extended/cpp/external_class.h | 0 .../cpp/local_variables.cpp | 0 .../cpp/scope_variables.cpp | 0 .../aisec/cpg_vis_neo4j/Application.kt | 1 - settings.gradle.kts | 5 + 176 files changed, 428 insertions(+), 552 deletions(-) delete mode 100644 cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/LanguageFrontendTest.kt delete mode 100644 cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/BotanExampleTest.kt delete mode 100644 cpg-core/src/test/resources/botan/CMakeLists.txt delete mode 100644 cpg-core/src/test/resources/botan/simple.cpp delete mode 100644 cpg-core/src/test/resources/botan/simple2.cpp delete mode 100644 cpg-core/src/test/resources/botan/symm_block_cipher.cpp create mode 100644 cpg-language-cxx/build.gradle.kts rename {cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp => cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx}/CLanguage.kt (99%) rename {cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp => cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx}/CPPLanguage.kt (99%) rename {cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp => cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx}/CXXHandler.kt (98%) rename {cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp => cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx}/CXXLanguageFrontend.kt (98%) rename {cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp => cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx}/DeclarationHandler.kt (99%) rename {cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp => cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx}/DeclaratorHandler.kt (99%) rename {cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp => cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx}/ExpressionHandler.kt (84%) rename {cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp => cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx}/InitializerHandler.kt (98%) rename {cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp => cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx}/ParameterDeclarationHandler.kt (98%) rename {cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp => cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx}/StatementHandler.kt (99%) rename {cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp => cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx}/package-info.kt (95%) rename {cpg-core => cpg-language-cxx}/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/FunctionPointerCallResolver.kt (99%) rename {cpg-core => cpg-language-cxx}/src/test/kotlin/de/fraunhofer/aisec/cpg/PerformanceRegressionTest.kt (97%) create mode 100644 cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/ScopeManagerCXXTest.kt rename {cpg-core => cpg-language-cxx}/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/EOGTest.kt (100%) rename {cpg-core => cpg-language-cxx}/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/calls/ConstructorsTest.kt (100%) rename {cpg-core => cpg-language-cxx}/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/calls/FunctionPointerTest.kt (100%) rename {cpg-core => cpg-language-cxx}/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/templates/ClassTemplateTest.kt (100%) rename {cpg-core => cpg-language-cxx}/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/templates/FunctionTemplateTest.kt (99%) rename {cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph => cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements}/types/TypeTests.kt (99%) rename {cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph => cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements}/types/TypedefTest.kt (97%) rename {cpg-core => cpg-language-cxx}/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/variable_resolution/VariableResolverCppTest.kt (100%) rename {cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp => cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx}/CXXAmbiguitiesTest.kt (99%) rename {cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp => cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx}/CXXCompilationDatabaseTest.kt (99%) rename {cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp => cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx}/CXXIncludeTest.kt (97%) rename {cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp => cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx}/CXXLanguageFrontendTest.kt (99%) rename {cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp => cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx}/CXXLiteralTest.kt (99%) rename {cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp => cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx}/CXXResolveTest.kt (99%) rename {cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp => cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx}/CXXSymbolConfigurationTest.kt (99%) rename {cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp => cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx}/LambdaTest.kt (99%) create mode 100644 cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/helpers/BenchmarkCXXTest.kt rename {cpg-core => cpg-language-cxx}/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolverTest.kt (99%) rename {cpg-core => cpg-language-cxx}/src/test/resources/another-include.h (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/assignmentexpression.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/attributes.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/binaryoperator.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/bindings/replace_declaration.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/bindings/use_then_declare.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/c/enum.c (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/c/func_ptr_call.c (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/c/struct.c (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/c/typedef_in_header/_header.h (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/c/typedef_in_header/header.h (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/c/typedef_in_header/main.c (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/call_me_crazy.h (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/calls/calls.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/calls/cxxprioresolution/defined.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/calls/cxxprioresolution/methodresolution/overloadedresolution.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/calls/cxxprioresolution/methodresolution/overloadnoresolution.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/calls/cxxprioresolution/scopedResolutionWithDefaults.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/calls/cxxprioresolution/undefined.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/calls/defaultargs/defaultInDeclaration.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/calls/defaultargs/defaultInDefinition.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/calls/defaultargs/defaultInMethod.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/calls/defaultargs/partialDefaults.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/calls/ignore-return.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/calls/implicitcast/ambiguouscall.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/calls/implicitcast/implicitCastInMethod.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/calls/implicitcast/implicitcast.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/cfg.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/cfg/break_continue.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/cfg/forloop.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/cfg/goto.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/cfg/if.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/cfg/ifextra.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/cfg/loops.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/cfg/loopscfg.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/cfg/switch.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/cg.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/compiling/hierarchy/multistep/multi_inheritance.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/compiling/hierarchy/multistep/simple_inheritance.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/components/castexpr.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/components/designatedInitializer.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/components/foreachstmt.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/components/trystmt.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/compoundstmt.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/constructors/constructors.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/constructors/defaultarg/constructorDefault.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/constructors/implicitcastarg/constructorImplicit.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/cpp-this-field.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/cxx/arrays.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/cxx/declstmt.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/cxx/funcptr_class_simple.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/cxx/functiondecl.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/cxx/lambdas.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/cxx/literals.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/cxx/namespaced_function.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/cxx/objcreation.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/cxx/parenthesis.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/cxx/recordstmt.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/cxxCompilationDatabase/compile_commands_arch.json (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/cxxCompilationDatabase/compile_commands_arguments.json (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/cxxCompilationDatabase/compile_commands_commands.json (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/cxxCompilationDatabase/compile_commands_multi_tus.json (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/cxxCompilationDatabase/compile_commands_simple.json (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/cxxCompilationDatabase/includes_1/config.h (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/cxxCompilationDatabase/includes_1/header_1.h (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/cxxCompilationDatabase/includes_2/config.h (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/cxxCompilationDatabase/includes_2/header_2.h (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/cxxCompilationDatabase/main.c (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/cxxCompilationDatabase/main_arm64.c (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/cxxCompilationDatabase/main_simple.c (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/cxxCompilationDatabase/main_tu_1.c (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/cxxCompilationDatabase/main_tu_2.c (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/cxxCompilationDatabase/sys_includes/sys_header.h (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/fix-455/main.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/foo.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/foo2.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/functionPointers/func_ptr.c (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/functionPointers/func_ptr.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/function_ptr_or_type_cast.c (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/if.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/include.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/include.h (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/initlistexpression.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/integer_literals.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/largenegativenumber.cpp (100%) create mode 100644 cpg-language-cxx/src/test/resources/log4j2.xml rename {cpg-core => cpg-language-cxx}/src/test/resources/method_or_function_call.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/namespaces.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/postfixexpression.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/shiftexpression.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/symbols.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/templates/classtemplates/array.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/templates/classtemplates/array2.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/templates/classtemplates/pair.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/templates/classtemplates/pair2.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/templates/classtemplates/pair3-1.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/templates/classtemplates/pair3-2.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/templates/classtemplates/pair3.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/templates/functiontemplates/functionTemplate.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/templates/functiontemplates/functionTemplateInvocation1.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/templates/functiontemplates/functionTemplateInvocation2.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/templates/functiontemplates/functionTemplateInvocation3.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/templates/functiontemplates/functionTemplateInvocation4.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/templates/functiontemplates/functionTemplateInvocation5.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/templates/functiontemplates/functionTemplateInvocation6.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/templates/functiontemplates/functionTemplateInvocation7.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/templates/functiontemplates/functionTemplateInvocation8.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/templates/functiontemplates/functionTemplateMethod.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/typedefs/typedefs.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/typeidexpr.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/types.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/types/fptr_type.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/types/type.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/unaryoperator.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/unity/a.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/unity/b.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/unity/common.h (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/variables/local_variables.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/variables_extended/cpp/external_class.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/variables_extended/cpp/external_class.h (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/variables_extended/cpp/local_variables.cpp (100%) rename {cpg-core => cpg-language-cxx}/src/test/resources/variables_extended/cpp/scope_variables.cpp (100%) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 81e9ed4cac..7f15373ac2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -111,13 +111,13 @@ jobs: run: | if [ "$SONAR_TOKEN" != "" ] then - ./gradlew --no-daemon --parallel -Pversion=$VERSION -Pexperimental -PenableJavaFrontend=true -PenableGoFrontend=true -PenablePythonFrontend=true -PenableLLVMFrontend=true -PenableTypeScriptFrontend=true -Pintegration spotlessCheck -x spotlessApply build -x distZip -x distTar testCodeCoverageReport sonar \ + ./gradlew --no-daemon --parallel -Pversion=$VERSION -Pexperimental -PenableJavaFrontend=true -PenableCXXFrontend=true -PenableGoFrontend=true -PenablePythonFrontend=true -PenableLLVMFrontend=true -PenableTypeScriptFrontend=true -Pintegration spotlessCheck -x spotlessApply build -x distZip -x distTar testCodeCoverageReport sonar \ -Dsonar.projectKey=Fraunhofer-AISEC_cpg \ -Dsonar.organization=fraunhofer-aisec \ -Dsonar.host.url=https://sonarcloud.io \ -Dsonar.login=$SONAR_TOKEN else - ./gradlew --no-daemon --parallel -Pversion=$VERSION -Pexperimental -PenableJavaFrontend=true -PenableGoFrontend=true -PenablePythonFrontend=true -PenableLLVMFrontend=true -PenableTypeScriptFrontend=true -Pintegration spotlessCheck -x spotlessApply build -x distZip -x distTar + ./gradlew --no-daemon --parallel -Pversion=$VERSION -Pexperimental -PenableJavaFrontend=true -PenableCXXFrontend=true -PenableGoFrontend=true -PenablePythonFrontend=true -PenableLLVMFrontend=true -PenableTypeScriptFrontend=true -Pintegration spotlessCheck -x spotlessApply build -x distZip -x distTar fi id: build env: diff --git a/build.gradle.kts b/build.gradle.kts index 1ba55a7509..0c05e80b60 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -67,6 +67,12 @@ val enableJavaFrontend: Boolean by extra { } project.logger.lifecycle("Java frontend is ${if (enableJavaFrontend) "enabled" else "disabled"}") +val enableCXXFrontend: Boolean by extra { + val enableCXXFrontend: String? by project + enableCXXFrontend.toBoolean() +} +project.logger.lifecycle("C/C++ frontend is ${if (enableCXXFrontend) "enabled" else "disabled"}") + val enableGoFrontend: Boolean by extra { val enableGoFrontend: String? by project enableGoFrontend.toBoolean() diff --git a/buildSrc/src/main/kotlin/cpg.frontend-dependency-conventions.gradle.kts b/buildSrc/src/main/kotlin/cpg.frontend-dependency-conventions.gradle.kts index 4212444a77..fc5c2914f6 100644 --- a/buildSrc/src/main/kotlin/cpg.frontend-dependency-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/cpg.frontend-dependency-conventions.gradle.kts @@ -4,6 +4,7 @@ plugins { } val enableJavaFrontend: Boolean by rootProject.extra +val enableCXXFrontend: Boolean by rootProject.extra val enableGoFrontend: Boolean by rootProject.extra val enablePythonFrontend: Boolean by rootProject.extra val enableLLVMFrontend: Boolean by rootProject.extra @@ -11,6 +12,7 @@ val enableTypeScriptFrontend: Boolean by rootProject.extra dependencies { if (enableJavaFrontend) api(project(":cpg-language-java")) + if (enableCXXFrontend) api(project(":cpg-language-cxx")) if (enableGoFrontend) api(project(":cpg-language-go")) if (enablePythonFrontend) api(project(":cpg-language-python")) if (enableLLVMFrontend) api(project(":cpg-language-llvm")) diff --git a/configure_frontends.sh b/configure_frontends.sh index b1d8f2c6a9..d4d02998e5 100755 --- a/configure_frontends.sh +++ b/configure_frontends.sh @@ -46,6 +46,8 @@ fi answerJava=$(ask "Do you want to enable the Java frontend? (currently $(getProperty "enableJavaFrontend"))") setProperty "enableJavaFrontend" $answerJava +answerCXX=$(ask "Do you want to enable the C/C++ frontend? (currently $(getProperty "enableCXXFrontend"))") +setProperty "enableCXXFrontend" $answerCXX answerGo=$(ask "Do you want to enable the Go frontend? (currently $(getProperty "enableGoFrontend"))") setProperty "enableGoFrontend" $answerGo answerPython=$(ask "Do you want to enable the Python frontend? (currently $(getProperty "enablePythonFrontend"))") diff --git a/cpg-analysis/build.gradle.kts b/cpg-analysis/build.gradle.kts index c7fbbfbd48..65d6a2e623 100644 --- a/cpg-analysis/build.gradle.kts +++ b/cpg-analysis/build.gradle.kts @@ -43,6 +43,7 @@ publishing { dependencies { api(projects.cpgCore) testImplementation(projects.cpgLanguageJava) + testImplementation(projects.cpgLanguageCxx) testImplementation(testFixtures(projects.cpgCore)) } diff --git a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/TypeManager.java b/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/TypeManager.java index 853a378d4a..f5a92b99eb 100644 --- a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/TypeManager.java +++ b/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/TypeManager.java @@ -31,7 +31,6 @@ 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.cpp.CLanguage; import de.fraunhofer.aisec.cpg.graph.declarations.*; import de.fraunhofer.aisec.cpg.graph.scopes.NameScope; import de.fraunhofer.aisec.cpg.graph.scopes.RecordScope; @@ -566,7 +565,7 @@ public boolean isSupertypeOf(Type superType, Type subType, MetadataProvider prov // arrays and pointers match in C/C++ // TODO: Make this independent from the specific language - if (language instanceof CLanguage && checkArrayAndPointer(superType, subType)) { + if (isCXX(language) && checkArrayAndPointer(superType, subType)) { return true; } @@ -596,6 +595,12 @@ public boolean isSupertypeOf(Type superType, Type subType, MetadataProvider prov } } + private boolean isCXX(Language language) { + return language != null + && (language.getClass().getSimpleName().equals("CLanguage") + || language.getClass().getSimpleName().equals("CPPLanguage")); + } + public boolean checkArrayAndPointer(Type first, Type second) { int firstDepth = first.getReferenceDepth(); int secondDepth = second.getReferenceDepth(); diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/InferenceConfiguration.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/InferenceConfiguration.kt index 1773cfdb45..b56b6f7df1 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/InferenceConfiguration.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/InferenceConfiguration.kt @@ -25,7 +25,6 @@ */ package de.fraunhofer.aisec.cpg -import de.fraunhofer.aisec.cpg.frontends.cpp.CXXLanguageFrontend import org.apache.commons.lang3.builder.ToStringBuilder import org.apache.commons.lang3.builder.ToStringStyle @@ -35,7 +34,7 @@ import org.apache.commons.lang3.builder.ToStringStyle */ class InferenceConfiguration private constructor( - /** Enables smart guessing of cast vs. call expressions in the [CXXLanguageFrontend] */ + /** Enables smart guessing of cast vs. call expressions in the C/C++ language frontend */ val guessCastExpressions: Boolean, /** Enables the inference of record declarations */ diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationConfiguration.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationConfiguration.kt index 1a8729aed1..483928aaa3 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationConfiguration.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationConfiguration.kt @@ -30,8 +30,6 @@ import de.fraunhofer.aisec.cpg.frontends.CompilationDatabase import de.fraunhofer.aisec.cpg.frontends.KClassSerializer import de.fraunhofer.aisec.cpg.frontends.Language import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend -import de.fraunhofer.aisec.cpg.frontends.cpp.CLanguage -import de.fraunhofer.aisec.cpg.frontends.cpp.CPPLanguage import de.fraunhofer.aisec.cpg.passes.* import de.fraunhofer.aisec.cpg.passes.order.* import java.io.File @@ -475,7 +473,6 @@ private constructor( registerPass() // creates EOG registerPass() registerPass() - registerPass() registerPass() return this } @@ -513,9 +510,14 @@ private constructor( } /** Register all default languages. */ + @Deprecated( + message = + "We moved all languages out of the core package and therefore you should register individual languages instead. For compatibility reasons we do a dynamic lookup to Java and C/C++ languages here but this function will be removed in the future." + ) fun defaultLanguages(): Builder { - registerLanguage(CLanguage()) - registerLanguage(CPPLanguage()) + optionalLanguage("de.fraunhofer.aisec.cpg.frontends.cxx.CLanguage") + optionalLanguage("de.fraunhofer.aisec.cpg.frontends.cxx.CPPLanguage") + optionalLanguage("de.fraunhofer.aisec.cpg.frontends.java.JavaLanguage") return this } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationManager.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationManager.kt index 5b7f2b1cbc..d034a60fe4 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationManager.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationManager.kt @@ -29,7 +29,6 @@ import de.fraunhofer.aisec.cpg.frontends.Language import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend import de.fraunhofer.aisec.cpg.frontends.SupportsParallelParsing import de.fraunhofer.aisec.cpg.frontends.TranslationException -import de.fraunhofer.aisec.cpg.frontends.cpp.CXXLanguageFrontend import de.fraunhofer.aisec.cpg.graph.Component import de.fraunhofer.aisec.cpg.graph.Name import de.fraunhofer.aisec.cpg.graph.TypeManager @@ -185,7 +184,8 @@ private constructor( PrintWriter(tmpFile).use { writer -> list.forEach { - if (CXXLanguageFrontend.CXX_EXTENSIONS.contains(Util.getExtension(it))) { + val cxxExtensions = listOf(".c", ".cpp", ".cc") + if (cxxExtensions.contains(Util.getExtension(it))) { if (ctx.config.topLevel != null) { val topLevel = ctx.config.topLevel.toPath() writer.write( diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/CallExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/CallExpression.kt index 4a434eb14d..f480bd8d4b 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/CallExpression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/CallExpression.kt @@ -40,7 +40,6 @@ import de.fraunhofer.aisec.cpg.graph.types.FunctionPointerType import de.fraunhofer.aisec.cpg.graph.types.TupleType import de.fraunhofer.aisec.cpg.graph.types.Type import de.fraunhofer.aisec.cpg.passes.CallResolver -import de.fraunhofer.aisec.cpg.passes.FunctionPointerCallResolver import de.fraunhofer.aisec.cpg.passes.VariableUsageResolver import java.util.* import org.apache.commons.lang3.builder.ToStringBuilder @@ -52,7 +51,7 @@ import org.neo4j.ogm.annotation.Relationship */ open class CallExpression : Expression(), HasType.TypeListener, SecondaryTypeEdge, ArgumentHolder { /** Connection to its [FunctionDeclaration]. This will be populated by the [CallResolver]. */ - @PopulatedByPass(CallResolver::class, FunctionPointerCallResolver::class) + @PopulatedByPass(CallResolver::class) @Relationship(value = "INVOKES", direction = Relationship.Direction.OUTGOING) var invokeEdges = mutableListOf>() protected set @@ -61,7 +60,7 @@ open class CallExpression : Expression(), HasType.TypeListener, SecondaryTypeEdg * A virtual property to quickly access the list of declarations that this call invokes without * property edges. */ - @PopulatedByPass(CallResolver::class, FunctionPointerCallResolver::class) + @PopulatedByPass(CallResolver::class) var invokes: List get(): List { val targets: MutableList = ArrayList() diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolver.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolver.kt index 5381390552..5c6eaf15d7 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolver.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolver.kt @@ -26,11 +26,7 @@ package de.fraunhofer.aisec.cpg.passes import de.fraunhofer.aisec.cpg.TranslationContext -import de.fraunhofer.aisec.cpg.frontends.HasComplexCallResolution -import de.fraunhofer.aisec.cpg.frontends.HasDefaultArguments -import de.fraunhofer.aisec.cpg.frontends.HasSuperClasses -import de.fraunhofer.aisec.cpg.frontends.HasTemplates -import de.fraunhofer.aisec.cpg.frontends.cpp.CPPLanguage +import de.fraunhofer.aisec.cpg.frontends.* import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.declarations.* import de.fraunhofer.aisec.cpg.graph.declarations.TemplateDeclaration.TemplateInitialization @@ -350,7 +346,7 @@ open class CallResolver(ctx: TranslationContext) : SymbolResolverPass(ctx) { if ( invocationCandidates.isEmpty() && callee.name.localName.isNotEmpty() && - (callee.language !is CPPLanguage || shouldSearchForInvokesInParent(call)) + (!callee.language.isCPP || shouldSearchForInvokesInParent(call)) ) { val records = possibleContainingTypes.mapNotNull { recordMap[it.root.name] }.toSet() invocationCandidates = @@ -543,7 +539,7 @@ open class CallResolver(ctx: TranslationContext) : SymbolResolverPass(ctx) { // FunctionDeclaration with the same name as the function in the CallExpression we have // to stop the search in the parent even if the FunctionDeclaration does not match with // the signature of the CallExpression - if (call.language is CPPLanguage) { // TODO: Needs a special trait? + if (call.language.isCPP) { // TODO: Needs a special trait? workingPossibleTypes.removeIf { recordDeclaration -> !shouldContinueSearchInParent(recordDeclaration, name) } @@ -557,6 +553,11 @@ open class CallResolver(ctx: TranslationContext) : SymbolResolverPass(ctx) { } } + private val Language?.isCPP: Boolean + get() { + return this != null && this::class.simpleName == "CPPLanguage" + } + private fun getOverridingCandidates( possibleSubTypes: Set, declaration: FunctionDeclaration @@ -602,9 +603,7 @@ open class CallResolver(ctx: TranslationContext) : SymbolResolverPass(ctx) { constructorCandidate = resolveConstructorWithDefaults(constructExpression, signature, recordDeclaration) } - if ( - constructorCandidate == null && constructExpression.language is CPPLanguage - ) { // TODO: Fix this + if (constructorCandidate == null && constructExpression.language.isCPP) { // TODO: Fix this // If we don't find any candidate and our current language is c/c++ we check if there is // a candidate with an implicit cast constructorCandidate = diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/LanguageFrontendTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/LanguageFrontendTest.kt deleted file mode 100644 index 74f9be3cd2..0000000000 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/LanguageFrontendTest.kt +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (c) 2019, 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.frontends - -import de.fraunhofer.aisec.cpg.BaseTest -import de.fraunhofer.aisec.cpg.TranslationConfiguration -import de.fraunhofer.aisec.cpg.TranslationManager.Companion.builder -import java.io.File -import java.util.concurrent.ExecutionException -import kotlin.test.Test -import kotlin.test.assertEquals - -internal class LanguageFrontendTest : BaseTest() { - @Test - @Throws(ExecutionException::class, InterruptedException::class) - fun testParseDirectory() { - val analyzer = - builder() - .config( - TranslationConfiguration.builder() - .sourceLocations(File("src/test/resources/botan")) - .debugParser(true) - .defaultLanguages() - .build() - ) - .build() - val res = analyzer.analyze().get() - assertEquals(3, res.translationUnits.size) - } -} diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/BotanExampleTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/BotanExampleTest.kt deleted file mode 100644 index 390635be29..0000000000 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/BotanExampleTest.kt +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (c) 2019, 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.frontends.cpp - -import de.fraunhofer.aisec.cpg.BaseTest -import de.fraunhofer.aisec.cpg.TestUtils.analyzeAndGetFirstTU -import de.fraunhofer.aisec.cpg.assertLocalName -import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration -import java.io.File -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertNotNull -import kotlin.test.assertTrue - -internal class BotanExampleTest : BaseTest() { - @Test - fun testExample() { - val file = File("src/test/resources/botan/symm_block_cipher.cpp") - val declaration = analyzeAndGetFirstTU(listOf(file), file.parentFile.toPath(), false) - assertNotNull(declaration) - - val declarations = declaration.declarations - assertEquals(5, declarations.size) - - val doCrypt = declarations.firstOrNull { it.name.localName == "do_crypt" } - assertTrue(doCrypt is FunctionDeclaration) - assertLocalName("do_crypt", doCrypt) - - val encrypt = declarations.firstOrNull { it.name.localName == "encrypt" } - assertTrue(encrypt is FunctionDeclaration) - assertLocalName("encrypt", encrypt) - - val decrypt = declarations.firstOrNull { it.name.localName == "decrypt" } - assertTrue(decrypt is FunctionDeclaration) - assertLocalName("decrypt", decrypt) - - val main = declarations.firstOrNull { it.name.localName == "main" } - assertTrue(main is FunctionDeclaration) - assertLocalName("main", main) - } -} diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/helpers/BenchmarkTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/helpers/BenchmarkTest.kt index ca17408db3..e8169130f9 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/helpers/BenchmarkTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/helpers/BenchmarkTest.kt @@ -25,13 +25,10 @@ */ package de.fraunhofer.aisec.cpg.helpers -import de.fraunhofer.aisec.cpg.TestUtils import java.io.File import kotlin.io.path.Path import kotlin.test.Test -import kotlin.test.assertContains import kotlin.test.assertEquals -import kotlin.test.assertNotNull class BenchmarkTest { @@ -53,34 +50,4 @@ class BenchmarkTest { assertEquals(relPath, relativeOrAbsolute(relPath, null)) assertEquals(absPath, relativeOrAbsolute(absPath, null)) } - - @Test - fun testGetBenchmarkResult() { - val file = File("src/test/resources/components/foreachstmt.cpp") - val tr = TestUtils.analyze(listOf(file), file.parentFile.toPath(), true) - - assertNotNull(tr) - val res = tr.benchmarkResults - assertNotNull(res) - - val resMap = res.entries.associate { it[0] to it[1] } - assertEquals(1, resMap["Number of files translated"]) - - val files = resMap["Translated file(s)"] as List<*> - assertNotNull(files) - assertEquals(1, files.size) - assertEquals(Path("foreachstmt.cpp"), files[0]) - - val json = res.json - assertContains(json, "{") - } - - @Test - fun testPrintBenchmark() { - val file = File("src/test/resources/components/foreachstmt.cpp") - val tr = TestUtils.analyze(listOf(file), file.parentFile.toPath(), true) - - assertNotNull(tr) - tr.benchmarkResults.print() - } } diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/scopes/ScopeManagerTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/scopes/ScopeManagerTest.kt index d6dbeb8ca7..31086a2685 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/scopes/ScopeManagerTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/scopes/ScopeManagerTest.kt @@ -25,18 +25,16 @@ */ package de.fraunhofer.aisec.cpg.passes.scopes -import de.fraunhofer.aisec.cpg.* -import de.fraunhofer.aisec.cpg.frontends.TranslationException -import de.fraunhofer.aisec.cpg.frontends.cpp.CPPLanguage -import de.fraunhofer.aisec.cpg.frontends.cpp.CXXLanguageFrontend +import de.fraunhofer.aisec.cpg.BaseTest +import de.fraunhofer.aisec.cpg.ScopeManager +import de.fraunhofer.aisec.cpg.TranslationConfiguration +import de.fraunhofer.aisec.cpg.TranslationContext +import de.fraunhofer.aisec.cpg.frontends.TestLanguage +import de.fraunhofer.aisec.cpg.frontends.TestLanguageFrontend import de.fraunhofer.aisec.cpg.graph.* -import de.fraunhofer.aisec.cpg.graph.declarations.ConstructorDeclaration -import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration import de.fraunhofer.aisec.cpg.graph.scopes.NameScope -import java.io.File import kotlin.test.* -// TODO(oxisto): Use TestLanguage instead of CPPLanguage/JavaLanguage internal class ScopeManagerTest : BaseTest() { private lateinit var config: TranslationConfiguration @@ -45,41 +43,12 @@ internal class ScopeManagerTest : BaseTest() { config = TranslationConfiguration.builder().defaultPasses().build() } - @Test - @Throws(TranslationException::class) - fun testReplaceNode() { - val scopeManager = ScopeManager() - val frontend = - CXXLanguageFrontend( - CPPLanguage(), - TranslationContext(config, scopeManager, TypeManager()) - ) - val tu = frontend.parse(File("src/test/resources/cxx/recordstmt.cpp")) - val methods = tu.allChildren().filter { it !is ConstructorDeclaration } - assertFalse(methods.isEmpty()) - - methods.forEach { - val scope = scopeManager.lookupScope(it) - assertSame(it, scope!!.astNode) - } - - val constructors = tu.allChildren() - assertFalse(constructors.isEmpty()) - - // make sure that the scope of the constructor actually has the constructor as an ast node. - // this is necessary, since the constructor was probably created as a function declaration - // which later gets 'upgraded' to a constructor declaration. - constructors.forEach { - val scope = scopeManager.lookupScope(it) - assertSame(it, scope!!.astNode) - } - } - @Test fun testMerge() { val tm = TypeManager() val s1 = ScopeManager() - val frontend1 = CXXLanguageFrontend(CPPLanguage(), TranslationContext(config, s1, tm)) + val frontend1 = + TestLanguageFrontend("::", TestLanguage(), TranslationContext(config, s1, tm)) s1.resetToGlobal(frontend1.newTranslationUnitDeclaration("f1.cpp", null)) // build a namespace declaration in f1.cpp with the namespace A @@ -90,7 +59,8 @@ internal class ScopeManagerTest : BaseTest() { s1.leaveScope(namespaceA1) val s2 = ScopeManager() - val frontend2 = CXXLanguageFrontend(CPPLanguage(), TranslationContext(config, s2, tm)) + val frontend2 = + TestLanguageFrontend("::", TestLanguage(), TranslationContext(config, s2, tm)) s2.resetToGlobal(frontend2.newTranslationUnitDeclaration("f1.cpp", null)) // and do the same in the other file @@ -102,7 +72,8 @@ internal class ScopeManagerTest : BaseTest() { // merge the two scopes. this replicates the behaviour of parseParallel val final = ScopeManager() - val frontend = CXXLanguageFrontend(CPPLanguage(), TranslationContext(config, final, tm)) + val frontend = + TestLanguageFrontend("::", TestLanguage(), TranslationContext(config, final, tm)) final.mergeFrom(listOf(s1, s2)) // in the final scope manager, there should only be one NameScope "A" @@ -140,7 +111,7 @@ internal class ScopeManagerTest : BaseTest() { fun testScopeFQN() { val s = ScopeManager() val frontend = - CXXLanguageFrontend(CPPLanguage(), TranslationContext(config, s, TypeManager())) + TestLanguageFrontend("::", TestLanguage(), TranslationContext(config, s, TypeManager())) s.resetToGlobal(frontend.newTranslationUnitDeclaration("file.cpp", null)) assertNull(s.currentNamespace) diff --git a/cpg-core/src/test/resources/botan/CMakeLists.txt b/cpg-core/src/test/resources/botan/CMakeLists.txt deleted file mode 100644 index fcba6a291c..0000000000 --- a/cpg-core/src/test/resources/botan/CMakeLists.txt +++ /dev/null @@ -1,10 +0,0 @@ -cmake_minimum_required(VERSION 3.12) -project(botan) - -set(CMAKE_CXX_STANDARD 17) - -include_directories(/usr/include/botan-2) - -link_libraries(botan-2) - -add_executable(symm_block_cipher symm_block_cipher.cpp) \ No newline at end of file diff --git a/cpg-core/src/test/resources/botan/simple.cpp b/cpg-core/src/test/resources/botan/simple.cpp deleted file mode 100644 index 8c631ea96e..0000000000 --- a/cpg-core/src/test/resources/botan/simple.cpp +++ /dev/null @@ -1,64 +0,0 @@ -#include -#include -//#include "test" -//int bar(int i) {return i;} - -namespace std { - template - class unique_ptr { - private: - T number; - - public: - SomeClass(T number) : number() { - - } - void do_sth() {} - }; -} -// -//class Test { -// public void do_sth() {} -//} -// -//template -//class SomeClass { -//private: -// T number; -// -// public: -// SomeClass(T number) : number() { -// -// } -// void do_sth() {} -//}; -// -//int main2() { -// SomeClass c(5); -// UnknownClass d(4); -// Test e(3); -// -// c.do_sth(); -// d.do_sth(); -// e.do_sth(); -// -// { -// int i = 12; -// } -// { -// float i = 10; -// i++; -// } -// { -// i = 41; -// } -//} -// -//int main() { -// Test t(); -// t.do_sth() -// foo(bar(1)); -// return 0; -//} -// -// diff --git a/cpg-core/src/test/resources/botan/simple2.cpp b/cpg-core/src/test/resources/botan/simple2.cpp deleted file mode 100644 index 2c23b0af54..0000000000 --- a/cpg-core/src/test/resources/botan/simple2.cpp +++ /dev/null @@ -1,38 +0,0 @@ -//#include -#include -#include "simple.cpp" - -#define __IV_LENGTH 16 - -//namespace std { -// template -// class unique_ptr { -// private: -// T number; -// -// public: -// SomeClass(T number) : number() { -// -// } -// void do_sth() {} -// }; -//} -using namespace std; - - class Test { - private: - float a; - public: - void foo() {} - Test(float b) {a = b;} - }; - - int* do_crypt(float b) { - //std::unique_ptr processor(new Test2(b)); - - processor->foo(); - string hans = "asd"; - std::string hans = "asd"; - - return __IV_LENGTH; - } diff --git a/cpg-core/src/test/resources/botan/symm_block_cipher.cpp b/cpg-core/src/test/resources/botan/symm_block_cipher.cpp deleted file mode 100644 index 904befbc82..0000000000 --- a/cpg-core/src/test/resources/botan/symm_block_cipher.cpp +++ /dev/null @@ -1,87 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include - -/*BAD example MS1(BlockCiphers) - #define __CIPHER "Twofish/CBC" -*/ - -#define __CIPHER "AES-256/CBC" -#define __KEY_LENGTH 32 -#define __IV_LENGTH 16 - -/* BAD example MS1(UseRandomIV) - Botan::InitializationVector __IV("deadbeefdeadbeefdeadbeefdeadbeef"); -*/ - -Botan::InitializationVector __IV; - -Botan::secure_vector do_crypt(const std::string &cipher, - const std::vector &input, - const Botan::SymmetricKey &key, - const Botan::InitializationVector &iv, - Botan::Cipher_Dir direction) -{ - if(iv.size() == 0) - throw std::runtime_error("IV must not be empty"); - - std::unique_ptr processor(Botan::get_cipher_mode(cipher, direction)); - if(!processor) - throw std::runtime_error("Cipher algorithm not found"); - - // Set key - processor->set_key(key); - - // Set IV - processor->start(iv.bits_of()); - - Botan::secure_vector buf(input.begin(), input.end()); - processor->finish(buf); - - return buf; -} - - -std::string encrypt(std::string cleartext) { - const std::string key_hex = "f00dbabef00dbabef00dbabef00dbabef00dbabef00dbabef00dbabef00dbabe"; - const Botan::SymmetricKey key(key_hex); - assert(key.length() == __KEY_LENGTH); - - Botan::AutoSeeded_RNG rng; - __IV = rng.random_vec(__IV_LENGTH); - - const std::vector input(cleartext.begin(), cleartext.end()); - std::cerr << "Got " << input.size() << " bytes of input data.\n"; - - Botan::secure_vector cipherblob = do_crypt(__CIPHER, input, key, __IV, Botan::Cipher_Dir::ENCRYPTION); - return Botan::hex_encode(cipherblob); -} - -std::string decrypt(const std::string& ciphertext) { - const std::string key_hex = "f00dbabef00dbabef00dbabef00dbabef00dbabef00dbabef00dbabef00dbabe"; - const Botan::SymmetricKey key(key_hex); - assert(key.length() == __KEY_LENGTH); - - const std::vector input = Botan::hex_decode(ciphertext); - std::cerr << "Got " << input.size() << " bytes of ciphertext data.\n"; - - - Botan::secure_vector clearblob = do_crypt(__CIPHER, input, key, __IV, Botan::Cipher_Dir::DECRYPTION); - return std::string(clearblob.begin(), clearblob.end()); -} - - -int main() { - std::string cleartext = "Hello World"; - auto ciphertext = encrypt(cleartext); - std::cout << "ciphertext: " << ciphertext << std::endl; - auto cleartext_decrypted = decrypt(ciphertext); - std::cout << "cleartext_decrypted: " << cleartext_decrypted << std::endl; - return 0; -} - - diff --git a/cpg-language-cxx/build.gradle.kts b/cpg-language-cxx/build.gradle.kts new file mode 100644 index 0000000000..8ca370de65 --- /dev/null +++ b/cpg-language-cxx/build.gradle.kts @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2023, 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. + * + * $$$$$$\ $$$$$$$\ $$$$$$\ + * $$ __$$\ $$ __$$\ $$ __$$\ + * $$ / \__|$$ | $$ |$$ / \__| + * $$ | $$$$$$$ |$$ |$$$$\ + * $$ | $$ ____/ $$ |\_$$ | + * $$ | $$\ $$ | $$ | $$ | + * \$$$$$ |$$ | \$$$$$ | + * \______/ \__| \______/ + * + */ +plugins { + id("cpg.frontend-conventions") +} + +publishing { + publications { + named("cpg-language-cxx") { + pom { + artifactId = "cpg-language-cxx" + name.set("Code Property Graph - C/C++ Frontend") + description.set("A C/C++ language frontend for the CPG") + } + } + } +} + +tasks.withType().configureEach { + kotlinOptions { + freeCompilerArgs = listOf("-Xcontext-receivers") + } +} + +dependencies { + api(libs.javaparser) + + testImplementation(libs.junit.params) +} diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CLanguage.kt b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CLanguage.kt similarity index 99% rename from cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CLanguage.kt rename to cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CLanguage.kt index 244b0da599..79c8538a23 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CLanguage.kt +++ b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CLanguage.kt @@ -23,7 +23,7 @@ * \______/ \__| \______/ * */ -package de.fraunhofer.aisec.cpg.frontends.cpp +package de.fraunhofer.aisec.cpg.frontends.cxx import com.fasterxml.jackson.annotation.JsonIgnore import de.fraunhofer.aisec.cpg.TranslationContext diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CPPLanguage.kt b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CPPLanguage.kt similarity index 99% rename from cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CPPLanguage.kt rename to cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CPPLanguage.kt index b986a512fb..3e1333927a 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CPPLanguage.kt +++ b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CPPLanguage.kt @@ -23,7 +23,7 @@ * \______/ \__| \______/ * */ -package de.fraunhofer.aisec.cpg.frontends.cpp +package de.fraunhofer.aisec.cpg.frontends.cxx import de.fraunhofer.aisec.cpg.TranslationContext import de.fraunhofer.aisec.cpg.frontends.* diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CXXHandler.kt b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXHandler.kt similarity index 98% rename from cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CXXHandler.kt rename to cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXHandler.kt index e6373d8e0a..e2b5224d7f 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CXXHandler.kt +++ b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXHandler.kt @@ -23,7 +23,7 @@ * \______/ \__| \______/ * */ -package de.fraunhofer.aisec.cpg.frontends.cpp +package de.fraunhofer.aisec.cpg.frontends.cxx import de.fraunhofer.aisec.cpg.frontends.Handler import de.fraunhofer.aisec.cpg.graph.Node diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CXXLanguageFrontend.kt b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXLanguageFrontend.kt similarity index 98% rename from cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CXXLanguageFrontend.kt rename to cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXLanguageFrontend.kt index cf8fd8cc4d..b681ef2ca7 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CXXLanguageFrontend.kt +++ b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXLanguageFrontend.kt @@ -23,7 +23,7 @@ * \______/ \__| \______/ * */ -package de.fraunhofer.aisec.cpg.frontends.cpp +package de.fraunhofer.aisec.cpg.frontends.cxx import de.fraunhofer.aisec.cpg.ResolveInFrontend import de.fraunhofer.aisec.cpg.TranslationContext @@ -40,6 +40,8 @@ import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression import de.fraunhofer.aisec.cpg.graph.types.* import de.fraunhofer.aisec.cpg.graph.types.FunctionType import de.fraunhofer.aisec.cpg.helpers.Benchmark +import de.fraunhofer.aisec.cpg.passes.FunctionPointerCallResolver +import de.fraunhofer.aisec.cpg.passes.order.RegisterExtraPass import de.fraunhofer.aisec.cpg.sarif.PhysicalLocation import de.fraunhofer.aisec.cpg.sarif.Region import java.io.File @@ -75,6 +77,7 @@ import org.slf4j.LoggerFactory * ad [GPPLanguage]). This enables us (to some degree) to deal with the finer difference between C * and C++ code. */ +@RegisterExtraPass(FunctionPointerCallResolver::class) class CXXLanguageFrontend(language: Language, ctx: TranslationContext) : LanguageFrontend(language, ctx) { @@ -196,9 +199,7 @@ class CXXLanguageFrontend(language: Language, ctx: Translat // include paths val includePaths: MutableList = ArrayList() - if (config.topLevel != null) { - includePaths.add(config.topLevel.toPath().toAbsolutePath().toString()) - } + config.topLevel?.let { includePaths.add(it.toPath().toAbsolutePath().toString()) } val symbols: HashMap = HashMap() symbols.putAll(config.symbols) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/DeclarationHandler.kt b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/DeclarationHandler.kt similarity index 99% rename from cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/DeclarationHandler.kt rename to cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/DeclarationHandler.kt index a314cdd2bf..968d817e86 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/DeclarationHandler.kt +++ b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/DeclarationHandler.kt @@ -23,7 +23,7 @@ * \______/ \__| \______/ * */ -package de.fraunhofer.aisec.cpg.frontends.cpp +package de.fraunhofer.aisec.cpg.frontends.cxx import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.declarations.* diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/DeclaratorHandler.kt b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/DeclaratorHandler.kt similarity index 99% rename from cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/DeclaratorHandler.kt rename to cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/DeclaratorHandler.kt index dd1162f62d..fdd53b30e2 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/DeclaratorHandler.kt +++ b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/DeclaratorHandler.kt @@ -23,7 +23,7 @@ * \______/ \__| \______/ * */ -package de.fraunhofer.aisec.cpg.frontends.cpp +package de.fraunhofer.aisec.cpg.frontends.cxx import de.fraunhofer.aisec.cpg.ResolveInFrontend import de.fraunhofer.aisec.cpg.graph.* diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/ExpressionHandler.kt b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/ExpressionHandler.kt similarity index 84% rename from cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/ExpressionHandler.kt rename to cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/ExpressionHandler.kt index bc23a553c0..b4f515597f 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/ExpressionHandler.kt +++ b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/ExpressionHandler.kt @@ -23,7 +23,7 @@ * \______/ \__| \______/ * */ -package de.fraunhofer.aisec.cpg.frontends.cpp +package de.fraunhofer.aisec.cpg.frontends.cxx import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration @@ -393,56 +393,62 @@ class ExpressionHandler(lang: CXXLanguageFrontend) : private fun handleFunctionCallExpression(ctx: IASTFunctionCallExpression): Expression { val reference = handle(ctx.functionNameExpression) val callExpression: CallExpression - if (reference is MemberExpression) { - val baseType = reference.base.type.root - assert(baseType !is SecondOrderType) - callExpression = newMemberCallExpression(reference, code = ctx.rawSignature) - if ( - (ctx.functionNameExpression as? IASTFieldReference)?.fieldName is CPPASTTemplateId - ) { - // Make necessary adjustments if we are handling a function template + when { + reference is MemberExpression -> { + val baseType = reference.base.type.root + assert(baseType !is SecondOrderType) + callExpression = newMemberCallExpression(reference, code = ctx.rawSignature) + if ( + (ctx.functionNameExpression as? IASTFieldReference)?.fieldName + is CPPASTTemplateId + ) { + // Make necessary adjustments if we are handling a function template + val name = + ((ctx.functionNameExpression as IASTFieldReference).fieldName + as CPPASTTemplateId) + .templateName + .toString() + callExpression.name = language.parseName(name) + getTemplateArguments( + (ctx.functionNameExpression as IASTFieldReference).fieldName + as CPPASTTemplateId + ) + .forEach { callExpression.addTemplateParameter(it) } + } + } + reference is BinaryOperator && + (reference.operatorCode == ".*" || reference.operatorCode == "->*") -> { + // This is a function pointer call to a class method. We keep this as a binary + // operator + // with the .* or ->* operator code, so that we can resolve this later in the + // FunctionPointerCallResolver + callExpression = newMemberCallExpression(reference, code = ctx.rawSignature) + } + reference is UnaryOperator && reference.operatorCode == "*" -> { + // Classic C-style function pointer call -> let's extract the target + callExpression = newCallExpression(reference, "", reference.code, false) + } + ctx.functionNameExpression is IASTIdExpression && + (ctx.functionNameExpression as IASTIdExpression).name is CPPASTTemplateId -> { val name = - ((ctx.functionNameExpression as IASTFieldReference).fieldName - as CPPASTTemplateId) + ((ctx.functionNameExpression as IASTIdExpression).name as CPPASTTemplateId) .templateName .toString() - callExpression.name = language.parseName(name) + val ref = newDeclaredReferenceExpression(name) + callExpression = newCallExpression(ref, name, ctx.rawSignature, true) getTemplateArguments( - (ctx.functionNameExpression as IASTFieldReference).fieldName - as CPPASTTemplateId + (ctx.functionNameExpression as IASTIdExpression).name as CPPASTTemplateId ) .forEach { callExpression.addTemplateParameter(it) } } - } else if ( - reference is BinaryOperator && - (reference.operatorCode == ".*" || reference.operatorCode == "->*") - ) { - // This is a function pointer call to a class method. We keep this as a binary operator - // with the .* or ->* operator code, so that we can resolve this later in the - // FunctionPointerCallResolver - callExpression = newMemberCallExpression(reference, code = ctx.rawSignature) - } else if (reference is UnaryOperator && reference.operatorCode == "*") { - // Classic C-style function pointer call -> let's extract the target - callExpression = newCallExpression(reference, "", reference.code, false) - } else if ( - ctx.functionNameExpression is IASTIdExpression && - (ctx.functionNameExpression as IASTIdExpression).name is CPPASTTemplateId - ) { - val name = - ((ctx.functionNameExpression as IASTIdExpression).name as CPPASTTemplateId) - .templateName - .toString() - val ref = newDeclaredReferenceExpression(name) - callExpression = newCallExpression(ref, name, ctx.rawSignature, true) - getTemplateArguments( - (ctx.functionNameExpression as IASTIdExpression).name as CPPASTTemplateId - ) - .forEach { callExpression.addTemplateParameter(it) } - } else if (reference is CastExpression) { - // this really is a cast expression in disguise - return reference - } else { - callExpression = newCallExpression(reference, reference?.name, ctx.rawSignature, false) + reference is CastExpression -> { + // this really is a cast expression in disguise + return reference + } + else -> { + callExpression = + newCallExpression(reference, reference?.name, ctx.rawSignature, false) + } } for ((i, argument) in ctx.arguments.withIndex()) { @@ -682,15 +688,19 @@ class ExpressionHandler(lang: CXXLanguageFrontend) : // next, check for possible prefixes var radix = 10 var offset = 0 - if (strippedValue.startsWith("0b")) { - radix = 2 // binary - offset = 2 // len("0b") - } else if (strippedValue.startsWith("0x")) { - radix = 16 // hex - offset = 2 // len("0x") - } else if (strippedValue.startsWith("0") && strippedValue.length > 1) { - radix = 8 // octal - offset = 1 // len("0") + when { + strippedValue.startsWith("0b") -> { + radix = 2 // binary + offset = 2 // len("0b") + } + strippedValue.startsWith("0x") -> { + radix = 16 // hex + offset = 2 // len("0x") + } + strippedValue.startsWith("0") && strippedValue.length > 1 -> { + radix = 8 // octal + offset = 1 // len("0") + } } // strip the prefix @@ -702,61 +712,65 @@ class ExpressionHandler(lang: CXXLanguageFrontend) : // basically we parse everything as BigInteger and then decide what to do try { bigValue = BigInteger(strippedValue, radix) - val numberValue: Number - if ("ull" == suffix || "ul" == suffix) { - // unsigned long (long) will always be represented as BigInteger - numberValue = bigValue - } else if ("ll" == suffix || "l" == suffix) { - // both long and long long can be represented in Java long, but only within - // Long.MAX_VALUE - if (bigValue > BigInteger.valueOf(Long.MAX_VALUE)) { - // keep it as BigInteger - numberValue = bigValue - Util.warnWithFileLocation( - frontend, - ctx, - log, - "Integer literal {} is too large to be represented in a signed type, interpreting it as unsigned.", - ctx - ) - } else { - numberValue = bigValue.toLong() + val numberValue: Number = + when { + "ull" == suffix || "ul" == suffix -> { + // unsigned long (long) will always be represented as BigInteger + bigValue + } + "ll" == suffix || "l" == suffix -> { + // both long and long long can be represented in Java long, but only within + // Long.MAX_VALUE + if (bigValue > BigInteger.valueOf(Long.MAX_VALUE)) { + Util.warnWithFileLocation( + frontend, + ctx, + log, + "Integer literal {} is too large to be represented in a signed type, interpreting it as unsigned.", + ctx + ) + // keep it as BigInteger + bigValue + } else { + bigValue.toLong() + } + } + bigValue > BigInteger.valueOf(Long.MAX_VALUE) -> { + // No suffix, we just cast it to the appropriate signed type that is + // required, but + // only within Long.MAX_VALUE + Util.warnWithFileLocation( + frontend, + ctx, + log, + "Integer literal {} is too large to be represented in a signed type, interpreting it as unsigned.", + ctx + ) + // keep it as BigInteger + bigValue + } + bigValue.toLong() > Int.MAX_VALUE -> { + bigValue.toLong() + } + else -> { + bigValue.toInt() + } } - } else if (bigValue > BigInteger.valueOf(Long.MAX_VALUE)) { - // No suffix, we just cast it to the appropriate signed type that is required, but - // only within Long.MAX_VALUE - - // keep it as BigInteger - numberValue = bigValue - Util.warnWithFileLocation( - frontend, - ctx, - log, - "Integer literal {} is too large to be represented in a signed type, interpreting it as unsigned.", - ctx - ) - } else if (bigValue.toLong() > Int.MAX_VALUE) { - numberValue = bigValue.toLong() - } else { - numberValue = bigValue.toInt() - } // retrieve type based on stored Java number val type = - if (numberValue is BigInteger && "ul" == suffix) { - // we follow the way clang/llvm handles this and this seems to always - // be an unsigned long long, except if it is explicitly specified as ul - parseType("unsigned long") - } else if (numberValue is BigInteger) { - parseType("unsigned long long") - } else if (numberValue is Long && "ll" == suffix) { - // differentiate between long and long long - parseType("long long") - } else if (numberValue is Long) { - parseType("long") - } else { - parseType("int") - } + parseType( + when { + // we follow the way clang/llvm handles this and this seems to always + // be an unsigned long long, except if it is explicitly specified as ul + // differentiate between long and long long + numberValue is BigInteger && "ul" == suffix -> "unsigned long" + numberValue is BigInteger -> "unsigned long long" + numberValue is Long && "ll" == suffix -> "long long" + numberValue is Long -> "long" + else -> "int" + } + ) return newLiteral(numberValue, type, ctx.rawSignature) } catch (ex: NumberFormatException) { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/InitializerHandler.kt b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/InitializerHandler.kt similarity index 98% rename from cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/InitializerHandler.kt rename to cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/InitializerHandler.kt index 82079faf4f..be3311744a 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/InitializerHandler.kt +++ b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/InitializerHandler.kt @@ -23,7 +23,7 @@ * \______/ \__| \______/ * */ -package de.fraunhofer.aisec.cpg.frontends.cpp +package de.fraunhofer.aisec.cpg.frontends.cxx import de.fraunhofer.aisec.cpg.graph.newConstructExpression import de.fraunhofer.aisec.cpg.graph.newProblemExpression diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/ParameterDeclarationHandler.kt b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/ParameterDeclarationHandler.kt similarity index 98% rename from cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/ParameterDeclarationHandler.kt rename to cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/ParameterDeclarationHandler.kt index da130534d3..94b990e2a8 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/ParameterDeclarationHandler.kt +++ b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/ParameterDeclarationHandler.kt @@ -23,7 +23,7 @@ * \______/ \__| \______/ * */ -package de.fraunhofer.aisec.cpg.frontends.cpp +package de.fraunhofer.aisec.cpg.frontends.cxx import de.fraunhofer.aisec.cpg.graph.declarations.Declaration import de.fraunhofer.aisec.cpg.graph.declarations.ParamVariableDeclaration diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/StatementHandler.kt b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/StatementHandler.kt similarity index 99% rename from cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/StatementHandler.kt rename to cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/StatementHandler.kt index 63a398818d..8d4d8cc561 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/StatementHandler.kt +++ b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/StatementHandler.kt @@ -23,7 +23,7 @@ * \______/ \__| \______/ * */ -package de.fraunhofer.aisec.cpg.frontends.cpp +package de.fraunhofer.aisec.cpg.frontends.cxx import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.declarations.Declaration diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/package-info.kt b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/package-info.kt similarity index 95% rename from cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/package-info.kt rename to cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/package-info.kt index d29992fc3a..4bcfe4ded4 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/package-info.kt +++ b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/package-info.kt @@ -23,4 +23,4 @@ * \______/ \__| \______/ * */ -package de.fraunhofer.aisec.cpg.frontends.cpp +package de.fraunhofer.aisec.cpg.frontends.cxx diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/FunctionPointerCallResolver.kt b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/FunctionPointerCallResolver.kt similarity index 99% rename from cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/FunctionPointerCallResolver.kt rename to cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/FunctionPointerCallResolver.kt index cc50cdff5b..225840df5f 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/FunctionPointerCallResolver.kt +++ b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/FunctionPointerCallResolver.kt @@ -26,7 +26,7 @@ package de.fraunhofer.aisec.cpg.passes import de.fraunhofer.aisec.cpg.TranslationContext -import de.fraunhofer.aisec.cpg.frontends.cpp.CXXLanguageFrontend +import de.fraunhofer.aisec.cpg.frontends.cxx.CXXLanguageFrontend import de.fraunhofer.aisec.cpg.graph.Component import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/PerformanceRegressionTest.kt b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/PerformanceRegressionTest.kt similarity index 97% rename from cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/PerformanceRegressionTest.kt rename to cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/PerformanceRegressionTest.kt index 77ad7f7764..ef33d46eaf 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/PerformanceRegressionTest.kt +++ b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/PerformanceRegressionTest.kt @@ -27,8 +27,8 @@ package de.fraunhofer.aisec.cpg import de.fraunhofer.aisec.cpg.TestUtils.analyzeAndGetFirstTU import de.fraunhofer.aisec.cpg.frontends.TestLanguageFrontend -import de.fraunhofer.aisec.cpg.frontends.cpp.CPPLanguage -import de.fraunhofer.aisec.cpg.frontends.cpp.CXXLanguageFrontend +import de.fraunhofer.aisec.cpg.frontends.cxx.CPPLanguage +import de.fraunhofer.aisec.cpg.frontends.cxx.CXXLanguageFrontend import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration diff --git a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/ScopeManagerCXXTest.kt b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/ScopeManagerCXXTest.kt new file mode 100644 index 0000000000..d040f58a10 --- /dev/null +++ b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/ScopeManagerCXXTest.kt @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2023, 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 + +import de.fraunhofer.aisec.cpg.frontends.TranslationException +import de.fraunhofer.aisec.cpg.frontends.cxx.CPPLanguage +import de.fraunhofer.aisec.cpg.frontends.cxx.CXXLanguageFrontend +import de.fraunhofer.aisec.cpg.graph.* +import de.fraunhofer.aisec.cpg.graph.TypeManager +import de.fraunhofer.aisec.cpg.graph.declarations.ConstructorDeclaration +import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration +import java.io.File +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertFalse +import kotlin.test.assertSame + +internal class ScopeManagerTest : BaseTest() { + private lateinit var config: TranslationConfiguration + + @BeforeTest + fun setUp() { + config = TranslationConfiguration.builder().defaultPasses().build() + } + + @Test + @Throws(TranslationException::class) + fun testReplaceNode() { + val scopeManager = ScopeManager() + val frontend = + CXXLanguageFrontend( + CPPLanguage(), + TranslationContext(config, scopeManager, TypeManager()) + ) + val tu = frontend.parse(File("src/test/resources/cxx/recordstmt.cpp")) + val methods = tu.allChildren().filter { it !is ConstructorDeclaration } + assertFalse(methods.isEmpty()) + + methods.forEach { + val scope = scopeManager.lookupScope(it) + assertSame(it, scope!!.astNode) + } + + val constructors = tu.allChildren() + assertFalse(constructors.isEmpty()) + + // make sure that the scope of the constructor actually has the constructor as an ast node. + // this is necessary, since the constructor was probably created as a function declaration + // which later gets 'upgraded' to a constructor declaration. + constructors.forEach { + val scope = scopeManager.lookupScope(it) + assertSame(it, scope!!.astNode) + } + } +} diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/EOGTest.kt b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/EOGTest.kt similarity index 100% rename from cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/EOGTest.kt rename to cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/EOGTest.kt diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/calls/ConstructorsTest.kt b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/calls/ConstructorsTest.kt similarity index 100% rename from cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/calls/ConstructorsTest.kt rename to cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/calls/ConstructorsTest.kt diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/calls/FunctionPointerTest.kt b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/calls/FunctionPointerTest.kt similarity index 100% rename from cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/calls/FunctionPointerTest.kt rename to cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/calls/FunctionPointerTest.kt diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/templates/ClassTemplateTest.kt b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/templates/ClassTemplateTest.kt similarity index 100% rename from cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/templates/ClassTemplateTest.kt rename to cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/templates/ClassTemplateTest.kt diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/templates/FunctionTemplateTest.kt b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/templates/FunctionTemplateTest.kt similarity index 99% rename from cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/templates/FunctionTemplateTest.kt rename to cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/templates/FunctionTemplateTest.kt index bc988cadd3..102ae391e7 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/templates/FunctionTemplateTest.kt +++ b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/templates/FunctionTemplateTest.kt @@ -30,7 +30,7 @@ import de.fraunhofer.aisec.cpg.TestUtils.analyze import de.fraunhofer.aisec.cpg.TestUtils.findByUniqueName import de.fraunhofer.aisec.cpg.TestUtils.findByUniquePredicate import de.fraunhofer.aisec.cpg.assertLocalName -import de.fraunhofer.aisec.cpg.frontends.cpp.CPPLanguage +import de.fraunhofer.aisec.cpg.frontends.cxx.CPPLanguage import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.declarations.* import de.fraunhofer.aisec.cpg.graph.edge.Properties diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/types/TypeTests.kt b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/types/TypeTests.kt similarity index 99% rename from cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/types/TypeTests.kt rename to cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/types/TypeTests.kt index cc8da1792c..736e749bb9 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/types/TypeTests.kt +++ b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/types/TypeTests.kt @@ -23,15 +23,16 @@ * \______/ \__| \______/ * */ -package de.fraunhofer.aisec.cpg.graph.types +package de.fraunhofer.aisec.cpg.enhancements.types import de.fraunhofer.aisec.cpg.* import de.fraunhofer.aisec.cpg.TestUtils.analyze import de.fraunhofer.aisec.cpg.TestUtils.analyzeAndGetFirstTU import de.fraunhofer.aisec.cpg.TestUtils.findByUniqueName -import de.fraunhofer.aisec.cpg.frontends.cpp.CPPLanguage -import de.fraunhofer.aisec.cpg.frontends.cpp.CXXLanguageFrontend +import de.fraunhofer.aisec.cpg.frontends.cxx.CPPLanguage +import de.fraunhofer.aisec.cpg.frontends.cxx.CXXLanguageFrontend import de.fraunhofer.aisec.cpg.graph.* +import de.fraunhofer.aisec.cpg.graph.types.* import java.nio.file.Path import java.util.* import kotlin.test.* diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/types/TypedefTest.kt b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/types/TypedefTest.kt similarity index 97% rename from cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/types/TypedefTest.kt rename to cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/types/TypedefTest.kt index c4d8e52cc1..73ba90efaa 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/types/TypedefTest.kt +++ b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/types/TypedefTest.kt @@ -23,7 +23,7 @@ * \______/ \__| \______/ * */ -package de.fraunhofer.aisec.cpg.graph.types +package de.fraunhofer.aisec.cpg.enhancements.types import de.fraunhofer.aisec.cpg.BaseTest import de.fraunhofer.aisec.cpg.TestUtils.analyze @@ -31,6 +31,8 @@ import de.fraunhofer.aisec.cpg.TestUtils.findByUniqueName import de.fraunhofer.aisec.cpg.TestUtils.findByUniquePredicate import de.fraunhofer.aisec.cpg.graph.declarations.ValueDeclaration import de.fraunhofer.aisec.cpg.graph.records +import de.fraunhofer.aisec.cpg.graph.types.FunctionPointerType +import de.fraunhofer.aisec.cpg.graph.types.NumericType import de.fraunhofer.aisec.cpg.graph.variables import java.nio.file.Path import kotlin.test.* diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/variable_resolution/VariableResolverCppTest.kt b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/variable_resolution/VariableResolverCppTest.kt similarity index 100% rename from cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/variable_resolution/VariableResolverCppTest.kt rename to cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/variable_resolution/VariableResolverCppTest.kt diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CXXAmbiguitiesTest.kt b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXAmbiguitiesTest.kt similarity index 99% rename from cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CXXAmbiguitiesTest.kt rename to cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXAmbiguitiesTest.kt index 1942e644ef..087f756f0f 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CXXAmbiguitiesTest.kt +++ b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXAmbiguitiesTest.kt @@ -23,7 +23,7 @@ * \______/ \__| \______/ * */ -package de.fraunhofer.aisec.cpg.frontends.cpp +package de.fraunhofer.aisec.cpg.frontends.cxx import de.fraunhofer.aisec.cpg.TestUtils import de.fraunhofer.aisec.cpg.graph.bodyOrNull diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CXXCompilationDatabaseTest.kt b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXCompilationDatabaseTest.kt similarity index 99% rename from cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CXXCompilationDatabaseTest.kt rename to cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXCompilationDatabaseTest.kt index a7e90e7642..8a9a38c7ca 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CXXCompilationDatabaseTest.kt +++ b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXCompilationDatabaseTest.kt @@ -23,7 +23,7 @@ * \______/ \__| \______/ * */ -package de.fraunhofer.aisec.cpg.frontends.cpp +package de.fraunhofer.aisec.cpg.frontends.cxx import de.fraunhofer.aisec.cpg.TestUtils import de.fraunhofer.aisec.cpg.graph.byNameOrNull diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CXXIncludeTest.kt b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXIncludeTest.kt similarity index 97% rename from cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CXXIncludeTest.kt rename to cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXIncludeTest.kt index 49e03bae93..0a4226ac40 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CXXIncludeTest.kt +++ b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXIncludeTest.kt @@ -23,7 +23,7 @@ * \______/ \__| \______/ * */ -package de.fraunhofer.aisec.cpg.frontends.cpp +package de.fraunhofer.aisec.cpg.frontends.cxx import de.fraunhofer.aisec.cpg.BaseTest import de.fraunhofer.aisec.cpg.TestUtils.analyzeAndGetFirstTU @@ -110,6 +110,7 @@ internal class CXXIncludeTest : BaseTest() { .topLevel(file.parentFile) .loadIncludes(true) .debugParser(true) + .defaultPasses() .defaultLanguages() .includeBlocklist(File("src/test/resources/include.h").absolutePath) .failOnError(true) @@ -137,6 +138,7 @@ internal class CXXIncludeTest : BaseTest() { .topLevel(file.parentFile) .loadIncludes(true) .debugParser(true) + .defaultPasses() .defaultLanguages() .includeBlocklist("include.h") .failOnError(true) @@ -164,6 +166,7 @@ internal class CXXIncludeTest : BaseTest() { .topLevel(file.parentFile) .loadIncludes(true) .debugParser(true) + .defaultPasses() .defaultLanguages() .includeWhitelist(File("src/test/resources/include.h").absolutePath) .failOnError(true) @@ -191,6 +194,7 @@ internal class CXXIncludeTest : BaseTest() { .topLevel(file.parentFile) .loadIncludes(true) .debugParser(true) + .defaultPasses() .defaultLanguages() .includeWhitelist("include.h") .failOnError(true) @@ -218,6 +222,7 @@ internal class CXXIncludeTest : BaseTest() { .topLevel(file.parentFile) .loadIncludes(true) .debugParser(true) + .defaultPasses() .defaultLanguages() .includeBlocklist("include.h") // blacklist entries take priority .includeWhitelist("include.h") @@ -249,6 +254,7 @@ internal class CXXIncludeTest : BaseTest() { .topLevel(file.parentFile) .loadIncludes(false) .debugParser(true) + .defaultPasses() .defaultLanguages() .failOnError(true) ) diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CXXLanguageFrontendTest.kt b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXLanguageFrontendTest.kt similarity index 99% rename from cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CXXLanguageFrontendTest.kt rename to cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXLanguageFrontendTest.kt index 3af1e05838..9c1c5b0692 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CXXLanguageFrontendTest.kt +++ b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXLanguageFrontendTest.kt @@ -23,7 +23,7 @@ * \______/ \__| \______/ * */ -package de.fraunhofer.aisec.cpg.frontends.cpp +package de.fraunhofer.aisec.cpg.frontends.cxx import de.fraunhofer.aisec.cpg.BaseTest import de.fraunhofer.aisec.cpg.InferenceConfiguration.Companion.builder diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CXXLiteralTest.kt b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXLiteralTest.kt similarity index 99% rename from cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CXXLiteralTest.kt rename to cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXLiteralTest.kt index f5580e1259..3f4634989d 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CXXLiteralTest.kt +++ b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXLiteralTest.kt @@ -23,7 +23,7 @@ * \______/ \__| \______/ * */ -package de.fraunhofer.aisec.cpg.frontends.cpp +package de.fraunhofer.aisec.cpg.frontends.cxx import de.fraunhofer.aisec.cpg.BaseTest import de.fraunhofer.aisec.cpg.TestUtils.analyzeAndGetFirstTU diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CXXResolveTest.kt b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXResolveTest.kt similarity index 99% rename from cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CXXResolveTest.kt rename to cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXResolveTest.kt index 9962b3e12d..6813991d47 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CXXResolveTest.kt +++ b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXResolveTest.kt @@ -23,7 +23,7 @@ * \______/ \__| \______/ * */ -package de.fraunhofer.aisec.cpg.frontends.cpp +package de.fraunhofer.aisec.cpg.frontends.cxx import de.fraunhofer.aisec.cpg.InferenceConfiguration import de.fraunhofer.aisec.cpg.TestUtils.analyzeAndGetFirstTU diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CXXSymbolConfigurationTest.kt b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXSymbolConfigurationTest.kt similarity index 99% rename from cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CXXSymbolConfigurationTest.kt rename to cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXSymbolConfigurationTest.kt index 39a4cb7465..1270cac616 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CXXSymbolConfigurationTest.kt +++ b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXSymbolConfigurationTest.kt @@ -23,7 +23,7 @@ * \______/ \__| \______/ * */ -package de.fraunhofer.aisec.cpg.frontends.cpp +package de.fraunhofer.aisec.cpg.frontends.cxx import de.fraunhofer.aisec.cpg.* import de.fraunhofer.aisec.cpg.frontends.TranslationException diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/LambdaTest.kt b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/LambdaTest.kt similarity index 99% rename from cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/LambdaTest.kt rename to cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/LambdaTest.kt index af551a50be..eb970ae368 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/LambdaTest.kt +++ b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/LambdaTest.kt @@ -23,7 +23,7 @@ * \______/ \__| \______/ * */ -package de.fraunhofer.aisec.cpg.frontends.cpp +package de.fraunhofer.aisec.cpg.frontends.cxx import de.fraunhofer.aisec.cpg.TranslationConfiguration import de.fraunhofer.aisec.cpg.TranslationManager diff --git a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/helpers/BenchmarkCXXTest.kt b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/helpers/BenchmarkCXXTest.kt new file mode 100644 index 0000000000..d0e5b5cbe4 --- /dev/null +++ b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/helpers/BenchmarkCXXTest.kt @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2023, 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.helpers + +import de.fraunhofer.aisec.cpg.TestUtils +import java.io.File +import kotlin.io.path.Path +import kotlin.test.assertContains +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import org.junit.jupiter.api.Test + +class BenchmarkCXXTest { + + @Test + fun testGetBenchmarkResult() { + val file = File("src/test/resources/components/foreachstmt.cpp") + val tr = TestUtils.analyze(listOf(file), file.parentFile.toPath(), true) + + assertNotNull(tr) + val res = tr.benchmarkResults + assertNotNull(res) + + val resMap = res.entries.associate { it[0] to it[1] } + assertEquals(1, resMap["Number of files translated"]) + + val files = resMap["Translated file(s)"] as List<*> + assertNotNull(files) + assertEquals(1, files.size) + assertEquals(Path("foreachstmt.cpp"), files[0]) + + val json = res.json + assertContains(json, "{") + } + + @Test + fun testPrintBenchmark() { + val file = File("src/test/resources/components/foreachstmt.cpp") + val tr = TestUtils.analyze(listOf(file), file.parentFile.toPath(), true) + + assertNotNull(tr) + tr.benchmarkResults.print() + } +} diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolverTest.kt b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolverTest.kt similarity index 99% rename from cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolverTest.kt rename to cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolverTest.kt index 2a5c0cdb0e..51056da5b5 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolverTest.kt +++ b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolverTest.kt @@ -33,7 +33,7 @@ import de.fraunhofer.aisec.cpg.TestUtils.findByUniqueName import de.fraunhofer.aisec.cpg.TestUtils.findByUniquePredicate import de.fraunhofer.aisec.cpg.TranslationResult import de.fraunhofer.aisec.cpg.assertLocalName -import de.fraunhofer.aisec.cpg.frontends.cpp.CPPLanguage +import de.fraunhofer.aisec.cpg.frontends.cxx.CPPLanguage import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.declarations.ConstructorDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration diff --git a/cpg-core/src/test/resources/another-include.h b/cpg-language-cxx/src/test/resources/another-include.h similarity index 100% rename from cpg-core/src/test/resources/another-include.h rename to cpg-language-cxx/src/test/resources/another-include.h diff --git a/cpg-core/src/test/resources/assignmentexpression.cpp b/cpg-language-cxx/src/test/resources/assignmentexpression.cpp similarity index 100% rename from cpg-core/src/test/resources/assignmentexpression.cpp rename to cpg-language-cxx/src/test/resources/assignmentexpression.cpp diff --git a/cpg-core/src/test/resources/attributes.cpp b/cpg-language-cxx/src/test/resources/attributes.cpp similarity index 100% rename from cpg-core/src/test/resources/attributes.cpp rename to cpg-language-cxx/src/test/resources/attributes.cpp diff --git a/cpg-core/src/test/resources/binaryoperator.cpp b/cpg-language-cxx/src/test/resources/binaryoperator.cpp similarity index 100% rename from cpg-core/src/test/resources/binaryoperator.cpp rename to cpg-language-cxx/src/test/resources/binaryoperator.cpp diff --git a/cpg-core/src/test/resources/bindings/replace_declaration.cpp b/cpg-language-cxx/src/test/resources/bindings/replace_declaration.cpp similarity index 100% rename from cpg-core/src/test/resources/bindings/replace_declaration.cpp rename to cpg-language-cxx/src/test/resources/bindings/replace_declaration.cpp diff --git a/cpg-core/src/test/resources/bindings/use_then_declare.cpp b/cpg-language-cxx/src/test/resources/bindings/use_then_declare.cpp similarity index 100% rename from cpg-core/src/test/resources/bindings/use_then_declare.cpp rename to cpg-language-cxx/src/test/resources/bindings/use_then_declare.cpp diff --git a/cpg-core/src/test/resources/c/enum.c b/cpg-language-cxx/src/test/resources/c/enum.c similarity index 100% rename from cpg-core/src/test/resources/c/enum.c rename to cpg-language-cxx/src/test/resources/c/enum.c diff --git a/cpg-core/src/test/resources/c/func_ptr_call.c b/cpg-language-cxx/src/test/resources/c/func_ptr_call.c similarity index 100% rename from cpg-core/src/test/resources/c/func_ptr_call.c rename to cpg-language-cxx/src/test/resources/c/func_ptr_call.c diff --git a/cpg-core/src/test/resources/c/struct.c b/cpg-language-cxx/src/test/resources/c/struct.c similarity index 100% rename from cpg-core/src/test/resources/c/struct.c rename to cpg-language-cxx/src/test/resources/c/struct.c diff --git a/cpg-core/src/test/resources/c/typedef_in_header/_header.h b/cpg-language-cxx/src/test/resources/c/typedef_in_header/_header.h similarity index 100% rename from cpg-core/src/test/resources/c/typedef_in_header/_header.h rename to cpg-language-cxx/src/test/resources/c/typedef_in_header/_header.h diff --git a/cpg-core/src/test/resources/c/typedef_in_header/header.h b/cpg-language-cxx/src/test/resources/c/typedef_in_header/header.h similarity index 100% rename from cpg-core/src/test/resources/c/typedef_in_header/header.h rename to cpg-language-cxx/src/test/resources/c/typedef_in_header/header.h diff --git a/cpg-core/src/test/resources/c/typedef_in_header/main.c b/cpg-language-cxx/src/test/resources/c/typedef_in_header/main.c similarity index 100% rename from cpg-core/src/test/resources/c/typedef_in_header/main.c rename to cpg-language-cxx/src/test/resources/c/typedef_in_header/main.c diff --git a/cpg-core/src/test/resources/call_me_crazy.h b/cpg-language-cxx/src/test/resources/call_me_crazy.h similarity index 100% rename from cpg-core/src/test/resources/call_me_crazy.h rename to cpg-language-cxx/src/test/resources/call_me_crazy.h diff --git a/cpg-core/src/test/resources/calls/calls.cpp b/cpg-language-cxx/src/test/resources/calls/calls.cpp similarity index 100% rename from cpg-core/src/test/resources/calls/calls.cpp rename to cpg-language-cxx/src/test/resources/calls/calls.cpp diff --git a/cpg-core/src/test/resources/calls/cxxprioresolution/defined.cpp b/cpg-language-cxx/src/test/resources/calls/cxxprioresolution/defined.cpp similarity index 100% rename from cpg-core/src/test/resources/calls/cxxprioresolution/defined.cpp rename to cpg-language-cxx/src/test/resources/calls/cxxprioresolution/defined.cpp diff --git a/cpg-core/src/test/resources/calls/cxxprioresolution/methodresolution/overloadedresolution.cpp b/cpg-language-cxx/src/test/resources/calls/cxxprioresolution/methodresolution/overloadedresolution.cpp similarity index 100% rename from cpg-core/src/test/resources/calls/cxxprioresolution/methodresolution/overloadedresolution.cpp rename to cpg-language-cxx/src/test/resources/calls/cxxprioresolution/methodresolution/overloadedresolution.cpp diff --git a/cpg-core/src/test/resources/calls/cxxprioresolution/methodresolution/overloadnoresolution.cpp b/cpg-language-cxx/src/test/resources/calls/cxxprioresolution/methodresolution/overloadnoresolution.cpp similarity index 100% rename from cpg-core/src/test/resources/calls/cxxprioresolution/methodresolution/overloadnoresolution.cpp rename to cpg-language-cxx/src/test/resources/calls/cxxprioresolution/methodresolution/overloadnoresolution.cpp diff --git a/cpg-core/src/test/resources/calls/cxxprioresolution/scopedResolutionWithDefaults.cpp b/cpg-language-cxx/src/test/resources/calls/cxxprioresolution/scopedResolutionWithDefaults.cpp similarity index 100% rename from cpg-core/src/test/resources/calls/cxxprioresolution/scopedResolutionWithDefaults.cpp rename to cpg-language-cxx/src/test/resources/calls/cxxprioresolution/scopedResolutionWithDefaults.cpp diff --git a/cpg-core/src/test/resources/calls/cxxprioresolution/undefined.cpp b/cpg-language-cxx/src/test/resources/calls/cxxprioresolution/undefined.cpp similarity index 100% rename from cpg-core/src/test/resources/calls/cxxprioresolution/undefined.cpp rename to cpg-language-cxx/src/test/resources/calls/cxxprioresolution/undefined.cpp diff --git a/cpg-core/src/test/resources/calls/defaultargs/defaultInDeclaration.cpp b/cpg-language-cxx/src/test/resources/calls/defaultargs/defaultInDeclaration.cpp similarity index 100% rename from cpg-core/src/test/resources/calls/defaultargs/defaultInDeclaration.cpp rename to cpg-language-cxx/src/test/resources/calls/defaultargs/defaultInDeclaration.cpp diff --git a/cpg-core/src/test/resources/calls/defaultargs/defaultInDefinition.cpp b/cpg-language-cxx/src/test/resources/calls/defaultargs/defaultInDefinition.cpp similarity index 100% rename from cpg-core/src/test/resources/calls/defaultargs/defaultInDefinition.cpp rename to cpg-language-cxx/src/test/resources/calls/defaultargs/defaultInDefinition.cpp diff --git a/cpg-core/src/test/resources/calls/defaultargs/defaultInMethod.cpp b/cpg-language-cxx/src/test/resources/calls/defaultargs/defaultInMethod.cpp similarity index 100% rename from cpg-core/src/test/resources/calls/defaultargs/defaultInMethod.cpp rename to cpg-language-cxx/src/test/resources/calls/defaultargs/defaultInMethod.cpp diff --git a/cpg-core/src/test/resources/calls/defaultargs/partialDefaults.cpp b/cpg-language-cxx/src/test/resources/calls/defaultargs/partialDefaults.cpp similarity index 100% rename from cpg-core/src/test/resources/calls/defaultargs/partialDefaults.cpp rename to cpg-language-cxx/src/test/resources/calls/defaultargs/partialDefaults.cpp diff --git a/cpg-core/src/test/resources/calls/ignore-return.cpp b/cpg-language-cxx/src/test/resources/calls/ignore-return.cpp similarity index 100% rename from cpg-core/src/test/resources/calls/ignore-return.cpp rename to cpg-language-cxx/src/test/resources/calls/ignore-return.cpp diff --git a/cpg-core/src/test/resources/calls/implicitcast/ambiguouscall.cpp b/cpg-language-cxx/src/test/resources/calls/implicitcast/ambiguouscall.cpp similarity index 100% rename from cpg-core/src/test/resources/calls/implicitcast/ambiguouscall.cpp rename to cpg-language-cxx/src/test/resources/calls/implicitcast/ambiguouscall.cpp diff --git a/cpg-core/src/test/resources/calls/implicitcast/implicitCastInMethod.cpp b/cpg-language-cxx/src/test/resources/calls/implicitcast/implicitCastInMethod.cpp similarity index 100% rename from cpg-core/src/test/resources/calls/implicitcast/implicitCastInMethod.cpp rename to cpg-language-cxx/src/test/resources/calls/implicitcast/implicitCastInMethod.cpp diff --git a/cpg-core/src/test/resources/calls/implicitcast/implicitcast.cpp b/cpg-language-cxx/src/test/resources/calls/implicitcast/implicitcast.cpp similarity index 100% rename from cpg-core/src/test/resources/calls/implicitcast/implicitcast.cpp rename to cpg-language-cxx/src/test/resources/calls/implicitcast/implicitcast.cpp diff --git a/cpg-core/src/test/resources/cfg.cpp b/cpg-language-cxx/src/test/resources/cfg.cpp similarity index 100% rename from cpg-core/src/test/resources/cfg.cpp rename to cpg-language-cxx/src/test/resources/cfg.cpp diff --git a/cpg-core/src/test/resources/cfg/break_continue.cpp b/cpg-language-cxx/src/test/resources/cfg/break_continue.cpp similarity index 100% rename from cpg-core/src/test/resources/cfg/break_continue.cpp rename to cpg-language-cxx/src/test/resources/cfg/break_continue.cpp diff --git a/cpg-core/src/test/resources/cfg/forloop.cpp b/cpg-language-cxx/src/test/resources/cfg/forloop.cpp similarity index 100% rename from cpg-core/src/test/resources/cfg/forloop.cpp rename to cpg-language-cxx/src/test/resources/cfg/forloop.cpp diff --git a/cpg-core/src/test/resources/cfg/goto.cpp b/cpg-language-cxx/src/test/resources/cfg/goto.cpp similarity index 100% rename from cpg-core/src/test/resources/cfg/goto.cpp rename to cpg-language-cxx/src/test/resources/cfg/goto.cpp diff --git a/cpg-core/src/test/resources/cfg/if.cpp b/cpg-language-cxx/src/test/resources/cfg/if.cpp similarity index 100% rename from cpg-core/src/test/resources/cfg/if.cpp rename to cpg-language-cxx/src/test/resources/cfg/if.cpp diff --git a/cpg-core/src/test/resources/cfg/ifextra.cpp b/cpg-language-cxx/src/test/resources/cfg/ifextra.cpp similarity index 100% rename from cpg-core/src/test/resources/cfg/ifextra.cpp rename to cpg-language-cxx/src/test/resources/cfg/ifextra.cpp diff --git a/cpg-core/src/test/resources/cfg/loops.cpp b/cpg-language-cxx/src/test/resources/cfg/loops.cpp similarity index 100% rename from cpg-core/src/test/resources/cfg/loops.cpp rename to cpg-language-cxx/src/test/resources/cfg/loops.cpp diff --git a/cpg-core/src/test/resources/cfg/loopscfg.cpp b/cpg-language-cxx/src/test/resources/cfg/loopscfg.cpp similarity index 100% rename from cpg-core/src/test/resources/cfg/loopscfg.cpp rename to cpg-language-cxx/src/test/resources/cfg/loopscfg.cpp diff --git a/cpg-core/src/test/resources/cfg/switch.cpp b/cpg-language-cxx/src/test/resources/cfg/switch.cpp similarity index 100% rename from cpg-core/src/test/resources/cfg/switch.cpp rename to cpg-language-cxx/src/test/resources/cfg/switch.cpp diff --git a/cpg-core/src/test/resources/cg.cpp b/cpg-language-cxx/src/test/resources/cg.cpp similarity index 100% rename from cpg-core/src/test/resources/cg.cpp rename to cpg-language-cxx/src/test/resources/cg.cpp diff --git a/cpg-core/src/test/resources/compiling/hierarchy/multistep/multi_inheritance.cpp b/cpg-language-cxx/src/test/resources/compiling/hierarchy/multistep/multi_inheritance.cpp similarity index 100% rename from cpg-core/src/test/resources/compiling/hierarchy/multistep/multi_inheritance.cpp rename to cpg-language-cxx/src/test/resources/compiling/hierarchy/multistep/multi_inheritance.cpp diff --git a/cpg-core/src/test/resources/compiling/hierarchy/multistep/simple_inheritance.cpp b/cpg-language-cxx/src/test/resources/compiling/hierarchy/multistep/simple_inheritance.cpp similarity index 100% rename from cpg-core/src/test/resources/compiling/hierarchy/multistep/simple_inheritance.cpp rename to cpg-language-cxx/src/test/resources/compiling/hierarchy/multistep/simple_inheritance.cpp diff --git a/cpg-core/src/test/resources/components/castexpr.cpp b/cpg-language-cxx/src/test/resources/components/castexpr.cpp similarity index 100% rename from cpg-core/src/test/resources/components/castexpr.cpp rename to cpg-language-cxx/src/test/resources/components/castexpr.cpp diff --git a/cpg-core/src/test/resources/components/designatedInitializer.cpp b/cpg-language-cxx/src/test/resources/components/designatedInitializer.cpp similarity index 100% rename from cpg-core/src/test/resources/components/designatedInitializer.cpp rename to cpg-language-cxx/src/test/resources/components/designatedInitializer.cpp diff --git a/cpg-core/src/test/resources/components/foreachstmt.cpp b/cpg-language-cxx/src/test/resources/components/foreachstmt.cpp similarity index 100% rename from cpg-core/src/test/resources/components/foreachstmt.cpp rename to cpg-language-cxx/src/test/resources/components/foreachstmt.cpp diff --git a/cpg-core/src/test/resources/components/trystmt.cpp b/cpg-language-cxx/src/test/resources/components/trystmt.cpp similarity index 100% rename from cpg-core/src/test/resources/components/trystmt.cpp rename to cpg-language-cxx/src/test/resources/components/trystmt.cpp diff --git a/cpg-core/src/test/resources/compoundstmt.cpp b/cpg-language-cxx/src/test/resources/compoundstmt.cpp similarity index 100% rename from cpg-core/src/test/resources/compoundstmt.cpp rename to cpg-language-cxx/src/test/resources/compoundstmt.cpp diff --git a/cpg-core/src/test/resources/constructors/constructors.cpp b/cpg-language-cxx/src/test/resources/constructors/constructors.cpp similarity index 100% rename from cpg-core/src/test/resources/constructors/constructors.cpp rename to cpg-language-cxx/src/test/resources/constructors/constructors.cpp diff --git a/cpg-core/src/test/resources/constructors/defaultarg/constructorDefault.cpp b/cpg-language-cxx/src/test/resources/constructors/defaultarg/constructorDefault.cpp similarity index 100% rename from cpg-core/src/test/resources/constructors/defaultarg/constructorDefault.cpp rename to cpg-language-cxx/src/test/resources/constructors/defaultarg/constructorDefault.cpp diff --git a/cpg-core/src/test/resources/constructors/implicitcastarg/constructorImplicit.cpp b/cpg-language-cxx/src/test/resources/constructors/implicitcastarg/constructorImplicit.cpp similarity index 100% rename from cpg-core/src/test/resources/constructors/implicitcastarg/constructorImplicit.cpp rename to cpg-language-cxx/src/test/resources/constructors/implicitcastarg/constructorImplicit.cpp diff --git a/cpg-core/src/test/resources/cpp-this-field.cpp b/cpg-language-cxx/src/test/resources/cpp-this-field.cpp similarity index 100% rename from cpg-core/src/test/resources/cpp-this-field.cpp rename to cpg-language-cxx/src/test/resources/cpp-this-field.cpp diff --git a/cpg-core/src/test/resources/cxx/arrays.cpp b/cpg-language-cxx/src/test/resources/cxx/arrays.cpp similarity index 100% rename from cpg-core/src/test/resources/cxx/arrays.cpp rename to cpg-language-cxx/src/test/resources/cxx/arrays.cpp diff --git a/cpg-core/src/test/resources/cxx/declstmt.cpp b/cpg-language-cxx/src/test/resources/cxx/declstmt.cpp similarity index 100% rename from cpg-core/src/test/resources/cxx/declstmt.cpp rename to cpg-language-cxx/src/test/resources/cxx/declstmt.cpp diff --git a/cpg-core/src/test/resources/cxx/funcptr_class_simple.cpp b/cpg-language-cxx/src/test/resources/cxx/funcptr_class_simple.cpp similarity index 100% rename from cpg-core/src/test/resources/cxx/funcptr_class_simple.cpp rename to cpg-language-cxx/src/test/resources/cxx/funcptr_class_simple.cpp diff --git a/cpg-core/src/test/resources/cxx/functiondecl.cpp b/cpg-language-cxx/src/test/resources/cxx/functiondecl.cpp similarity index 100% rename from cpg-core/src/test/resources/cxx/functiondecl.cpp rename to cpg-language-cxx/src/test/resources/cxx/functiondecl.cpp diff --git a/cpg-core/src/test/resources/cxx/lambdas.cpp b/cpg-language-cxx/src/test/resources/cxx/lambdas.cpp similarity index 100% rename from cpg-core/src/test/resources/cxx/lambdas.cpp rename to cpg-language-cxx/src/test/resources/cxx/lambdas.cpp diff --git a/cpg-core/src/test/resources/cxx/literals.cpp b/cpg-language-cxx/src/test/resources/cxx/literals.cpp similarity index 100% rename from cpg-core/src/test/resources/cxx/literals.cpp rename to cpg-language-cxx/src/test/resources/cxx/literals.cpp diff --git a/cpg-core/src/test/resources/cxx/namespaced_function.cpp b/cpg-language-cxx/src/test/resources/cxx/namespaced_function.cpp similarity index 100% rename from cpg-core/src/test/resources/cxx/namespaced_function.cpp rename to cpg-language-cxx/src/test/resources/cxx/namespaced_function.cpp diff --git a/cpg-core/src/test/resources/cxx/objcreation.cpp b/cpg-language-cxx/src/test/resources/cxx/objcreation.cpp similarity index 100% rename from cpg-core/src/test/resources/cxx/objcreation.cpp rename to cpg-language-cxx/src/test/resources/cxx/objcreation.cpp diff --git a/cpg-core/src/test/resources/cxx/parenthesis.cpp b/cpg-language-cxx/src/test/resources/cxx/parenthesis.cpp similarity index 100% rename from cpg-core/src/test/resources/cxx/parenthesis.cpp rename to cpg-language-cxx/src/test/resources/cxx/parenthesis.cpp diff --git a/cpg-core/src/test/resources/cxx/recordstmt.cpp b/cpg-language-cxx/src/test/resources/cxx/recordstmt.cpp similarity index 100% rename from cpg-core/src/test/resources/cxx/recordstmt.cpp rename to cpg-language-cxx/src/test/resources/cxx/recordstmt.cpp diff --git a/cpg-core/src/test/resources/cxxCompilationDatabase/compile_commands_arch.json b/cpg-language-cxx/src/test/resources/cxxCompilationDatabase/compile_commands_arch.json similarity index 100% rename from cpg-core/src/test/resources/cxxCompilationDatabase/compile_commands_arch.json rename to cpg-language-cxx/src/test/resources/cxxCompilationDatabase/compile_commands_arch.json diff --git a/cpg-core/src/test/resources/cxxCompilationDatabase/compile_commands_arguments.json b/cpg-language-cxx/src/test/resources/cxxCompilationDatabase/compile_commands_arguments.json similarity index 100% rename from cpg-core/src/test/resources/cxxCompilationDatabase/compile_commands_arguments.json rename to cpg-language-cxx/src/test/resources/cxxCompilationDatabase/compile_commands_arguments.json diff --git a/cpg-core/src/test/resources/cxxCompilationDatabase/compile_commands_commands.json b/cpg-language-cxx/src/test/resources/cxxCompilationDatabase/compile_commands_commands.json similarity index 100% rename from cpg-core/src/test/resources/cxxCompilationDatabase/compile_commands_commands.json rename to cpg-language-cxx/src/test/resources/cxxCompilationDatabase/compile_commands_commands.json diff --git a/cpg-core/src/test/resources/cxxCompilationDatabase/compile_commands_multi_tus.json b/cpg-language-cxx/src/test/resources/cxxCompilationDatabase/compile_commands_multi_tus.json similarity index 100% rename from cpg-core/src/test/resources/cxxCompilationDatabase/compile_commands_multi_tus.json rename to cpg-language-cxx/src/test/resources/cxxCompilationDatabase/compile_commands_multi_tus.json diff --git a/cpg-core/src/test/resources/cxxCompilationDatabase/compile_commands_simple.json b/cpg-language-cxx/src/test/resources/cxxCompilationDatabase/compile_commands_simple.json similarity index 100% rename from cpg-core/src/test/resources/cxxCompilationDatabase/compile_commands_simple.json rename to cpg-language-cxx/src/test/resources/cxxCompilationDatabase/compile_commands_simple.json diff --git a/cpg-core/src/test/resources/cxxCompilationDatabase/includes_1/config.h b/cpg-language-cxx/src/test/resources/cxxCompilationDatabase/includes_1/config.h similarity index 100% rename from cpg-core/src/test/resources/cxxCompilationDatabase/includes_1/config.h rename to cpg-language-cxx/src/test/resources/cxxCompilationDatabase/includes_1/config.h diff --git a/cpg-core/src/test/resources/cxxCompilationDatabase/includes_1/header_1.h b/cpg-language-cxx/src/test/resources/cxxCompilationDatabase/includes_1/header_1.h similarity index 100% rename from cpg-core/src/test/resources/cxxCompilationDatabase/includes_1/header_1.h rename to cpg-language-cxx/src/test/resources/cxxCompilationDatabase/includes_1/header_1.h diff --git a/cpg-core/src/test/resources/cxxCompilationDatabase/includes_2/config.h b/cpg-language-cxx/src/test/resources/cxxCompilationDatabase/includes_2/config.h similarity index 100% rename from cpg-core/src/test/resources/cxxCompilationDatabase/includes_2/config.h rename to cpg-language-cxx/src/test/resources/cxxCompilationDatabase/includes_2/config.h diff --git a/cpg-core/src/test/resources/cxxCompilationDatabase/includes_2/header_2.h b/cpg-language-cxx/src/test/resources/cxxCompilationDatabase/includes_2/header_2.h similarity index 100% rename from cpg-core/src/test/resources/cxxCompilationDatabase/includes_2/header_2.h rename to cpg-language-cxx/src/test/resources/cxxCompilationDatabase/includes_2/header_2.h diff --git a/cpg-core/src/test/resources/cxxCompilationDatabase/main.c b/cpg-language-cxx/src/test/resources/cxxCompilationDatabase/main.c similarity index 100% rename from cpg-core/src/test/resources/cxxCompilationDatabase/main.c rename to cpg-language-cxx/src/test/resources/cxxCompilationDatabase/main.c diff --git a/cpg-core/src/test/resources/cxxCompilationDatabase/main_arm64.c b/cpg-language-cxx/src/test/resources/cxxCompilationDatabase/main_arm64.c similarity index 100% rename from cpg-core/src/test/resources/cxxCompilationDatabase/main_arm64.c rename to cpg-language-cxx/src/test/resources/cxxCompilationDatabase/main_arm64.c diff --git a/cpg-core/src/test/resources/cxxCompilationDatabase/main_simple.c b/cpg-language-cxx/src/test/resources/cxxCompilationDatabase/main_simple.c similarity index 100% rename from cpg-core/src/test/resources/cxxCompilationDatabase/main_simple.c rename to cpg-language-cxx/src/test/resources/cxxCompilationDatabase/main_simple.c diff --git a/cpg-core/src/test/resources/cxxCompilationDatabase/main_tu_1.c b/cpg-language-cxx/src/test/resources/cxxCompilationDatabase/main_tu_1.c similarity index 100% rename from cpg-core/src/test/resources/cxxCompilationDatabase/main_tu_1.c rename to cpg-language-cxx/src/test/resources/cxxCompilationDatabase/main_tu_1.c diff --git a/cpg-core/src/test/resources/cxxCompilationDatabase/main_tu_2.c b/cpg-language-cxx/src/test/resources/cxxCompilationDatabase/main_tu_2.c similarity index 100% rename from cpg-core/src/test/resources/cxxCompilationDatabase/main_tu_2.c rename to cpg-language-cxx/src/test/resources/cxxCompilationDatabase/main_tu_2.c diff --git a/cpg-core/src/test/resources/cxxCompilationDatabase/sys_includes/sys_header.h b/cpg-language-cxx/src/test/resources/cxxCompilationDatabase/sys_includes/sys_header.h similarity index 100% rename from cpg-core/src/test/resources/cxxCompilationDatabase/sys_includes/sys_header.h rename to cpg-language-cxx/src/test/resources/cxxCompilationDatabase/sys_includes/sys_header.h diff --git a/cpg-core/src/test/resources/fix-455/main.cpp b/cpg-language-cxx/src/test/resources/fix-455/main.cpp similarity index 100% rename from cpg-core/src/test/resources/fix-455/main.cpp rename to cpg-language-cxx/src/test/resources/fix-455/main.cpp diff --git a/cpg-core/src/test/resources/foo.cpp b/cpg-language-cxx/src/test/resources/foo.cpp similarity index 100% rename from cpg-core/src/test/resources/foo.cpp rename to cpg-language-cxx/src/test/resources/foo.cpp diff --git a/cpg-core/src/test/resources/foo2.cpp b/cpg-language-cxx/src/test/resources/foo2.cpp similarity index 100% rename from cpg-core/src/test/resources/foo2.cpp rename to cpg-language-cxx/src/test/resources/foo2.cpp diff --git a/cpg-core/src/test/resources/functionPointers/func_ptr.c b/cpg-language-cxx/src/test/resources/functionPointers/func_ptr.c similarity index 100% rename from cpg-core/src/test/resources/functionPointers/func_ptr.c rename to cpg-language-cxx/src/test/resources/functionPointers/func_ptr.c diff --git a/cpg-core/src/test/resources/functionPointers/func_ptr.cpp b/cpg-language-cxx/src/test/resources/functionPointers/func_ptr.cpp similarity index 100% rename from cpg-core/src/test/resources/functionPointers/func_ptr.cpp rename to cpg-language-cxx/src/test/resources/functionPointers/func_ptr.cpp diff --git a/cpg-core/src/test/resources/function_ptr_or_type_cast.c b/cpg-language-cxx/src/test/resources/function_ptr_or_type_cast.c similarity index 100% rename from cpg-core/src/test/resources/function_ptr_or_type_cast.c rename to cpg-language-cxx/src/test/resources/function_ptr_or_type_cast.c diff --git a/cpg-core/src/test/resources/if.cpp b/cpg-language-cxx/src/test/resources/if.cpp similarity index 100% rename from cpg-core/src/test/resources/if.cpp rename to cpg-language-cxx/src/test/resources/if.cpp diff --git a/cpg-core/src/test/resources/include.cpp b/cpg-language-cxx/src/test/resources/include.cpp similarity index 100% rename from cpg-core/src/test/resources/include.cpp rename to cpg-language-cxx/src/test/resources/include.cpp diff --git a/cpg-core/src/test/resources/include.h b/cpg-language-cxx/src/test/resources/include.h similarity index 100% rename from cpg-core/src/test/resources/include.h rename to cpg-language-cxx/src/test/resources/include.h diff --git a/cpg-core/src/test/resources/initlistexpression.cpp b/cpg-language-cxx/src/test/resources/initlistexpression.cpp similarity index 100% rename from cpg-core/src/test/resources/initlistexpression.cpp rename to cpg-language-cxx/src/test/resources/initlistexpression.cpp diff --git a/cpg-core/src/test/resources/integer_literals.cpp b/cpg-language-cxx/src/test/resources/integer_literals.cpp similarity index 100% rename from cpg-core/src/test/resources/integer_literals.cpp rename to cpg-language-cxx/src/test/resources/integer_literals.cpp diff --git a/cpg-core/src/test/resources/largenegativenumber.cpp b/cpg-language-cxx/src/test/resources/largenegativenumber.cpp similarity index 100% rename from cpg-core/src/test/resources/largenegativenumber.cpp rename to cpg-language-cxx/src/test/resources/largenegativenumber.cpp diff --git a/cpg-language-cxx/src/test/resources/log4j2.xml b/cpg-language-cxx/src/test/resources/log4j2.xml new file mode 100644 index 0000000000..ac6e67063f --- /dev/null +++ b/cpg-language-cxx/src/test/resources/log4j2.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/cpg-core/src/test/resources/method_or_function_call.cpp b/cpg-language-cxx/src/test/resources/method_or_function_call.cpp similarity index 100% rename from cpg-core/src/test/resources/method_or_function_call.cpp rename to cpg-language-cxx/src/test/resources/method_or_function_call.cpp diff --git a/cpg-core/src/test/resources/namespaces.cpp b/cpg-language-cxx/src/test/resources/namespaces.cpp similarity index 100% rename from cpg-core/src/test/resources/namespaces.cpp rename to cpg-language-cxx/src/test/resources/namespaces.cpp diff --git a/cpg-core/src/test/resources/postfixexpression.cpp b/cpg-language-cxx/src/test/resources/postfixexpression.cpp similarity index 100% rename from cpg-core/src/test/resources/postfixexpression.cpp rename to cpg-language-cxx/src/test/resources/postfixexpression.cpp diff --git a/cpg-core/src/test/resources/shiftexpression.cpp b/cpg-language-cxx/src/test/resources/shiftexpression.cpp similarity index 100% rename from cpg-core/src/test/resources/shiftexpression.cpp rename to cpg-language-cxx/src/test/resources/shiftexpression.cpp diff --git a/cpg-core/src/test/resources/symbols.cpp b/cpg-language-cxx/src/test/resources/symbols.cpp similarity index 100% rename from cpg-core/src/test/resources/symbols.cpp rename to cpg-language-cxx/src/test/resources/symbols.cpp diff --git a/cpg-core/src/test/resources/templates/classtemplates/array.cpp b/cpg-language-cxx/src/test/resources/templates/classtemplates/array.cpp similarity index 100% rename from cpg-core/src/test/resources/templates/classtemplates/array.cpp rename to cpg-language-cxx/src/test/resources/templates/classtemplates/array.cpp diff --git a/cpg-core/src/test/resources/templates/classtemplates/array2.cpp b/cpg-language-cxx/src/test/resources/templates/classtemplates/array2.cpp similarity index 100% rename from cpg-core/src/test/resources/templates/classtemplates/array2.cpp rename to cpg-language-cxx/src/test/resources/templates/classtemplates/array2.cpp diff --git a/cpg-core/src/test/resources/templates/classtemplates/pair.cpp b/cpg-language-cxx/src/test/resources/templates/classtemplates/pair.cpp similarity index 100% rename from cpg-core/src/test/resources/templates/classtemplates/pair.cpp rename to cpg-language-cxx/src/test/resources/templates/classtemplates/pair.cpp diff --git a/cpg-core/src/test/resources/templates/classtemplates/pair2.cpp b/cpg-language-cxx/src/test/resources/templates/classtemplates/pair2.cpp similarity index 100% rename from cpg-core/src/test/resources/templates/classtemplates/pair2.cpp rename to cpg-language-cxx/src/test/resources/templates/classtemplates/pair2.cpp diff --git a/cpg-core/src/test/resources/templates/classtemplates/pair3-1.cpp b/cpg-language-cxx/src/test/resources/templates/classtemplates/pair3-1.cpp similarity index 100% rename from cpg-core/src/test/resources/templates/classtemplates/pair3-1.cpp rename to cpg-language-cxx/src/test/resources/templates/classtemplates/pair3-1.cpp diff --git a/cpg-core/src/test/resources/templates/classtemplates/pair3-2.cpp b/cpg-language-cxx/src/test/resources/templates/classtemplates/pair3-2.cpp similarity index 100% rename from cpg-core/src/test/resources/templates/classtemplates/pair3-2.cpp rename to cpg-language-cxx/src/test/resources/templates/classtemplates/pair3-2.cpp diff --git a/cpg-core/src/test/resources/templates/classtemplates/pair3.cpp b/cpg-language-cxx/src/test/resources/templates/classtemplates/pair3.cpp similarity index 100% rename from cpg-core/src/test/resources/templates/classtemplates/pair3.cpp rename to cpg-language-cxx/src/test/resources/templates/classtemplates/pair3.cpp diff --git a/cpg-core/src/test/resources/templates/functiontemplates/functionTemplate.cpp b/cpg-language-cxx/src/test/resources/templates/functiontemplates/functionTemplate.cpp similarity index 100% rename from cpg-core/src/test/resources/templates/functiontemplates/functionTemplate.cpp rename to cpg-language-cxx/src/test/resources/templates/functiontemplates/functionTemplate.cpp diff --git a/cpg-core/src/test/resources/templates/functiontemplates/functionTemplateInvocation1.cpp b/cpg-language-cxx/src/test/resources/templates/functiontemplates/functionTemplateInvocation1.cpp similarity index 100% rename from cpg-core/src/test/resources/templates/functiontemplates/functionTemplateInvocation1.cpp rename to cpg-language-cxx/src/test/resources/templates/functiontemplates/functionTemplateInvocation1.cpp diff --git a/cpg-core/src/test/resources/templates/functiontemplates/functionTemplateInvocation2.cpp b/cpg-language-cxx/src/test/resources/templates/functiontemplates/functionTemplateInvocation2.cpp similarity index 100% rename from cpg-core/src/test/resources/templates/functiontemplates/functionTemplateInvocation2.cpp rename to cpg-language-cxx/src/test/resources/templates/functiontemplates/functionTemplateInvocation2.cpp diff --git a/cpg-core/src/test/resources/templates/functiontemplates/functionTemplateInvocation3.cpp b/cpg-language-cxx/src/test/resources/templates/functiontemplates/functionTemplateInvocation3.cpp similarity index 100% rename from cpg-core/src/test/resources/templates/functiontemplates/functionTemplateInvocation3.cpp rename to cpg-language-cxx/src/test/resources/templates/functiontemplates/functionTemplateInvocation3.cpp diff --git a/cpg-core/src/test/resources/templates/functiontemplates/functionTemplateInvocation4.cpp b/cpg-language-cxx/src/test/resources/templates/functiontemplates/functionTemplateInvocation4.cpp similarity index 100% rename from cpg-core/src/test/resources/templates/functiontemplates/functionTemplateInvocation4.cpp rename to cpg-language-cxx/src/test/resources/templates/functiontemplates/functionTemplateInvocation4.cpp diff --git a/cpg-core/src/test/resources/templates/functiontemplates/functionTemplateInvocation5.cpp b/cpg-language-cxx/src/test/resources/templates/functiontemplates/functionTemplateInvocation5.cpp similarity index 100% rename from cpg-core/src/test/resources/templates/functiontemplates/functionTemplateInvocation5.cpp rename to cpg-language-cxx/src/test/resources/templates/functiontemplates/functionTemplateInvocation5.cpp diff --git a/cpg-core/src/test/resources/templates/functiontemplates/functionTemplateInvocation6.cpp b/cpg-language-cxx/src/test/resources/templates/functiontemplates/functionTemplateInvocation6.cpp similarity index 100% rename from cpg-core/src/test/resources/templates/functiontemplates/functionTemplateInvocation6.cpp rename to cpg-language-cxx/src/test/resources/templates/functiontemplates/functionTemplateInvocation6.cpp diff --git a/cpg-core/src/test/resources/templates/functiontemplates/functionTemplateInvocation7.cpp b/cpg-language-cxx/src/test/resources/templates/functiontemplates/functionTemplateInvocation7.cpp similarity index 100% rename from cpg-core/src/test/resources/templates/functiontemplates/functionTemplateInvocation7.cpp rename to cpg-language-cxx/src/test/resources/templates/functiontemplates/functionTemplateInvocation7.cpp diff --git a/cpg-core/src/test/resources/templates/functiontemplates/functionTemplateInvocation8.cpp b/cpg-language-cxx/src/test/resources/templates/functiontemplates/functionTemplateInvocation8.cpp similarity index 100% rename from cpg-core/src/test/resources/templates/functiontemplates/functionTemplateInvocation8.cpp rename to cpg-language-cxx/src/test/resources/templates/functiontemplates/functionTemplateInvocation8.cpp diff --git a/cpg-core/src/test/resources/templates/functiontemplates/functionTemplateMethod.cpp b/cpg-language-cxx/src/test/resources/templates/functiontemplates/functionTemplateMethod.cpp similarity index 100% rename from cpg-core/src/test/resources/templates/functiontemplates/functionTemplateMethod.cpp rename to cpg-language-cxx/src/test/resources/templates/functiontemplates/functionTemplateMethod.cpp diff --git a/cpg-core/src/test/resources/typedefs/typedefs.cpp b/cpg-language-cxx/src/test/resources/typedefs/typedefs.cpp similarity index 100% rename from cpg-core/src/test/resources/typedefs/typedefs.cpp rename to cpg-language-cxx/src/test/resources/typedefs/typedefs.cpp diff --git a/cpg-core/src/test/resources/typeidexpr.cpp b/cpg-language-cxx/src/test/resources/typeidexpr.cpp similarity index 100% rename from cpg-core/src/test/resources/typeidexpr.cpp rename to cpg-language-cxx/src/test/resources/typeidexpr.cpp diff --git a/cpg-core/src/test/resources/types.cpp b/cpg-language-cxx/src/test/resources/types.cpp similarity index 100% rename from cpg-core/src/test/resources/types.cpp rename to cpg-language-cxx/src/test/resources/types.cpp diff --git a/cpg-core/src/test/resources/types/fptr_type.cpp b/cpg-language-cxx/src/test/resources/types/fptr_type.cpp similarity index 100% rename from cpg-core/src/test/resources/types/fptr_type.cpp rename to cpg-language-cxx/src/test/resources/types/fptr_type.cpp diff --git a/cpg-core/src/test/resources/types/type.cpp b/cpg-language-cxx/src/test/resources/types/type.cpp similarity index 100% rename from cpg-core/src/test/resources/types/type.cpp rename to cpg-language-cxx/src/test/resources/types/type.cpp diff --git a/cpg-core/src/test/resources/unaryoperator.cpp b/cpg-language-cxx/src/test/resources/unaryoperator.cpp similarity index 100% rename from cpg-core/src/test/resources/unaryoperator.cpp rename to cpg-language-cxx/src/test/resources/unaryoperator.cpp diff --git a/cpg-core/src/test/resources/unity/a.cpp b/cpg-language-cxx/src/test/resources/unity/a.cpp similarity index 100% rename from cpg-core/src/test/resources/unity/a.cpp rename to cpg-language-cxx/src/test/resources/unity/a.cpp diff --git a/cpg-core/src/test/resources/unity/b.cpp b/cpg-language-cxx/src/test/resources/unity/b.cpp similarity index 100% rename from cpg-core/src/test/resources/unity/b.cpp rename to cpg-language-cxx/src/test/resources/unity/b.cpp diff --git a/cpg-core/src/test/resources/unity/common.h b/cpg-language-cxx/src/test/resources/unity/common.h similarity index 100% rename from cpg-core/src/test/resources/unity/common.h rename to cpg-language-cxx/src/test/resources/unity/common.h diff --git a/cpg-core/src/test/resources/variables/local_variables.cpp b/cpg-language-cxx/src/test/resources/variables/local_variables.cpp similarity index 100% rename from cpg-core/src/test/resources/variables/local_variables.cpp rename to cpg-language-cxx/src/test/resources/variables/local_variables.cpp diff --git a/cpg-core/src/test/resources/variables_extended/cpp/external_class.cpp b/cpg-language-cxx/src/test/resources/variables_extended/cpp/external_class.cpp similarity index 100% rename from cpg-core/src/test/resources/variables_extended/cpp/external_class.cpp rename to cpg-language-cxx/src/test/resources/variables_extended/cpp/external_class.cpp diff --git a/cpg-core/src/test/resources/variables_extended/cpp/external_class.h b/cpg-language-cxx/src/test/resources/variables_extended/cpp/external_class.h similarity index 100% rename from cpg-core/src/test/resources/variables_extended/cpp/external_class.h rename to cpg-language-cxx/src/test/resources/variables_extended/cpp/external_class.h diff --git a/cpg-core/src/test/resources/variables_extended/cpp/local_variables.cpp b/cpg-language-cxx/src/test/resources/variables_extended/cpp/local_variables.cpp similarity index 100% rename from cpg-core/src/test/resources/variables_extended/cpp/local_variables.cpp rename to cpg-language-cxx/src/test/resources/variables_extended/cpp/local_variables.cpp diff --git a/cpg-core/src/test/resources/variables_extended/cpp/scope_variables.cpp b/cpg-language-cxx/src/test/resources/variables_extended/cpp/scope_variables.cpp similarity index 100% rename from cpg-core/src/test/resources/variables_extended/cpp/scope_variables.cpp rename to cpg-language-cxx/src/test/resources/variables_extended/cpp/scope_variables.cpp diff --git a/cpg-neo4j/src/main/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/Application.kt b/cpg-neo4j/src/main/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/Application.kt index a10ff48df6..cb615852e9 100644 --- a/cpg-neo4j/src/main/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/Application.kt +++ b/cpg-neo4j/src/main/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/Application.kt @@ -230,7 +230,6 @@ class Application : Callable { EvaluationOrderGraphPass::class, TypeResolver::class, ControlFlowSensitiveDFGPass::class, - FunctionPointerCallResolver::class, FilenameMapper::class ) private var passClassMap = passClassList.associateBy { it.simpleName } diff --git a/settings.gradle.kts b/settings.gradle.kts index 275ab02053..40507dbf43 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -13,6 +13,10 @@ val enableJavaFrontend: Boolean by extra { val enableJavaFrontend: String? by settings enableJavaFrontend.toBoolean() } +val enableCXXFrontend: Boolean by extra { + val enableCXXFrontend: String? by settings + enableCXXFrontend.toBoolean() +} val enableGoFrontend: Boolean by extra { val enableGoFrontend: String? by settings enableGoFrontend.toBoolean() @@ -31,6 +35,7 @@ val enableTypeScriptFrontend: Boolean by extra { } if (enableJavaFrontend) include(":cpg-language-java") +if (enableCXXFrontend) include(":cpg-language-cxx") if (enableGoFrontend) include(":cpg-language-go") if (enableLLVMFrontend) include(":cpg-language-llvm") if (enablePythonFrontend) include(":cpg-language-python") From 933cf99a75f1083fa3fec68262b8fed1e8c1a0b9 Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Fri, 2 Jun 2023 10:12:50 +0200 Subject: [PATCH 055/143] Only register extra passes if `defaultPasses()` was called (#1188) With the exctraction of the C/C++ frontend and the move from the function call resolver to an an extra pass, we encountered a problem, in which we always tried to execute the extra pass even though default passes were disabled. This PR now checks, whether default passes need to be run and only register extra passes from language frontend if they are enabled. Also fixes a minor bug in the CI pipeline --- .github/workflows/build.yml | 2 +- .../aisec/cpg/TranslationConfiguration.kt | 14 ++++++++++++-- .../cpg/passes/order/RegisterExtraPass.kt | 8 +++++++- .../aisec/cpg/frontends/cxx/CXXIncludeTest.kt | 18 ++++++------------ 4 files changed, 26 insertions(+), 16 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7f15373ac2..64f094e4d2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -134,7 +134,7 @@ jobs: if: startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, 'beta') && !contains(github.ref, 'alpha') run: | export ORG_GRADLE_PROJECT_signingKey=`echo ${{ secrets.GPG_PRIVATE_KEY }} | base64 -d` - ./gradlew --no-daemon -Dorg.gradle.internal.publish.checksums.insecure=true --parallel -Pversion=$VERSION -PenableJavaFrontend=true -PenableGoFrontend=true -PenablePythonFrontend=true -PenableLLVMFrontend=true -PenableTypeScriptFrontend=true publish dokkaHtmlMultiModule + ./gradlew --no-daemon -Dorg.gradle.internal.publish.checksums.insecure=true --parallel -Pversion=$VERSION -PenableJavaFrontend=true -PenableCXXFrontend=true -PenableGoFrontend=true -PenablePythonFrontend=true -PenableLLVMFrontend=true -PenableTypeScriptFrontend=true publish dokkaHtmlMultiModule env: VERSION: ${{ env.version }} ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.GPG_PASSWORD }} diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationConfiguration.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationConfiguration.kt index 483928aaa3..cb3d27218e 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationConfiguration.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationConfiguration.kt @@ -237,6 +237,7 @@ private constructor( private var compilationDatabase: CompilationDatabase? = null private var matchCommentsToNodes = false private var addIncludesToGraph = true + private var useDefaultPasses = false fun symbols(symbols: Map): Builder { this.symbols = symbols @@ -456,7 +457,6 @@ private constructor( * - [VariableUsageResolver] * - [CallResolver] * - [DFGPass] - * - [FunctionPointerCallResolver] * - [EvaluationOrderGraphPass] * - [TypeResolver] * - [ControlFlowSensitiveDFGPass] @@ -474,12 +474,22 @@ private constructor( registerPass() registerPass() registerPass() + useDefaultPasses = true return this } - /** Register extra passes declared by a frontend with [RegisterExtraPass] */ + /** + * Register extra passes declared by a frontend with [RegisterExtraPass], but only if + * [useDefaultPasses] is true (which is set to true by invoking [defaultPasses]). + */ @Throws(ConfigurationException::class) private fun registerExtraFrontendPasses() { + // We do not want to register any extra passes from the frontends if we are not running + // the default passes + if (!useDefaultPasses) { + return + } + for (frontend in languages.map(Language::frontend)) { val extraPasses = frontend.findAnnotations() if (extraPasses.isNotEmpty()) { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/order/RegisterExtraPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/order/RegisterExtraPass.kt index 7d2824504f..4191d0e38d 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/order/RegisterExtraPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/order/RegisterExtraPass.kt @@ -25,10 +25,16 @@ */ package de.fraunhofer.aisec.cpg.passes.order +import de.fraunhofer.aisec.cpg.TranslationConfiguration import de.fraunhofer.aisec.cpg.passes.Pass import kotlin.reflect.KClass -/** Register a new pass required by a fronted. */ +/** + * Register a new default pass required by a frontend. Passes annotated this way are collected by + * [TranslationConfiguration.Builder.registerExtraFrontendPasses] and automatically registered in + * [TranslationConfiguration.Builder.build], but only if + * [TranslationConfiguration.Builder.defaultPasses] was called. + */ @Retention(AnnotationRetention.RUNTIME) @Target(AnnotationTarget.CLASS) @Repeatable diff --git a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXIncludeTest.kt b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXIncludeTest.kt index 0a4226ac40..11b649f78c 100644 --- a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXIncludeTest.kt +++ b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXIncludeTest.kt @@ -110,8 +110,7 @@ internal class CXXIncludeTest : BaseTest() { .topLevel(file.parentFile) .loadIncludes(true) .debugParser(true) - .defaultPasses() - .defaultLanguages() + .registerLanguage() .includeBlocklist(File("src/test/resources/include.h").absolutePath) .failOnError(true) ) @@ -138,8 +137,7 @@ internal class CXXIncludeTest : BaseTest() { .topLevel(file.parentFile) .loadIncludes(true) .debugParser(true) - .defaultPasses() - .defaultLanguages() + .registerLanguage() .includeBlocklist("include.h") .failOnError(true) ) @@ -166,8 +164,7 @@ internal class CXXIncludeTest : BaseTest() { .topLevel(file.parentFile) .loadIncludes(true) .debugParser(true) - .defaultPasses() - .defaultLanguages() + .registerLanguage() .includeWhitelist(File("src/test/resources/include.h").absolutePath) .failOnError(true) ) @@ -194,8 +191,7 @@ internal class CXXIncludeTest : BaseTest() { .topLevel(file.parentFile) .loadIncludes(true) .debugParser(true) - .defaultPasses() - .defaultLanguages() + .registerLanguage() .includeWhitelist("include.h") .failOnError(true) ) @@ -222,8 +218,7 @@ internal class CXXIncludeTest : BaseTest() { .topLevel(file.parentFile) .loadIncludes(true) .debugParser(true) - .defaultPasses() - .defaultLanguages() + .registerLanguage() .includeBlocklist("include.h") // blacklist entries take priority .includeWhitelist("include.h") .includeWhitelist("another-include.h") @@ -254,8 +249,7 @@ internal class CXXIncludeTest : BaseTest() { .topLevel(file.parentFile) .loadIncludes(false) .debugParser(true) - .defaultPasses() - .defaultLanguages() + .registerLanguage() .failOnError(true) ) assertNotNull(tus) From 8375eb24f7ee3c4f2ae869a98d73a5b7b8e1dbdd Mon Sep 17 00:00:00 2001 From: Konrad Weiss Date: Fri, 2 Jun 2023 11:21:53 +0200 Subject: [PATCH 056/143] Update gradle.properties.example (#1190) Adding CXX Frontend to the example of activated languages --- gradle.properties.example | 1 + 1 file changed, 1 insertion(+) diff --git a/gradle.properties.example b/gradle.properties.example index f621765a35..b9daa60f60 100644 --- a/gradle.properties.example +++ b/gradle.properties.example @@ -1,6 +1,7 @@ org.gradle.jvmargs=-Xmx4096m -XX:MaxMetaspaceSize=1024m -Dkotlin.daemon.jvm.options=-Xmx4g enableJavaFrontend=true +enableCXXFrontend=true enableGoFrontend=true enablePythonFrontend=true enableLLVMFrontend=true From ab3d02da58996454e10bcb2403e8ae1bdd068c1b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 2 Jun 2023 14:26:21 +0200 Subject: [PATCH 057/143] Update sonarqube to v4.2.0.3129 (#1184) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index fce42412b8..c2526027d9 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,7 +2,7 @@ kotlin = "1.8.0" neo4j = "4.0.2" log4j = "2.20.0" -sonarqube = "4.0.0.2929" +sonarqube = "4.2.0.3129" spotless = "6.19.0" [libraries] From ac2a497cb2216198649b654e9bb4ce4b96a3ab89 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 2 Jun 2023 12:46:36 +0000 Subject: [PATCH 058/143] Update dependency typescript to v5.1.3 (#1189) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- cpg-language-typescript/src/main/nodejs/package.json | 2 +- cpg-language-typescript/src/main/nodejs/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cpg-language-typescript/src/main/nodejs/package.json b/cpg-language-typescript/src/main/nodejs/package.json index 339f988c4e..cd910ddf82 100644 --- a/cpg-language-typescript/src/main/nodejs/package.json +++ b/cpg-language-typescript/src/main/nodejs/package.json @@ -7,7 +7,7 @@ }, "dependencies": { "@types/node": "18.16.5", - "typescript": "5.0.2" + "typescript": "5.1.3" }, "license": "Apache-2.0", "devDependencies": { diff --git a/cpg-language-typescript/src/main/nodejs/yarn.lock b/cpg-language-typescript/src/main/nodejs/yarn.lock index b73ddc457d..291e300f0a 100644 --- a/cpg-language-typescript/src/main/nodejs/yarn.lock +++ b/cpg-language-typescript/src/main/nodejs/yarn.lock @@ -720,10 +720,10 @@ terser@^5.16.5: commander "^2.20.0" source-map-support "~0.5.20" -typescript@5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.0.2.tgz#891e1a90c5189d8506af64b9ef929fca99ba1ee5" - integrity sha512-wVORMBGO/FAs/++blGNeAVdbNKtIh1rbBL2EyQ1+J9lClJ93KiiKe8PmFIVdXhHcyv44SL9oglmfeSsndo0jRw== +typescript@5.1.3: + version "5.1.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.1.3.tgz#8d84219244a6b40b6fb2b33cc1c062f715b9e826" + integrity sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw== uri-js@^4.2.2: version "4.4.1" From 070f029ff1bd75cc63d7f07ac5041b0d9d6abe2e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 6 Jun 2023 09:07:48 +0200 Subject: [PATCH 059/143] Update dependency org.bytedeco:llvm-platform to v16 (#1192) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c2526027d9..0fe3ddeea7 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -32,7 +32,7 @@ jetbrains-annotations = { module = "org.jetbrains:annotations", version = "24.0. picocli = { module = "info.picocli:picocli", version = "4.7.0"} picocli-codegen = { module = "info.picocli:picocli-codegen", version = "4.7.0"} jep = { module = "black.ninia:jep", version = "4.1.1" } # build.yml uses grep to extract the jep verison number for CI/CD purposes -llvm = { module = "org.bytedeco:llvm-platform", version = "15.0.3-1.5.8"} +llvm = { module = "org.bytedeco:llvm-platform", version = "16.0.4-1.5.9"} # test junit-params = { module = "org.junit.jupiter:junit-jupiter-params", version = "5.9.1"} From 9d78f4917fb38e85d26499a4649617560cc1eb57 Mon Sep 17 00:00:00 2001 From: Tobias Specht Date: Tue, 6 Jun 2023 09:27:08 +0200 Subject: [PATCH 060/143] Fix code property for function declaration (#1193) Code property will be handled by applyMetadata --- .../de/fraunhofer/aisec/cpg/frontends/cxx/DeclaratorHandler.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/DeclaratorHandler.kt b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/DeclaratorHandler.kt index fdd53b30e2..7c4291fb58 100644 --- a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/DeclaratorHandler.kt +++ b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/DeclaratorHandler.kt @@ -261,7 +261,7 @@ class DeclaratorHandler(lang: CXXLanguageFrontend) : ) } else { // a plain old function, outside any named scope - declaration = newFunctionDeclaration(name, ctx.rawSignature, ctx.parent) + declaration = newFunctionDeclaration(name, null, ctx.parent) } // We want to determine, whether we are currently outside a named scope on the AST From 2d621f6e799588baf3efa5593d60c285f5f916b6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 7 Jun 2023 17:06:29 +0000 Subject: [PATCH 061/143] Update dependency webpack to v5.85.0 (#1187) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- cpg-language-typescript/src/main/nodejs/package.json | 2 +- cpg-language-typescript/src/main/nodejs/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cpg-language-typescript/src/main/nodejs/package.json b/cpg-language-typescript/src/main/nodejs/package.json index cd910ddf82..b4e8e5611e 100644 --- a/cpg-language-typescript/src/main/nodejs/package.json +++ b/cpg-language-typescript/src/main/nodejs/package.json @@ -11,7 +11,7 @@ }, "license": "Apache-2.0", "devDependencies": { - "webpack": "5.84.1", + "webpack": "5.86.0", "webpack-cli": "5.1.0" } } \ No newline at end of file diff --git a/cpg-language-typescript/src/main/nodejs/yarn.lock b/cpg-language-typescript/src/main/nodejs/yarn.lock index 291e300f0a..c583d4be75 100644 --- a/cpg-language-typescript/src/main/nodejs/yarn.lock +++ b/cpg-language-typescript/src/main/nodejs/yarn.lock @@ -772,10 +772,10 @@ webpack-sources@^3.2.3: resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== -webpack@5.84.1: - version "5.84.1" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.84.1.tgz#d4493acdeca46b26ffc99d86d784cabfeb925a15" - integrity sha512-ZP4qaZ7vVn/K8WN/p990SGATmrL1qg4heP/MrVneczYtpDGJWlrgZv55vxaV2ul885Kz+25MP2kSXkPe3LZfmg== +webpack@5.86.0: + version "5.86.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.86.0.tgz#b0eb81794b62aee0b7e7eb8c5073495217d9fc6d" + integrity sha512-3BOvworZ8SO/D4GVP+GoRC3fVeg5MO4vzmq8TJJEkdmopxyazGDxN8ClqN12uzrZW9Tv8EED8v5VSb6Sqyi0pg== dependencies: "@types/eslint-scope" "^3.7.3" "@types/estree" "^1.0.0" From 0bf15b84e68f80c575ec122ad2d1ed7c9bb0ff11 Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Wed, 7 Jun 2023 20:59:26 +0200 Subject: [PATCH 062/143] Fixed a bug in `ScopeManager::mergeFrom` (#1196) * Fixed a bug in `ScopeManager::mergeFrom` There was a rather delicate bug in the merging functionality of the ScopeManager, that always resulted in a wrong merged global scope. * Almost fixed tests * Reverted accidental changes * Added assert to test --- .../de/fraunhofer/aisec/cpg/ScopeManager.kt | 5 ++- .../aisec/cpg/graph/scopes/GlobalScope.kt | 28 ++++++++++++---- .../DeclaredReferenceExpression.kt | 3 +- .../aisec/cpg/passes/CallResolver.kt | 2 +- .../aisec/cpg/passes/inference/Inference.kt | 33 ++++++++++--------- .../cpg/passes/scopes/ScopeManagerTest.kt | 4 +++ 6 files changed, 49 insertions(+), 26 deletions(-) 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 42eb46de39..3507dd9f56 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 @@ -172,7 +172,10 @@ class ScopeManager : ScopeProvider { } } - scopeMap.putAll(manager.scopeMap) + // We need to make sure that we do not put the "null" key (aka the global scope) of the + // individual scope manager into our map, otherwise we would overwrite our merged global + // scope. + scopeMap.putAll(manager.scopeMap.filter { it.key != null }) // free the maps, just to clear up some things. this scope manager will not be used // anymore diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/scopes/GlobalScope.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/scopes/GlobalScope.kt index 6b2f61ecc6..f18548e547 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/scopes/GlobalScope.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/scopes/GlobalScope.kt @@ -25,24 +25,40 @@ */ package de.fraunhofer.aisec.cpg.graph.scopes +import de.fraunhofer.aisec.cpg.ScopeManager +import de.fraunhofer.aisec.cpg.TranslationManager +import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration + +/** + * This should ideally only be called once. It constructs a new global scope, which is not + * associated to any AST node. However, depending on the language, a language frontend can + * explicitly set the ast node using [ScopeManager.resetToGlobal] if the language needs a global + * scope that is restricted to a translation unit, i.e. C++ while still maintaining a unique list of + * global variables. + */ class GlobalScope : StructureDeclarationScope(null) { + /** - * This should ideally only be called once. It constructs a new global scope, which is not - * associated to any AST node. However, depending on the language, a language frontend can - * explicitly set the ast node using [ScopeManager.resetToGlobal] if the language needs a global - * scope that is restricted to a translation unit, i.e. C++ while still maintaining a unique - * list of global variables. + * Because the way we currently handle parallel parsing in [TranslationManager.parseParallel], + * we end up with multiple [GlobalScope] objects, one for each [TranslationUnitDeclaration]. In + * the end, we need to merge all these different scopes into one final global scope. To be + * somewhat consistent with the behaviour of [TranslationManager.parseSequentially], we assign + * the *last* translation unit declaration we see to the AST node of the [GlobalScope]. This is + * not completely ideal, but the best we can do for now. */ fun mergeFrom(others: Collection) { for (other in others) { structureDeclarations.addAll(other.structureDeclarations) valueDeclarations.addAll(other.valueDeclarations) typedefs.putAll(other.typedefs) - // TODO what to do with astNode? for (child in other.children) { child.parent = this children.add(child) } } + + // We set the AST node of the global scope to the last declaration we see (this might not be + // 100 % deterministic). + this.astNode = others.lastOrNull()?.astNode } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/DeclaredReferenceExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/DeclaredReferenceExpression.kt index 10c72885bd..10859e88de 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/DeclaredReferenceExpression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/DeclaredReferenceExpression.kt @@ -116,8 +116,7 @@ open class DeclaredReferenceExpression : Expression(), HasType.TypeListener { } // since we want to update the sub types, we need to exclude ourselves from the root, - // otherwise - // it won't work. What a weird and broken system! + // otherwise it won't work. What a weird and broken system! root.remove(this) val subTypes: MutableList = ArrayList(possibleSubTypes) subTypes.addAll(src.possibleSubTypes) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolver.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolver.kt index 5c6eaf15d7..729b77a8df 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolver.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolver.kt @@ -399,7 +399,7 @@ open class CallResolver(ctx: TranslationContext) : SymbolResolverPass(ctx) { return possibleContainingTypes .mapNotNull { var record = recordMap[it.root.name] - if (record == null && config?.inferenceConfiguration?.inferRecords == true) { + if (record == null && config.inferenceConfiguration.inferRecords == true) { record = it.startInference(ctx).inferRecordDeclaration(it, currentTU) // update the record map if (record != null) it.root.name.let { name -> recordMap[name] = record } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/Inference.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/Inference.kt index 4048ce0d7e..5352160dbb 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/Inference.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/Inference.kt @@ -86,22 +86,6 @@ class Inference(val start: Node, override val ctx: TranslationContext) : } return inferInScopeOf(start) { - log.debug( - "Inferring a new function declaration {} with parameter types {}", - name, - signature.map { it?.name } - ) - - // "upgrade" our struct to a class, if it was inferred by us, since we are calling - // methods on it. But only if the language supports classes in the first place. - if ( - record?.isInferred == true && - record.kind == "struct" && - record.language is HasClasses - ) { - record.kind = "class" - } - val inferred: FunctionDeclaration = if (record != null) { newMethodDeclaration(name ?: "", code, isStatic, record) @@ -109,6 +93,13 @@ class Inference(val start: Node, override val ctx: TranslationContext) : newFunctionDeclaration(name ?: "", code) } + log.debug( + "Inferred a new {} declaration {} with parameter types {}", + if (inferred is MethodDeclaration) "method" else "function", + inferred.name, + signature.map { it?.name } + ) + createInferredParameters(inferred, signature) // Set the type and return type(s) @@ -126,6 +117,16 @@ class Inference(val start: Node, override val ctx: TranslationContext) : } } + // "upgrade" our struct to a class, if it was inferred by us, since we are calling + // methods on it. But only if the language supports classes in the first place. + if ( + record?.isInferred == true && + record.kind == "struct" && + record.language is HasClasses + ) { + record.kind = "class" + } + inferred } } diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/scopes/ScopeManagerTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/scopes/ScopeManagerTest.kt index 31086a2685..2926bfe247 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/scopes/ScopeManagerTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/scopes/ScopeManagerTest.kt @@ -94,6 +94,10 @@ internal class ScopeManagerTest : BaseTest() { assertEquals(scopeA, final.lookupScope(namespaceA1)) assertEquals(scopeA, final.lookupScope(namespaceA2)) + // in the final scope manager, the global scope should not be any of the merged scope + // managers' original global scopes + assertFalse(listOf(s1, s2).map { it.globalScope }.contains(final.globalScope)) + // resolve symbol val call = frontend.newCallExpression( From a15aa7b5125d369e5536c4053578eeb039672c0e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 8 Jun 2023 08:42:33 +0200 Subject: [PATCH 063/143] Update dependency commons-io:commons-io to v2.13.0 (#1198) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0fe3ddeea7..cd82eb19c2 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -27,7 +27,7 @@ eclipse-runtime = { module = "org.eclipse.platform:org.eclipse.core.runtime", ve osgi-service = { module = "org.osgi:org.osgi.service.prefs", version = "1.1.2"} icu4j = { module = "com.ibm.icu:icu4j", version = "73.1"} eclipse-cdt-core = { module = "org.eclipse.cdt:core", version = "8.0.0.202211292120"} -commons-io = { module = "commons-io:commons-io", version = "2.12.0"} +commons-io = { module = "commons-io:commons-io", version = "2.13.0"} jetbrains-annotations = { module = "org.jetbrains:annotations", version = "24.0.0"} picocli = { module = "info.picocli:picocli", version = "4.7.0"} picocli-codegen = { module = "info.picocli:picocli-codegen", version = "4.7.0"} From cf8375d61273f69839ec3dbaad3b28e65be812f4 Mon Sep 17 00:00:00 2001 From: KuechA <31155350+KuechA@users.noreply.github.com> Date: Sun, 11 Jun 2023 17:01:18 +0200 Subject: [PATCH 064/143] Infer DFG for calls to implicit functions (#1197) --- .../de/fraunhofer/aisec/cpg/passes/DFGPass.kt | 16 ++++++++++------ .../aisec/cpg/passes/UnresolvedDFGPassTest.kt | 8 +++----- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/DFGPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/DFGPass.kt index d1d3ec7202..6e9ab553d9 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/DFGPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/DFGPass.kt @@ -388,11 +388,15 @@ class DFGPass(ctx: TranslationContext) : ComponentPass(ctx) { if (call.invokes.isEmpty() && inferDfgForUnresolvedSymbols) { // Unresolved call expression - handleUnresolvedCalls(call) + handleUnresolvedCalls(call, call) } else if (call.invokes.isNotEmpty()) { call.invokes.forEach { - Util.attachCallParameters(it, call.arguments) - call.addPrevDFG(it) + if (it.isInferred && inferDfgForUnresolvedSymbols) { + handleUnresolvedCalls(call, it) + } else { + Util.attachCallParameters(it, call.arguments) + call.addPrevDFG(it) + } } } } @@ -402,11 +406,11 @@ class DFGPass(ctx: TranslationContext) : ComponentPass(ctx) { * - from base (if available) to the CallExpression * - from all arguments to the CallExpression */ - private fun handleUnresolvedCalls(call: CallExpression) { + private fun handleUnresolvedCalls(call: CallExpression, dfgTarget: Node) { if (call is MemberCallExpression && !call.isStatic) { - call.base?.let { call.addPrevDFG(it) } + call.base?.let { dfgTarget.addPrevDFG(it) } } - call.arguments.forEach { call.addPrevDFG(it) } + call.arguments.forEach { dfgTarget.addPrevDFG(it) } } } diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/UnresolvedDFGPassTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/UnresolvedDFGPassTest.kt index 72eb1dfee4..f5ecc5d7e7 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/UnresolvedDFGPassTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/UnresolvedDFGPassTest.kt @@ -149,16 +149,14 @@ class UnresolvedDFGPassTest { } declare { variable("s2", t("String")) { - memberCall("get", ref("os")) { - addArgument(literal(4, t("int"))) - } + memberCall("get", ref("os")) { literal(4, t("int")) } } } declare { variable("duc", t("DfgUnresolvedCalls")) { new { construct("DfgUnresolvedCalls") { - addArgument(literal(3, t("int"))) + literal(3, t("int")) } } } @@ -166,7 +164,7 @@ class UnresolvedDFGPassTest { declare { variable("i", t("int")) { memberCall("knownFunction", ref("duc")) { - addArgument(literal(2, t("int"))) + literal(2, t("int")) } } } From 8b9b1c039fd7efcd45a8fbcc178b5c989c69266c Mon Sep 17 00:00:00 2001 From: Tobias Specht Date: Tue, 13 Jun 2023 12:27:27 +0200 Subject: [PATCH 065/143] Clean up old list of file extensions (#1203) --- .../fraunhofer/aisec/cpg/frontends/cxx/CXXLanguageFrontend.kt | 2 -- .../aisec/cpg/frontends/java/JavaLanguageFrontend.kt | 1 - .../aisec/cpg/frontends/llvm/LLVMIRLanguageFrontend.kt | 4 ---- 3 files changed, 7 deletions(-) diff --git a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXLanguageFrontend.kt b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXLanguageFrontend.kt index b681ef2ca7..e102a7cea0 100644 --- a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXLanguageFrontend.kt +++ b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXLanguageFrontend.kt @@ -638,8 +638,6 @@ class CXXLanguageFrontend(language: Language, ctx: Translat } companion object { - @JvmField val CXX_EXTENSIONS = mutableListOf(".c", ".cpp", ".cc") - @JvmField val CXX_HEADER_EXTENSIONS = mutableListOf(".h", ".hpp") private val LOGGER = LoggerFactory.getLogger(CXXLanguageFrontend::class.java) private fun explore(node: IASTNode, indent: Int) { 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 049f92382e..a277e5b7a2 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 @@ -485,7 +485,6 @@ open class JavaLanguageFrontend(language: Language, ctx: T } companion object { - @JvmField val JAVA_EXTENSIONS = listOf(".java") const val THIS = "this" const val ANNOTATION_MEMBER_VALUE = "value" } diff --git a/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/LLVMIRLanguageFrontend.kt b/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/LLVMIRLanguageFrontend.kt index 9eb7bf5080..6d379022c8 100644 --- a/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/LLVMIRLanguageFrontend.kt +++ b/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/LLVMIRLanguageFrontend.kt @@ -72,10 +72,6 @@ class LLVMIRLanguageFrontend(language: Language, ctx: Tr */ var bindingsCache = mutableMapOf() - companion object { - @JvmField var LLVM_EXTENSIONS: List = listOf(".ll") - } - override fun parse(file: File): TranslationUnitDeclaration { var bench = Benchmark(this.javaClass, "Parsing sourcefile") // clear the bindings cache, because it is just valid within one module From e66bcb0603e34d8c927f3b2bf0014c3b5d94e69d Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Wed, 14 Jun 2023 14:19:51 +0200 Subject: [PATCH 066/143] Added documentation website (#1206) --- .github/workflows/build.yml | 4 + .github/workflows/docs.yml | 46 + docs/Dockerfile | 13 + docs/README.md | 17 + docs/docs/CPG/impl/index.md | 26 + docs/docs/CPG/impl/language.md | 100 + docs/docs/CPG/impl/passes.md | 69 + docs/docs/CPG/impl/scopes.md | 15 + docs/docs/CPG/index.md | 14 + docs/docs/CPG/specs/dfg.md | 473 + docs/docs/CPG/specs/eog.md | 770 ++ docs/docs/CPG/specs/graph.md | 3328 +++++ docs/docs/CPG/specs/index.md | 19 + docs/docs/CPG/specs/schema.md | 10952 ++++++++++++++++ docs/docs/Contributing/index.md | 156 + docs/docs/GettingStarted/cli.md | 10 + docs/docs/GettingStarted/index.md | 19 + docs/docs/GettingStarted/installation.md | 29 + docs/docs/GettingStarted/library.md | 77 + docs/docs/GettingStarted/query.md | 169 + .../img/Institut-AISEC-Gebaeude-Nacht.jpg | Bin 0 -> 29701 bytes docs/docs/assets/img/cpg-flow-graphs.drawio | 1 + .../assets/img/cpg-flow-no-backend.drawio | 1 + docs/docs/assets/img/cpg-flow-no-backend.png | Bin 0 -> 882240 bytes docs/docs/assets/img/cpg-flow.drawio | 1 + docs/docs/assets/img/cpg-flow.png | Bin 0 -> 726273 bytes docs/docs/assets/img/graph.svg | 1 + docs/docs/assets/img/logo-aisec.jpg | Bin 0 -> 63139 bytes .../img/overall-view-black-background.png | Bin 0 -> 163683 bytes .../img/overall-view-black-background.svg | 1619 +++ .../img/overall-view-white-background.png | Bin 0 -> 208348 bytes docs/docs/assets/img/overall-view.png | Bin 0 -> 175421 bytes docs/docs/assets/img/overall-view.svg | 1613 +++ docs/docs/index.md | 239 + docs/docs/stylesheets/extra.css | 448 + docs/mkdocs-material-plugins.txt | 3 + docs/mkdocs.yaml | 171 + 37 files changed, 20403 insertions(+) create mode 100644 .github/workflows/docs.yml create mode 100755 docs/Dockerfile create mode 100755 docs/README.md create mode 100755 docs/docs/CPG/impl/index.md create mode 100755 docs/docs/CPG/impl/language.md create mode 100755 docs/docs/CPG/impl/passes.md create mode 100755 docs/docs/CPG/impl/scopes.md create mode 100755 docs/docs/CPG/index.md create mode 100755 docs/docs/CPG/specs/dfg.md create mode 100644 docs/docs/CPG/specs/eog.md create mode 100644 docs/docs/CPG/specs/graph.md create mode 100755 docs/docs/CPG/specs/index.md create mode 100644 docs/docs/CPG/specs/schema.md create mode 100644 docs/docs/Contributing/index.md create mode 100755 docs/docs/GettingStarted/cli.md create mode 100644 docs/docs/GettingStarted/index.md create mode 100755 docs/docs/GettingStarted/installation.md create mode 100644 docs/docs/GettingStarted/library.md create mode 100755 docs/docs/GettingStarted/query.md create mode 100644 docs/docs/assets/img/Institut-AISEC-Gebaeude-Nacht.jpg create mode 100755 docs/docs/assets/img/cpg-flow-graphs.drawio create mode 100755 docs/docs/assets/img/cpg-flow-no-backend.drawio create mode 100755 docs/docs/assets/img/cpg-flow-no-backend.png create mode 100755 docs/docs/assets/img/cpg-flow.drawio create mode 100755 docs/docs/assets/img/cpg-flow.png create mode 100644 docs/docs/assets/img/graph.svg create mode 100755 docs/docs/assets/img/logo-aisec.jpg create mode 100755 docs/docs/assets/img/overall-view-black-background.png create mode 100755 docs/docs/assets/img/overall-view-black-background.svg create mode 100755 docs/docs/assets/img/overall-view-white-background.png create mode 100755 docs/docs/assets/img/overall-view.png create mode 100755 docs/docs/assets/img/overall-view.svg create mode 100755 docs/docs/index.md create mode 100755 docs/docs/stylesheets/extra.css create mode 100755 docs/mkdocs-material-plugins.txt create mode 100755 docs/mkdocs.yaml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 64f094e4d2..22e27d35af 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,8 +7,12 @@ on: - main tags: - v*.** + paths: + - "!docs/**" pull_request: types: [opened, synchronize, reopened] + paths: + - "!docs/**" jobs: build-cpgo-osx: diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000000..75642e346c --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,46 @@ +name: docs + +on: + workflow_dispatch: + push: + branches: + - main + paths: + - docs/** + pull_request: + types: [opened, synchronize, reopened] + paths: + - docs/** + +jobs: + build: + runs-on: ubuntu-latest + if: github.event.repository.fork == false + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Install python3 + uses: actions/setup-python@v3 + with: + python-version: 3.x + - name: Cache + uses: actions/cache@v3 + with: + key: ${{ github.ref }} + path: .cache + - name: Install Material for MkDocs + run: | + pip install mkdocs-material pillow cairosvg + pip install -r docs/mkdocs-material-plugins.txt + - name: Build + run: cd docs && mkdocs build --clean --config-file mkdocs.yaml -d site -v + - name: Publish main + if: github.ref == 'refs/heads/main' + uses: JamesIves/github-pages-deploy-action@v4 + with: + folder: docs/site + - name: Publish version + if: startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, 'beta') && !contains(github.ref, 'alpha') + uses: JamesIves/github-pages-deploy-action@v4 + with: + folder: docs/site diff --git a/docs/Dockerfile b/docs/Dockerfile new file mode 100755 index 0000000000..c354c36f0e --- /dev/null +++ b/docs/Dockerfile @@ -0,0 +1,13 @@ +FROM squidfunk/mkdocs-material + +# Ensure installation of required packages +# - uses updated cache on-the-fly without storing it locally +RUN apk --no-cache add \ + git + +# Install additional plugins (cf. `./mkdocs-material-plugins.txt`) +COPY mkdocs-material-plugins.txt / +RUN python -m pip install --no-cache-dir -r /mkdocs-material-plugins.txt + +# Trust git directory for git revision plugin +RUN git config --global --add safe.directory /docs diff --git a/docs/README.md b/docs/README.md new file mode 100755 index 0000000000..93519e93ab --- /dev/null +++ b/docs/README.md @@ -0,0 +1,17 @@ +# CPG Documentation + +The documentation for CPG is built with [Material for MkDocs](https://squidfunk.github.io/mkdocs-material/) and hosted with GitHub Pages. + +Simply use the provided [Dockerfiles](./Dockerfile) in this directory. +It includes all the necessary plugins. + +To build the Docker image use: +```shell +docker build -t mkdocs-material . +``` +Afterwards, you can start a local development server: +```shell +docker run --rm -it -p 8000:8000 -v ${PWD}:/docs mkdocs-material +``` + +Please note, that the `git-revision-date-localized` plugin does not work with git worktrees. \ No newline at end of file diff --git a/docs/docs/CPG/impl/index.md b/docs/docs/CPG/impl/index.md new file mode 100755 index 0000000000..9359f148d5 --- /dev/null +++ b/docs/docs/CPG/impl/index.md @@ -0,0 +1,26 @@ +--- +title: "Implementation and Concepts" +linkTitle: "Implementation and Concepts" +weight: 20 +no_list: false +menu: + main: + weight: 20 +description: > + The CPG library is a language-agnostic graph representation of source code. +--- + +# Implementation and Concepts + +The translation of source code to the graph consists of two main steps. First, +the source code is parsed and transferred to the CPG nodes by a so-called +**Language Frontend**. Then, **Passes** refine the information which is kept in +the graph. These two stages are strictly separated one from each other. + +![Overview of the CPG pipeline](../../assets/img/cpg-flow.png) +{ align=center } + + +* [Languages and Language Frontends](./language) +* [Scopes](./scopes) +* [Passes](./passes) diff --git a/docs/docs/CPG/impl/language.md b/docs/docs/CPG/impl/language.md new file mode 100755 index 0000000000..af60b07b34 --- /dev/null +++ b/docs/docs/CPG/impl/language.md @@ -0,0 +1,100 @@ +--- +title: "Implementation and Concepts - Language Frontends" +linkTitle: "Implementation and Concepts - Language Frontends" +weight: 20 +no_list: false +menu: + main: + weight: 20 +description: > + The CPG library is a language-agnostic graph representation of source code. +--- + + + +# Implementation and Concepts: Language and Language Frontends + +Even though we are aiming for a language-independent representation of source +code, we still need to parse source code depending on the original programming +language used. Therefore, we are introduce two concepts that help developers and +users to understand how the CPG translates language-specific code into an +abstract form. + +## `Language` + +The first concept is a `Language`. It represents the programming language as a +general concept and contains meta-information about it. This includes: + +* The name of the language, e.g. C++ +* The delimiter used to separate namespaces, e.g., `::` +* The [`LanguageFrontend`](#LanguageFrontend) used to parse it +* Additional [`LanguageTrait`](#LanguageTrait) implementations + +Each `Node` has a `language` property that specifies its language. + +### `LanguageTrait` + +A language trait aims to further categorize a programming language based on +conceptual paradigms. This can be easily extended by introducing new interfaces +based on `LanguageTrait`. Examples include: + +* Are default arguments supported? +* Does the language have structs or classes? +* Are function pointers supported? +* Are templates or generics used in the language? +* Do we need some special knowledge to resolve symbols, calls, variables? + +These traits are used during the pass execution phase to fine-tune things like +call resolution or type hierarchies. + +## `LanguageFrontend` + +In contrast to the `Language` concept, which represents the generic concept of a +programming language, a `LanguageFrontend` is a specific module in the CPG +library that does the actual translating of a programming language's source code +into our CPG representation. + +At minimum a language frontend needs to parse the languages' code and translate +it to specific CPG nodes. It will probably use some library to retrieve the +abstract syntax tree (AST). The frontend will set the nodes' `AST` edges and +establish proper scopes via the scope manager. Everything else, such as call or +symbol resolving is optional and will be done by later passes. However, if a +language frontend is confident in setting edges, such as `REFERS_TO`, it is +allowed to and this is respected by later passes. However, one must be extremely +careful in doing so. + +The frontend has a limited life-cycle and only exists during the *translation* +phase. Later, during the execution of passes, the language frontend will not +exist anymore. Language-specific customization of passes are done using +[`LanguageTraits`](#LanguageTrait). + +To create nodes, a language frontend MUST use the node builder functions in the +`ExpressionBuilder`, `DeclarationBuilder` or `StatementBuilder`. These are +Kotlin extension functions that automatically inject the context, such as +language, scope or code location of a language frontend or its handler into the +created nodes. + +## Supporting a new language + +To support a new language, all you have to do is to + +* Provide a new `Language`. Here, you have to think about the features of the + programming language and which `LanguageTraits` the respective language has to + implement. With this, you provide the respective fine-tuning to make the + Passes work properly. +* Implement a new `LanguageFrontend` which is executed if a file matches the new + `Language`. The requirements of the frontends are described above. + +To make use of the respective frontend by the CPG, you have to configure the +translation accordingly. This is done by the `TranslationConfiguration` where +you register your new language by calling one of the `registerLanguage()` +methods. As an example +```kotlin +val config: TranslationConfiguration = TranslationConfiguration + .builder() + // More configuration + .registerLanguage(MyNewLanguage()) // Option 1 + .registerLanguage() // Option 2 + .registerLanguage("MyThirdNewLanguage") // Option 3 + .build() +``` diff --git a/docs/docs/CPG/impl/passes.md b/docs/docs/CPG/impl/passes.md new file mode 100755 index 0000000000..a6903983f3 --- /dev/null +++ b/docs/docs/CPG/impl/passes.md @@ -0,0 +1,69 @@ +--- +title: "Implementation and Concepts - Passes" +linkTitle: "Implementation and Concepts - Passes" +weight: 20 +no_list: false +menu: + main: + weight: 20 +description: > + The CPG library is a language-agnostic graph representation of source code. +--- + + + +# Implementation and Concepts: Passes + +## What is a Pass? + +Passes get a prebuilt CPG that at least contains the CPG-AST and output a +modified graph. Their purpose is to extend the syntactic representation of code +with additional nodes and edges to represent the semantics of the program. +Passes can be executed in sequence, where the output of the previous pass serves +as input of the next pass. + +## Creating a new Pass + +The user of the cpg library can implement her own passes. Each pass needs to +extend the class `Pass` and implement its base function`accept(result: TranslationResult)`. +The remaining structure of the pass is free to be designed by the +implementer. + +## Registering a Pass + +A newly created pass has to be registered with the `TranslationManager` through +its builder by calling +``` +val configuration = TranslationConfiguration.builder(). + // ... + .registerPass(...) +``` +## Modifying a Pass + +A preexisting pass can be modified by extending it and overwriting its +functions. For this purpose, all member functions of existing library passes +have the visibility `protected`. Depending on the modified pass, internal +constructs have to be respected. + +For example, the `EvaluationOrderGraphPass` uses an internal handle structure. +When extending this pass, it is necessary to add handlers of new Node types to +the internal handler map. If a developer needs to override an exisiting handler, +the handle has to be implemented with the same signature to use the polymorphism +feature. Additionally the mapping of `node type -> handler` needs to be replaced +by a new entry `node type -> overridden` handler. + +## Ordering Passes +Passes may depend on the information added by another pass. This requires us to +enforce the order in which passes are executed. To do so, we provide the +following annotations for the passes: + +* `DependsOn(other: KClass, softDependency: Boolean = false)` -- The annotated pass is executed after + the other pass(es). If `softDependency` is set to `false`, it automatically + registers these passes if they haven't been registered by the user. +* `ExecuteBefore(other: KClass, ...)` -- The annotated pass is executed + before the other pass(es) specified. +* `ExecuteFirst` -- The annotated pass is executed as the first pass if possible. +* `ExecuteLast` -- The annotated pass is executed as the last pass if possible. +* `RequiredFrontend(frontend: KClass)` -- The annotated pass + is only executed if the frontend has been used. + diff --git a/docs/docs/CPG/impl/scopes.md b/docs/docs/CPG/impl/scopes.md new file mode 100755 index 0000000000..9fafc1ea83 --- /dev/null +++ b/docs/docs/CPG/impl/scopes.md @@ -0,0 +1,15 @@ +--- +title: "Implementation and Concepts - Scopes" +linkTitle: "Implementation and Concepts - Scopes" +weight: 20 +no_list: false +menu: + main: + weight: 20 +description: > + The CPG library is a language-agnostic graph representation of source code. +--- + + +# Implementation and Concepts: Scopes and Scope Manger + diff --git a/docs/docs/CPG/index.md b/docs/docs/CPG/index.md new file mode 100755 index 0000000000..c53676f80c --- /dev/null +++ b/docs/docs/CPG/index.md @@ -0,0 +1,14 @@ +--- +title: "Documentation" +linkTitle: "Documentation" +weight: 20 +no_list: true +menu: + main: + weight: 20 +description: > + The CPG library is a language-agnostic graph representation of source code. +--- + + + diff --git a/docs/docs/CPG/specs/dfg.md b/docs/docs/CPG/specs/dfg.md new file mode 100755 index 0000000000..6de6b62ad1 --- /dev/null +++ b/docs/docs/CPG/specs/dfg.md @@ -0,0 +1,473 @@ +# Specification: Data Flow Graph + +The Data Flow Graph (DFG) is built as edges between nodes. Each node has a set of incoming data flows (`prevDFG`) and outgoing data flows (`nextDFG`). In the following, we summarize how different types of nodes construct the respective data flows. + + +## CallExpression + +Interesting fields: + +* `invokes: List`: A list of the functions which are called +* `arguments: List`: The arguments which are used in the function call + +A call expressions calls another function. We differentiate two types of call expressions: 1) the called function is implemented in the program (and we can analyze the code) and 2) the called function cannot be analyzed (e.g., this is the case for library/API functions). For the first case, the `invokes` list contains values, in the second case, the list is empty. + +### Case 1: Known function + +For each function in the `invokes` list, the arguments of the call expression flow to the function's parameters. The value of the function declaration flows to the call. + +Scheme: + ```mermaid + flowchart LR + node([CallExpression]) -.- invokes["invokes[j]"]; + node -.- arguments["arguments[i]"]; + invokes ==> decl([FunctionDeclaration]) + decl -.- parameters["parameters[i]"] + arguments -- "for all i: DFG" --> parameters + invokes -- "forall j: DFG" --> node + ``` + +### Case 2: Unknown function + +The base and all arguments flow to the call expression. + +Scheme: + ```mermaid + flowchart LR + arguments["arguments[i]"] -- "for all i: DFG" --> node([CallExpression]); + base -- DFG --> node; + arguments -.- node; + node -.- base; + ``` + +## CastExpression + +Interesting fields: + +* `expression: Expression`: The inner expression which has to be casted + +The value of the `expression` flows to the cast expression. +Scheme: +```mermaid + flowchart LR + node([CastExpression]) -.- expression; + expression -- DFG --> node; +``` + +## BinaryOperator + +Interesting fields: + +* `operatorCode: String`: String representation of the operator +* `lhs: Expression`: The left-hand side of the operation +* `rhs: Expression`: The right-hand side of the operation + +We have to differentiate between the operators. We can group them into three categories: 1) Assignment, 2) Assignment with a Computation and 3) Computation. + +### Case 1: Assignment (`operatorCode: =`) + +The `rhs` flows to `lhs`. In some languages, it is possible to have an assignment in a subexpression (e.g. `a + (b=1)`). +For this reason, if the assignment's ast parent is not a `CompoundStatement` (i.e., a block of statements), we also add a DFG edge to the whole operator. + +Scheme: +```mermaid +flowchart LR + node([BinaryOperator]) -.- rhs(rhs); + rhs -- DFG --> lhs; + node([BinaryOperator]) -.- lhs(lhs); + +``` + +```mermaid +flowchart LR + node([BinaryOperator]) -.- lhs(lhs); + node([BinaryOperator]) -.- rhs(rhs); + rhs -- DFG --> lhs; + rhs -- DFG --> node; +``` + + ```mermaid + flowchart LR + A[binaryOperator.rhs] -- DFG --> binaryOperator.lhs; + subgraph S[If the ast parent is not a CompoundStatement] + direction LR + binaryOperator.rhs -- DFG --> binaryOperator; + end + A --> S; + ``` + + +### Case 2: Assignment with a Computation (`operatorCode: *=, /=, %=, +=, -=, <<=, >>=, &=, ^=, |=` ) + +The `lhs` and the `rhs` flow to the binary operator expression, the binary operator flows to the `lhs`. + +Scheme: + ```mermaid + flowchart LR + node([BinaryOperator]) -.- lhs(lhs); + node([BinaryOperator]) -.- rhs(rhs); + lhs -- DFG --> node; + rhs -- DFG --> node; + node == DFG ==> lhs; + ``` + +*Dangerous: We have to ensure that the first two operations are performed before the last one* + + +### Case 3: Computation + +The `lhs` and the `rhs` flow to the binary operator expression. + +Scheme: + ```mermaid + flowchart + node([BinaryOperator]) -.- lhs(lhs); + node([BinaryOperator]) -.- rhs(rhs); + rhs -- DFG --> node; + lhs -- DFG --> node; + ``` + +## ArrayCreationExpression + +Interesting fields: + +* `initializer: Expression`: The initialization values of the array. + +The `initializer` flows to the array creation expression. + +Scheme: + ```mermaid + flowchart LR + node([ArrayCreationExpression]) -.- initializer(initializer) + initializer -- DFG --> node + ``` + +## NewExpression + +Interesting fields: +* `initializer: Expression`: The initializer of the expression. + +The `initializer` flows to the whole expression. + +Scheme: + ```mermaid + flowchart LR + node([NewExpression]) -.- initializer(initializer) + initializer -- DFG --> node + ``` + +## ArraySubscriptionExpression + +Interesting fields: + +* `arrayExpression: Expression`: The array which is accessed +* `subscriptExpression: Expression`: The index which is accessed + +The `arrayExpression` flows to the subscription expression. This means, we do not differentiate between the field which is accessed. + +Scheme: + ```mermaid + flowchart LR + arrayExpression -- DFG --> node([ArraySubscriptionExpression]); + arrayExpression -.- node; + ``` + + +## ConditionalExpression + +Interesting fields: + +* `condition: Expression`: The condition which is evaluated +* `thenExpr: Expression`: The expression which is executed if the condition holds +* `elseExpr: Expression`: The expression which is executed if the condition does not hold + +The `thenExpr` and the `elseExpr` flow to the `ConditionalExpression`. This means that implicit data flows are not considered. + +Scheme: + ```mermaid + flowchart LR + thenExpr -- DFG --> node([ConditionalExpression]); + thenExpr -.- node; + elseExpr -.- node; + elseExpr -- DFG --> node; + ``` + +## DeclaredReferenceExpression + +Interesting fields: + +* `refersTo: Declaration`: The declaration e.g. of the variable or symbol +* `access: AccessValues`: Determines if the value is read from, written to or both + +This is the most tricky concept for the DFG edges. We have to differentiate between the DFG edges generated by the `DFGPass` and the ones generated by the `ControlFlowSensitiveDFGPass`. + +The `DFGPass` generates very simple edges based on the access to the variable as follows: + +* The value flows from the declaration to the expression for read access. Scheme: + ```mermaid + flowchart LR + refersTo -- DFG --> node([DeclaredReferenceExpression]); + refersTo -.- node; + ``` +* For write access, data flow from the expression to the declaration. Scheme: + ```mermaid + flowchart LR + node([DeclaredReferenceExpression]) -- DFG --> refersTo; + node -.- refersTo; + ``` +* For readwrite access, both flows are present. Scheme: + ```mermaid + flowchart LR + refersTo -- DFG 1 --> node([DeclaredReferenceExpression]); + refersTo -.- node; + node -- DFG 2 --> refersTo; + ``` + +This mostly serves one purpose: The current function pointer resolution requires such flows. Once the respective passes are redesigned, we may want to update this. + +The `ControlFlowSensitiveDFGPass` completely changes this behavior and accounts for the data flows which differ depending on the program's control flow (e.g., different assignments to a variable in an if and else branch, ...). The pass performs the following actions: + +* First, it clears all the edges between a `VariableDeclaration` and its `DeclaredReferenceExpression`. Actually, it clears all incoming and outgoing DFG edges of all VariableDeclarations in a function. This includes the initializer but this edge is restored right away. Scheme: + ```mermaid + flowchart LR + node([VariableDeclaration]) -.- initializer; + initializer -- DFG --> node; + ``` +* For each read access to a DeclaredReferenceExpression, it collects all potential previous assignments to the variable and adds these to the incoming DFG edges. You can imagine that this is done by traversing the EOG backwards until finding the first assignment to the variable for each possible path. Scheme: + ```mermaid + flowchart LR + node([DeclaredReferenceExpression]) -.- refersTo; + A == last write to ==> refersTo; + A[/Node/] -- DFG --> node; + ``` +* If we increment or decrement a variable with "++" or "--", the data of this statement flows from the previous writes of the variable to the input of the statement (= the DeclaredReferenceExpression). We write back to this reference and consider the lhs as a "write" to the variable! *Attention: This potentially adds loops and can look like a branch. Needs to be handled with care in subsequent passes/analyses!* Scheme: + ```mermaid + flowchart LR + node([UnaryOperator]) -.- input; + input -.- |"(optional)"| refersTo; + W -- DFG 1 --> input; + W[/Node/] == last write to ==> refersTo; + input -- DFG 2 --> node; + node -- DFG 3 --> input; + input -- DFG 4 --> R[/Node/]; + R == next read of ==> refersTo; + ``` +* For compound operators such as `+=, -=, *=, /=`, we have an incoming flow from the last writes to reference on the left hand side of the expression to the lhs. The lhs then flows to the whole expression. Also, the right hand side flows to the whole expression (if it's a read, this is processed separately). The data flows back to the lhs which is marked as the last write to the variable. *Attention: This potentially adds loops and can look like a branch. Needs to be handled with care in subsequent passes/analyses!* + ```mermaid + flowchart LR + node -.- rhs; + node -.- lhs; + lhs -.- refersTo; + W -- DFG 1 --> lhs; + W[/Node/] == last write to ==> refersTo; + rhs -- DFG 2 --> node; + lhs -- DFG 4 --> R; + lhs -- DFG 2 --> node([BinaryOperator]); + node -- DFG 3 --> lhs; + R[/Node/] == next read of ==> refersTo; + ``` +* If the variable is assigned a value (a binary operator `var = rhs`), the right hand side flows to the variable. This is considered as a write operation. + ```mermaid + flowchart LR + node -.- rhs; + node -.- lhs; + lhs -.- refersTo; + lhs -- DFG 2 --> node([BinaryOperator]); + R[/Node/] == next read of ==> refersTo; + rhs -- DFG --> lhs; + lhs -- DFG --> refersTo + ``` + +## MemberExpression + +Interesting fields: + +* `base: Expression`: The base object whose field is accessed +* `refersTo: Declaration?`: The field it refers to. If the class is not implemented in the code under analysis, it is `null`. + +The MemberExpression represents an access to an object's field and extends a DeclaredReferenceExpression with a `base`. + +If an implementation of the respective class is available, we handle it like a normal DeclaredReferenceExpression. +If the `refersTo` field is `null` (i.e., the implementation is not available), base flows to the expression. + +## ExpressionList + +Interesting fields: + +* `expressions: List` + +The data of the last statement in `expressions` flows to the expression. + +## InitializerListExpression + +Interesting fields: + +* `initializers: List`: The list of expressions which initialize the values. + +The data of all initializers flow to this expression. + +Scheme: +```mermaid + flowchart LR + inits["for all i: initializers[i]"] -- DFG --> node([InitializerListExpression]); + node -.- inits; +``` + +## KeyValueExpression + +Interesting fields: + +* `value: Expression`: The value which is assigned. + +The value flows to this expression. + +Scheme: +```mermaid + flowchart LR + value -- DFG --> node([KeyValueExpression]); + value -.- node; +``` + + +## LambdaExpression + +Interesting fields: + +* `function: FunctionDeclaration`: The usage of a lambda + +The data flow from the function representing the lambda to the expression. + +Scheme: +```mermaid + flowchart LR + function -- DFG --> node([LambdaExpression]); + function -.- node; +``` + +## UnaryOperator + +Interesting fields: + +* `input: Expression`: The inner expression +* `operatorCode: String`: A string representation of the operation + +The data flow from the input to this node and, in case of the operatorCodes ++ and -- also back from the node to the input. + +```mermaid + flowchart TD + node1([UnaryOperator]) -.- operator + operator ==> cmp + + cmp == "operator == '++' || + operator == '--'" ==> incdec; + + cmp == "operator != '++' && + operator != '--'" ==> finish[ ]; + + subgraph finish[ ] + node2([UnaryOperator]) -.- input2; + input2 -.- |"(optional)"| refersTo2; + W2[/Node/] == last write to ==> refersTo2; + W2 -- DFG 1 --> input2[input]; + input2 -- DFG 2 --> node2; + end + + subgraph incdec[ ] + node([UnaryOperator]) -.- input; + input -.- |"(optional)"| refersTo; + W[/Node/] == last write to ==> refersTo; + W -- DFG 1 --> input; + input -- DFG 2 --> node; + node -- DFG 3 --> input; + input -- DFG 4 --> R[/Node/]; + R == next read of ==> refersTo; + end +``` + +*Dangerous: We have to ensure that the first operation is performed before the last one (if applicable)* + + +## ReturnStatement + +Interesting fields: + +* `returnValue: Expression`: The value which is returned + +The return value flows to the whole statement. + +Scheme: +```mermaid + flowchart LR + returnValue -- DFG --> node([ReturnStatement]); + returnValue -.- node; +``` + +## FunctionDeclaration + +Interesting fields: + +* `body: Expression`: The body (i.e., all statements) of the function implementation + +The values of all return expressions in the body flow to the function declaration. + +Scheme: +```mermaid + flowchart LR + returns -- DFG --> node([FunctionDeclaration]); + body -.- node; + body -.- |in all statements| returns["returns: ReturnStatement"] +``` + + +## FieldDeclaration + +Interesting fields: + +* `initializer: Expression?`: The value which is used to initialize a field (if applicable). + +The value of the initializer flows to the whole field. + +In addition, all writes to a reference to the field (via a `DeclaredReferenceExpression`) flow to the field, for all reads, data flow to the reference. + +Scheme: +```mermaid + flowchart LR + initializer -- DFG --> node([FieldDeclaration]); + initializer -.- node; + node -- DFG --> R[/Node/]; + R == next read of ==> node; +``` + +## VariableDeclaration + +Interesting fields: + +* `initializer: Expression?`: The value which is used to initialize a variable (if applicable). + +The value of the initializer flows to the variable declaration. The value of the variable declarations flows to all `DeclaredReferenceExpressions` which read the value before the value of the variable is written to through another reference to the variable. + +Scheme: +```mermaid + flowchart LR + initializer -- DFG --> node([VariableDeclaration]); + initializer -.- node; + node -- DFG --> R[/Node/]; + R == next read of ==> node; +``` + +## Assignment + +Interesting fields: + +* `value: Expression`: The rhs of the assignment +* `target: AssignmentTarget`: The lhs of the assignment + +This should already be covered by the declarations and binary operator "=". If not, the `value` flows to the `target` + +Scheme: +```mermaid + flowchart LR + value -.- node([Assignment]); + target -.- node; + value -- DFG --> target; +``` diff --git a/docs/docs/CPG/specs/eog.md b/docs/docs/CPG/specs/eog.md new file mode 100644 index 0000000000..b8eaa53dcd --- /dev/null +++ b/docs/docs/CPG/specs/eog.md @@ -0,0 +1,770 @@ +# Specification: Evaluation Order Graph + +The Evaluation Order Graph (EOG) is built as edges between AST nodes after the initial translation of the code to the CPG. +Its purpose is to follow the order in which code is executed, similar to a CFG, and additionally differentiate on a finer level of granularity in which order expressions and subexpressions are evaluated. +Every node points to a set of previously evaluated nodes (`prevEOG`) and nodes that are evaluated after (`nextEOG`). +The EOG edges are intraprocedural and thus differentiate from INVOKES edges. +In the following, we summarize in which order the root node representing a language construct and its descendants in the AST tree are connected. + +An EOG always starts at root node representing a method/function or record that holds executable code and ends in the node representing the corresponding code or multiple return statements. +An implicit return statement with a code location of (-1,-1) is used if the actual source code does not have an explicit return statement. + +A distinct EOG is drawn for any declared component that can contain code, currently: `NamespaceDeclaration`, `TranslationUnitDeclaration`, `RecordDeclaration` and any subclass of `FunctionDeclaration`. + +The EOG is similar to a CFG which connects basic blocks of statements, but there are some subtle differences: + +* For methods without explicit return statement, the EOG will have an edge to a virtual return node with line number -1 which does not exist in the original code. + A CFG will always end with the last reachable statement(s) and not insert any virtual return statements. +* The EOG considers an opening blocking (`CompoundStatement`, indicated by a `{`) as a separate node. + A CFG will rather use the first actual executable statement within the block. +* For IF statements, the EOG treats the `if` keyword and the condition as separate nodes. + A CFG treats this as one `if` statement. +* The EOG considers a method header as a node. + A CFG will consider the first executable statement of the methods as a node. + +## General Structure + +The graphs in this specification abstract the representation of the handled graph, to formally specify how EOG edges are drawn between a parent node and the subgraphs rooted by its children. +Therefore, a collection of AST children are represented as abstract nodes showing the multiplicity of the node with an indicator (n), in case of sets, or as several nodes showing how the position in a list can impact the construction of an EOG, e.g., nodes (i - 1) to i. +The EOG is constructed as postorder of the AST traversal. +When building the EOG for the expression a + b, the entire expression is considered evaluated after the subexpression a and the subexpression b is evaluated, therefore EOG edges connect nodes of (a) and (b) before reaching the parent node (+). + +Note: Nodes describing the titled programing construct will be drawn round, while the rectangular nodes represent their abstract children, that can be atomic leaf nodes or deep AST subtrees. +EOG edges to these abstract nodes always mean that a subtree expansion would be necessary to connect the target of the EOG edge to the right node in the subtree. + +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; + prev:::outer --EOG--> lhs + node --EOG--> next:::outer + node([+]) -.-> lhs["a"] + node -.-> rhs["b"] + lhs --EOG--> rhs + rhs --EOG--> node +``` + +Whether a subgraph (a) or (b) is connected first, depends on the exact construct and sometimes the language that is translated into a CPG. +Note that, in the following graphics we will often draw an EOG edge to an abstract child node of a language construct that is an AST subtree. +The EOG path through that subtree will depend on the node types of that tree and mostly start connecting one of the AST leaf nodes. + +## FunctionDeclaration +A function declaration is the start of an intraprocedural EOG and contains its end. Therefore there is no incoming or outgoing edge to `previous` or `next` eog nodes that are not in its AST subtree. The EOG connects the code body, as well as the default values of parameters if they exist. + +Interesting fields: + +* `parameters: List`: The parameters of the function. +* `defaultValue: Expression`: Optional default values of the parameters that have to be evaluated before executing the function's body. +* `body: Statement`: One or multiple statements executed when this function is called. + +Scheme: +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; + parent(["FunctionDeclaration"]) --EOG-->child1["defaultValue(i-1)"] + child1 --EOG-->child2["defaultValue(i)"] + child2 --EOG--> body + parent -.-> body + parent -."parameters(n)".->child3["parameter(i-1)"] -.->child1 + parent -."parameters(n)".->child4["parameter(i)"] -.->child2 +``` + +## StatementHolder +StatementHolder is an interface for any node that is not a function and contains code that should be connected with an EOG. The following classes implement this interface: `NamespaceDeclaration`, `TranslationUnitDeclaration`, `RecordDeclaration` and `CompoundStatement`. The Node implementing the interface is the start of one or multiple EOGs. Note that code inside such a holder can be static or non static (bound to an instance of a record). Therefore, two separate EOGs may be built. + +Interesting fields: + +* `statements: List`: The code inside a holder. The individual elements are distinguished by a property marking them as `staticBlock` if they are a `CompoundStatement`. + +Scheme: +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; + holder([StatementHolder])-."statements(n)".->sblock1["StaticStatement(i-1)"] + holder([StatementHolder])-."statements(n)".->sblock2["StaticStatement(i)"] + holder-."statements(n)".->nblock1["NonStaticStatement(i-1)"] + holder-."statements(n)".->nblock2["NonStaticStatement(i)"] + holder--EOG-->sblock1 + sblock1--EOG-->sblock2 + holder--EOG-->nblock1 + nblock1--EOG-->nblock2 +``` + +## VariableDeclaration +Represents the declaration of a local variable. + +Interesting fields: + +* `initializer: Expression`: The result of evaluation will initialize the variable. + +Scheme: +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; + prev:::outer --EOG--> child + parent(["VariableDeclaration"]) --EOG--> next:::outer + parent -.-> child["initializer"] + child --EOG--> parent + +``` + +## CallExpression +Represents any type of call in a program. + +Interesting fields: + +* `callee: Expression`: The expression declaring the target of a call. This can be a base in a `MemberCallExpression` or a function pointer in a `CallExpression`or a reference. +* `arguments: List`: Mapped to the parameters of the call target but evaluated before the call happens. + +Scheme: +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; + prev:::outer --EOG--> child["callee"] + parent(["CallExpression"]) --EOG--> next:::outer + child --EOG--> arg1["Argument(i-1)"] + arg1--EOG--> arg2["Argument(i)"] + arg2["Argument(i)"] --EOG--> parent + parent -.-> child + parent -."arguments(n)".-> arg1 + parent -."arguments(n)".-> arg2 +``` + +## MemberExpression +Access to the field in a `RecordDeclaration`. + +Interesting fields: + +* `base: Expression`: The base evaluated to determine whose field we want to access. + +Scheme: +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; + prev:::outer --EOG--> child + parent(["MemberExpression"]) --EOG--> next:::outer + parent -.-> child["base"] + child --EOG--> parent +``` + +## ArraySubscriptionExpression +Array access in the form of `arrayExpression[subscriptExpression]`. + +Interesting fields: + +* `arrayExpression: Expression`: The array to be accessed. +* `subscriptExpression: Expression`: The index in the array. + +Scheme: +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; + prev:::outer --EOG--> child + child --EOG--> child2["subscriptExpression"] + parent(["ArraySubscriptionExpression"]) --EOG--> next:::outer + parent -.-> child["arrayExpression"] + parent -.-> child2 + child2 --EOG--> parent +``` + +## ArrayCreationExpression +Interesting fields: + +* `dimensions: List`: Multiple expressions that define the array's dimensions. +* `initializer: Expression`: The expression for array initialization. + +Scheme: +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; + prev:::outer --EOG--> child1["dimension(i-1)"] + child1 --EOG--> child2["dimension(i)"] + child2 --EOG--> initializer + parent(["ArrayCreationExpression"]) --EOG--> next:::outer + parent -.-> child1 + parent -.-> child2 + parent -.-> initializer + initializer --EOG--> parent +``` + +## KeyValueExpression +Represents a key / value pair that could be used in associative arrays, among others. + +Interesting fields: + +* `key: Expression`: The key used for later accessing this pair. +* `value: Expression`: The value of the pair. + +Scheme: +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; + prev:::outer --EOG--> child + child --EOG--> child2["value"] + parent(["KeyValueExpression"]) --EOG--> next:::outer + parent -.-> child["key"] + parent -.-> child2 + child2 --EOG--> parent +``` + +## DeclarationStatement + +Here, the EOG is only drawn to the child component if that component is a VariableDeclaration, not if it is a FunctionDeclaration. + +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; + prev:::outer --EOG--> child + parent(["DeclarationStatement"]) --EOG--> next:::outer + parent -.-> child(["VariableDeclaration"]) + child --EOG--> parent + +``` +## ReturnStatement +This forms the end of an EOG as this is the last statement to be executed in the function. + +Scheme: +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; + prev:::outer --EOG--> child + child["returnValue"] --EOG--> parent(["ReturnStatement"]) + parent -.-> child +``` + +## BinaryOperator + +Interesting fields: + +* `lhs: Expression`: Left hand side of a binary operation. +* `rhs: Expression`: Right hand side of a binary operation. +* `operatorCode: String`: The operation. + +We differentiate between two cases based on the `operatorCode`. + +### Short-circuit evaluation + +The operations `&&` and `||` have a short-circuit evaluation. This means that the expression can terminate early if the `lhs` is false (for `&&`) or `true` (for `||`). This affects the EOG by adding an EOG edge from `lhs` to the BinaryOperator. + +Scheme: +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; + prev:::outer --EOG--> lhs + node --EOG--> next:::outer + node([BinaryOperator]) -.-> lhs + node -.-> rhs + lhs --EOG--> rhs + lhs --EOG--> node + rhs --EOG--> node +``` +### Default case + +For the other binary operations like `+`, `-` but also assignments `=` and `+=` we follow the left before right order. The `lhs` is evaluated before the `rhs` as we assume left to right evaluation. + +Scheme: +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; + prev:::outer --EOG--> lhs + node --EOG--> next:::outer + node([BinaryOperator]) -.-> lhs + node -.-> rhs + lhs --EOG--> rhs + rhs --EOG--> node +``` + + +## CompoundStatement + +Represents an explicit block of statements. + +Interesting fields: + +* `statements:List`: Statements in a block of code that are evaluated sequentially. + +Scheme: +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; + prev:::outer --EOG--> child1["statement(i-1)"] + child1 --EOG-->child2["statement(i)"] + parent(["CompoundStatement"]) --EOG--> next:::outer + parent -."statements(n)".-> child1 + parent -."statements(n)".-> child2 + child2 --EOG--> parent + +``` + +## UnaryOperator +For unary operations like `!` but also writing operations: `++` and `--`. + +Interesting fields: + +* `input:Expression`: Wrapped by the unary operation. + +Scheme: +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; + prev:::outer --EOG--> child["input"] + child --EOG-->parent + parent(["UnaryOperator"]) --EOG--> next:::outer + parent -."statements(n)".-> child + +``` + + +### UnaryOperator for exception throws +Throwing of exceptions is modelled as unary operation. The EOG continues at an exception catching structure or a function that does a re-throw. + +Interesting fields: +* `input: Expression`: Exception to be thrown for exception handling. + +Scheme: +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; + prev:::outer --EOG--> child["input"] + child --EOG-->parent + parent(["throw"]) --EOG--> catchingContext:::outer + parent -."statements(n)".-> child + +``` + + +## AssertStatement +Statement that evaluates a condition and if the condition is false, evaluates a message, this message is generalized to a `Statement` to hold everything +from a single String, to an Exception construction. + +Interesting fields: + +* `condition: Expression` Its evaluation leads to evaluation of message and EOG termination or to the regular evaluation of the parent `AssertStatement`. +* `message: Statement`: A String message or Exception evaluated only if the assertion fails. + +Scheme: +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; + prev:::outer --EOG--> child1["condition"] + child1 --"EOG:false"-->child2["message"] + child1 --"EOG:true"-->parent + parent([AssertStatement]) --EOG--> next:::outer + parent -.-> child1 + parent -.-> child2 + +``` + +## TryStatement + +After the execution of the statement the control flow only proceeds with the next statement if all exceptions were handled. If not, execution is relayed to the next outer exception handling context. + +Interesting fields: + +* `resources:List`: Initialization of values needed in the block or special objects needing cleanup. +* `tryBlock:CompoundStatement`: The code that should be tried, exceptions inside lead to an eog edge to the catch clauses. +* `finallyBlock:CompoundStatement`: All EOG paths inside the `tryBlock` or the `catch` blocks will finally reach this block and evaluate it. +* `catchBlocks:List`: Children of `CatchClause` (omitted here), evaluated when the exception matches the clauses condition. + +Scheme: +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; + prev:::outer -- EOG --> resource["resource(i-1)"]; + resource -.- parent + resource -- EOG --> resourceI["resource(i)"] + resourceI -.- parent + resourceI -- EOG --> try["tryBlock"] + try -.- parent + throws:::outer -- EOG --> catch["catchBlock(i)"] + try -- EOG --> finally["finallyBlock"] + parent([TryStatement]) --EOG--> next:::outer + parent --EOG--> catchingContext:::outer + catch -- EOG --> finally + finally -- EOG --> parent + finally -.- parent + catch -.- parent +``` + +## ContinueStatement +The execution continues at the `condition` of a node associated to a `Continuable` scope, e.g. `WhileStatement`. This is not necessarily the closest enclosing node of this type, the `ContinueStatement` may contain a label specifying the exact outer node. + +Scheme: +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; + prev:::outer --EOG--> parent + parent(["ContinueStatement"]) --EOG--> conditionInContinuableContext:::outer + +``` +## BreakStatement +The execution continues after a node associated to a `Breakable` scope, e.g. `WhileStatement`or `SwitchStatement`. This is not necessarily the closest enclosing node of this type, the `BreakStatement` may contain a label specifying the exact outer node. + +Scheme: +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; + prev:::outer --EOG--> parent + parent(["BreakStatement"]) --EOG--> nextAfterBreakableContext:::outer +``` + +## DeleteExpression +Deletion of a specific object freeing memory or calling the destructor. + +Interesting fields: + +* `operand: Expression`: The result of the evaluation is the object to be deleted. + +Scheme: +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; + prev:::outer --EOG--> child["operand"] + child --EOG--> parent + parent(["DeleteExpression"]) --EOG--> next:::outer + parent -.-> child +``` + +## LabelStatement +The `LabelStatement` itself is not added to the EOG. EOG construction is directly forwarded to the labeled statement in the `subStatement`. + +Scheme: +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; + prev:::outer --EOG--> child["subStatement"] + child --EOG--> next:::outer + parent(["LabelStatement"]) -.-> child +``` + +## GotoStatement +Models a `goto`statement and an EOG-Edge is created to the appropriate `LabelStatement`. + +Scheme: +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; + prev:::outer --EOG--> child(["GotoStatement"]) + child --EOG--> labeledStatement:::outer +``` + +## NewExpression +Creates a new object, which is either an array or an instantiation of a `RecordDeclaration`. The initializer has to be evaluated to create the object. + +Interesting fields: + +* `initializer: Expression`: To be evaluated before creating a new object. + +* Scheme: +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; + prev:::outer --EOG--> child["initializer"] + child --EOG--> parent + parent(["NewExpression"]) --EOG--> next:::outer + parent -.-> child +``` + +## CastExpression +Interesting fields: + +* `expression: Expression`: An expression of a specific compile time type, cast to a specified other compile time type. + +Scheme: +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; + prev:::outer --EOG--> child["expression"] + child --EOG--> parent + parent(["CastExpression"]) --EOG--> next:::outer + parent -.-> child +``` + +## ExpressionList +List of several expressions that aer evaluated sequentially. The resulting value is the last evaluated expression. + +Interesting fields: + +* `expressions: List`: Several expressions in sequential order. + +Scheme: +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; + prev:::outer --EOG--> child1["expression(i-1)"] + child1 --EOG--> child2["expression(i)"] + child2 --EOG--> parent + parent(["ExpressionList"]) --EOG--> next:::outer + parent -."expressions(n)".-> child1 + parent -."expressions(n)".-> child2 +``` + +## InitializerListExpression +This expression initializes multiple variables or an object of multiple elements, e.g. arrays, lists. + +Scheme: +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; + prev:::outer --EOG--> child1["initializer(i-1)"] + child1 --EOG--> child2["initializer(i)"] + child2 --EOG--> parent + parent(["InitializerListExpression"]) --EOG--> next:::outer + parent -."initializers(n)".-> child1 + parent -."initializers(n)".-> child2 +``` + +## ConstructExpression +A ConstructExpression creates an object. + +Interesting fields: + +* `arguments: List`: Arguments to the construction, e.g. arguments for a call to a constructor. + +Scheme: +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; + prev:::outer --EOG--> child1["argument(i-1)"] + child1 --EOG--> child2["argument(i)"] + child2 --EOG--> parent + parent(["ConstructExpression"]) --EOG--> next:::outer + parent -."arguments(n)".-> child1 + parent -."arguments(n)".-> child2 +``` + +## SynchronizedStatement +The placement of the root node between expression and executed block is such that algorithms can be evaluated the expression and then encountering the information that this expression is used for synchronization. + +Interesting fields: + +* `expression: Expression`: Its evaluation returns an object that acts as a lock for synchronization. +* `blockStatement: CompoundStatement`: Code executed while the object evaluated from `expression` is locked. + +Scheme: +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; + prev:::outer --EOG--> child1["expression"] + child1 --EOG--> parent + parent --EOG--> child2["blockStatement"] + child2 --EOG--> next:::outer + parent -.-> child1 + parent -.-> child2 +``` + +## ConditionalExpression +A conditional evaluation of two expression, realizing the branching pattern of an `IfStatement` on the expression level. + +Interesting fields: + +* `condition:Expression`: Executed first to decide the branch of evaluation. +* `thenExpr:Expression`: Evaluated if `condition` evaluates to `true.` +* `elseExpr:Expression`: Evaluated if `condition` evaluates to `false.` + +Scheme: +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; + prev:::outer --EOG--> child1["condition"] + child1 --EOG--> parent(["ConditionalExpression"]) + parent --EOG:true--> child2["thenExpr"] + parent --EOG:false--> child3["elseExpr"] + child2 --EOG--> next:::outer + child3 --EOG--> next:::outer + parent -.-> child1 + parent -.-> child2 + parent -.-> child3 +``` + +## WhileStatement +This is a classic while loop where the condition is evaluated before every loop iteration. + +Note: The condition may be enclosed in a declaration, in that case the EOG will not contain a `condition` but rather a declaration of a variable where the `initializer` serves as loop condition. Uses of one or the other are currently mutually exclusive. + +Interesting fields: + +* `condition: Expression`: The condition for the loop. +* `conditionDeclaration: Declaration`: The declaration of a variable with condition as initializer. +* `statement: Statement`: The body of the loop to be iterated over. + +Scheme: +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; + prev:::outer --EOG--> child1["condition|conditionDeclaration"] + child1 --EOG--> parent + parent --EOG:false--> next:::outer + parent(["WhileStatement"]) --EOG:true--> child3["statement"] + child3 --EOG--> child1 + parent -.-> child1 + parent -.-> child3 +``` + +## DoStatement +This is a classic do while loop where the condition is evaluated after every loop iteration. + +Interesting fields: + +* `condition: Expression`: The condition of the loop. +* `statement: Statement`: The body of the loop to be iterated over. + +Scheme: +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; + prev:::outer --EOG--> child1["statement"]; + child1 --EOG--> child2["condition"]; + child2 --EOG--> parent(["DoStatement"]); + parent --EOG:false--> next:::outer + parent --EOG:true--> child1 + parent -.-> child1 + parent -.-> child2 +``` + +## ForEachStatement +This is a loop that iterates over all elements in a multi-element `iterable` with the single elements bound to the declaration of `variable` while evaluating `statement`. + +Interesting fields: + +* `iterable: Statement`: Elements of this iterable will trigger a loop iteration. +* `variable: Statement`: Variable declaring Statement that binds elements to a name. +* `statement: Statement`: Loop body to be iterated over. + +Scheme: +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; + prev:::outer --EOG--> child1["iterable"] + child1 --EOG--> child2["variable"] + child2 --EOG--> parent + parent --EOG:false--> next:::outer + parent(["ForEachStatement"]) --EOG:true--> child3["statement"] + child3 --EOG--> child1 + parent -.-> child2 + parent -.-> child1 + parent -.-> child3 +``` + +## ForStatement +This is a classic for-loop where a statement is executed before the loop run, a condition is evaluated before every loop iteration, and a post iteration statement can be declared. + +Note: The condition may be enclosed in a declaration. In this case, the EOG will not contain a `condition` but rather a declaration of a variable where the `initializer` serves as loop condition. Uses of one or the other are currently mutually exclusive. + +Interesting fields: + +* `initializerStatement:Statement`: Statement run once, before the loop starts. +* `condition: Expression`: The condition of the loop. +* `conditionDeclaration: Declaration`: The declaration of a variable with the condition as initializer. +* `statement: Statement`: The body of the loop to be iterated over. +* `iterationStatement: Statement`: The statement to be executed after each loop iteration. + +Scheme: +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; + iteration --EOG--> condition + statement --EOG--> iteration["iterationStatement"] + prev:::outer --EOG--> initializer["initializerStatement"] + parent --EOG:false--> next:::outer + initializer --EOG--> condition["condition|conditionDeclaration"] + condition --EOG--> parent + parent(["ForStatement"]) --EOG:true--> statement["statement"] +``` + +## IfStatement +This is a branching statement where the evaluation of a `condition` leads to the execution of one optional, or two mutually exclusive blocks of code. + +Note: The condition may be enclosed in a declaration, in that case the EOG will not contain a `condition` but rather a declaration of a variable where the `initializer` serves as branching condition. Uses of one or the other are currently mutually exclusive. + +Interesting fields: + +* `condition: Expression`: The condition of the branching decision. +* `conditionDeclaration: Declaration`: The declaration of a variable with condition as initializer. +* `thenStatement: Statement`: The body of the mandatory block that is evaluated if the `condition` evaluates to `true`. +* `elseStatement: Statement`: The body of an optional block that is evaluated if the `condition` evaluates to `false`. + +Scheme: +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; + prev:::outer --EOG--> child1["initializerStatement"] + child1 --EOG--> child2["condition|conditionDeclaration"] + child2 --EOG--> parent + parent(["IfStatement"]) --EOG:true--> child4["thenStatement"] + parent --EOG:false--> child5["elseStatement"] + parent --EOG--> next:::outer + child4 --EOG--> next:::outer + child5 --EOG--> next:::outer +``` + +## SwitchStatement +This is a switch statement where the evaluation of a `selector` decides the entry point in a large block of code. `CaseStatements` serve as entry points and `BreakStatements` are needed to prevent all cases after the entry to be evaluated. + +Note: The `selector` may be enclosed in a declaration. In this case, the EOG will not contain a selector but rather a declaration of a variable where the `initializer` serves as switch selector. Uses of one or the other are currently mutually exclusive. + +Interesting fields: + +* `selector: Expression`: The evaluated selector which needs to match the expression evaluation of the expression in a `caseStatement` or the entry will be the `defaultStatement`. +* `selectorDeclaration: Declaration`: The declarations `initializer` serves as `selector`. +* `statement: Statement`: The body containing all entry points and statements to be executed. +* `caseStatement: Statement`: The entry point into the evaluation of the switch body if the `selector` matches its `caseExpression`. +* `defaultStatement: Statement`: The default entry point if no `caseExpression` matched the selector. + +Scheme: +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; + prev:::outer --EOG--> child1["initializerStatement"] + child1 --EOG--> child2["selector|selectorDeclaration"] + child2 --EOG--> parent + parent(["SwitchStatement"]) --EOG--> child4["caseStatement"] + parent --EOG--> child5["defaultStatement"] + child7["statement(n-1)"] --EOG--> child6["statement"] + parent -.->child6 + child6 -."statements(n)".-> child4 + child6 -."statements(n)".-> child5 + child6 -."statements(n)".-> child7 + child6 --EOG--> next:::outer +``` + +## CaseStatement + +Serves as an entry point inside a `SwitchStatement`, the statements executed after entry are not children of this structure but can be found on the same AST hierarchy level. + +Interesting fields: + +* `caseExpression: Expression`: serves as an entry point if its evaluation matches the `selector` evaluation in `SwitchStatement` + +Scheme: +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; + prev:::outer --EOG--> child["caseExpression"] + child --EOG--> parent + parent(["CaseStatement"]) --EOG--> next:::outer + parent -.-> child + +``` +## LambdaExpression +The expression itself is connected to the outer EOG. A separate EOG is built for the expressed code, as the code itself is not executed at this point. + +Interesting fields: + +* `function: FunctionDeclaration`: The function declared by the lambda that can be executed at different points in the program. + +Scheme: +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; + prev:::outer --EOG--> parent["LambdaExpression"] + parent --EOG--> next:::outer + parent -.-> child + child(["function"]) --EOG-->internalNext:::outer + +``` + + + + + + diff --git a/docs/docs/CPG/specs/graph.md b/docs/docs/CPG/specs/graph.md new file mode 100644 index 0000000000..d4dcc81ecf --- /dev/null +++ b/docs/docs/CPG/specs/graph.md @@ -0,0 +1,3328 @@ + + +# CPG Schema +This file shows all node labels and relationships between them that are persisted from the in memory CPG to the Neo4j database. The specification is generated automatically and always up to date. +## Node +### Children +[Statement](#estatement) +[Declaration](#edeclaration) +[Type](#etype) +[AnnotationMember](#eannotationmember) +[Component](#ecomponent) +[Annotation](#eannotation) + +### Relationships +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +#### DFG +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +Node--"DFG*"-->NodeDFG[Node]:::outer +``` +#### EOG +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +Node--"EOG*"-->NodeEOG[Node]:::outer +``` +#### ANNOTATIONS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +Node--"ANNOTATIONS*"-->NodeANNOTATIONS[Annotation]:::outer +``` +#### AST +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +Node--"AST*"-->NodeAST[Node]:::outer +``` +#### SCOPE +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +Node--"SCOPE¹"-->NodeSCOPE[Node]:::outer +``` +#### TYPEDEFS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +Node--"TYPEDEFS*"-->NodeTYPEDEFS[TypedefDeclaration]:::outer +``` +## Statement +**Labels**:[Node](#enode) +[Statement](#estatement) + +### Children +[AssertStatement](#eassertstatement) +[DoStatement](#edostatement) +[CaseStatement](#ecasestatement) +[ReturnStatement](#ereturnstatement) +[Expression](#eexpression) +[IfStatement](#eifstatement) +[DeclarationStatement](#edeclarationstatement) +[ForStatement](#eforstatement) +[CatchClause](#ecatchclause) +[SwitchStatement](#eswitchstatement) +[GotoStatement](#egotostatement) +[WhileStatement](#ewhilestatement) +[CompoundStatement](#ecompoundstatement) +[ContinueStatement](#econtinuestatement) +[DefaultStatement](#edefaultstatement) +[SynchronizedStatement](#esynchronizedstatement) +[TryStatement](#etrystatement) +[ForEachStatement](#eforeachstatement) +[LabelStatement](#elabelstatement) +[BreakStatement](#ebreakstatement) +[EmptyStatement](#eemptystatement) + +### Relationships +[LOCALS](#StatementLOCALS) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +#### LOCALS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +Statement--"LOCALS*"-->StatementLOCALS[VariableDeclaration]:::outer +``` +## AssertStatement +**Labels**:[Node](#enode) +[Statement](#estatement) +[AssertStatement](#eassertstatement) + +### Relationships +[CONDITION](#AssertStatementCONDITION) + +[MESSAGE](#AssertStatementMESSAGE) + +[LOCALS](#StatementLOCALS) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +#### CONDITION +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +AssertStatement--"CONDITION¹"-->AssertStatementCONDITION[Expression]:::outer +``` +#### MESSAGE +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +AssertStatement--"MESSAGE¹"-->AssertStatementMESSAGE[Statement]:::outer +``` +## DoStatement +**Labels**:[Node](#enode) +[Statement](#estatement) +[DoStatement](#edostatement) + +### Relationships +[CONDITION](#DoStatementCONDITION) + +[STATEMENT](#DoStatementSTATEMENT) + +[LOCALS](#StatementLOCALS) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +#### CONDITION +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +DoStatement--"CONDITION¹"-->DoStatementCONDITION[Expression]:::outer +``` +#### STATEMENT +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +DoStatement--"STATEMENT¹"-->DoStatementSTATEMENT[Statement]:::outer +``` +## CaseStatement +**Labels**:[Node](#enode) +[Statement](#estatement) +[CaseStatement](#ecasestatement) + +### Relationships +[CASE_EXPRESSION](#CaseStatementCASE_EXPRESSION) + +[LOCALS](#StatementLOCALS) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +#### CASE_EXPRESSION +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +CaseStatement--"CASE_EXPRESSION¹"-->CaseStatementCASE_EXPRESSION[Expression]:::outer +``` +## ReturnStatement +**Labels**:[Node](#enode) +[Statement](#estatement) +[ReturnStatement](#ereturnstatement) + +### Relationships +[RETURN_VALUES](#ReturnStatementRETURN_VALUES) + +[LOCALS](#StatementLOCALS) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +#### RETURN_VALUES +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +ReturnStatement--"RETURN_VALUES*"-->ReturnStatementRETURN_VALUES[Expression]:::outer +``` +## Expression +**Labels**:[Node](#enode) +[Statement](#estatement) +[Expression](#eexpression) + +### Children +[NewExpression](#enewexpression) +[LambdaExpression](#elambdaexpression) +[UnaryOperator](#eunaryoperator) +[ArrayRangeExpression](#earrayrangeexpression) +[CallExpression](#ecallexpression) +[DesignatedInitializerExpression](#edesignatedinitializerexpression) +[KeyValueExpression](#ekeyvalueexpression) +[AssignExpression](#eassignexpression) +[CastExpression](#ecastexpression) +[ArrayCreationExpression](#earraycreationexpression) +[ArraySubscriptionExpression](#earraysubscriptionexpression) +[TypeExpression](#etypeexpression) +[BinaryOperator](#ebinaryoperator) +[ConditionalExpression](#econditionalexpression) +[DeclaredReferenceExpression](#edeclaredreferenceexpression) +[InitializerListExpression](#einitializerlistexpression) +[DeleteExpression](#edeleteexpression) +[CompoundStatementExpression](#ecompoundstatementexpression) +[ProblemExpression](#eproblemexpression) +[Literal](#eliteral) +[TypeIdExpression](#etypeidexpression) +[ExpressionList](#eexpressionlist) + +### Relationships +[POSSIBLE_SUB_TYPES](#ExpressionPOSSIBLE_SUB_TYPES) + +[TYPE](#ExpressionTYPE) + +[LOCALS](#StatementLOCALS) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +#### POSSIBLE_SUB_TYPES +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +Expression--"POSSIBLE_SUB_TYPES*"-->ExpressionPOSSIBLE_SUB_TYPES[Type]:::outer +``` +#### TYPE +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +Expression--"TYPE¹"-->ExpressionTYPE[Type]:::outer +``` +## NewExpression +**Labels**:[Node](#enode) +[Statement](#estatement) +[Expression](#eexpression) +[NewExpression](#enewexpression) + +### Relationships +[INITIALIZER](#NewExpressionINITIALIZER) + +[TEMPLATE_PARAMETERS](#NewExpressionTEMPLATE_PARAMETERS) + +[POSSIBLE_SUB_TYPES](#ExpressionPOSSIBLE_SUB_TYPES) + +[TYPE](#ExpressionTYPE) + +[LOCALS](#StatementLOCALS) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +#### INITIALIZER +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +NewExpression--"INITIALIZER¹"-->NewExpressionINITIALIZER[Expression]:::outer +``` +#### TEMPLATE_PARAMETERS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +NewExpression--"TEMPLATE_PARAMETERS*"-->NewExpressionTEMPLATE_PARAMETERS[Node]:::outer +``` +## LambdaExpression +**Labels**:[Node](#enode) +[Statement](#estatement) +[Expression](#eexpression) +[LambdaExpression](#elambdaexpression) + +### Relationships +[MUTABLE_VARIABLES](#LambdaExpressionMUTABLE_VARIABLES) + +[FUNCTION](#LambdaExpressionFUNCTION) + +[POSSIBLE_SUB_TYPES](#ExpressionPOSSIBLE_SUB_TYPES) + +[TYPE](#ExpressionTYPE) + +[LOCALS](#StatementLOCALS) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +#### MUTABLE_VARIABLES +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +LambdaExpression--"MUTABLE_VARIABLES*"-->LambdaExpressionMUTABLE_VARIABLES[ValueDeclaration]:::outer +``` +#### FUNCTION +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +LambdaExpression--"FUNCTION¹"-->LambdaExpressionFUNCTION[FunctionDeclaration]:::outer +``` +## UnaryOperator +**Labels**:[Node](#enode) +[Statement](#estatement) +[Expression](#eexpression) +[UnaryOperator](#eunaryoperator) + +### Relationships +[INPUT](#UnaryOperatorINPUT) + +[POSSIBLE_SUB_TYPES](#ExpressionPOSSIBLE_SUB_TYPES) + +[TYPE](#ExpressionTYPE) + +[LOCALS](#StatementLOCALS) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +#### INPUT +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +UnaryOperator--"INPUT¹"-->UnaryOperatorINPUT[Expression]:::outer +``` +## ArrayRangeExpression +**Labels**:[Node](#enode) +[Statement](#estatement) +[Expression](#eexpression) +[ArrayRangeExpression](#earrayrangeexpression) + +### Relationships +[CEILING](#ArrayRangeExpressionCEILING) + +[STEP](#ArrayRangeExpressionSTEP) + +[FLOOR](#ArrayRangeExpressionFLOOR) + +[POSSIBLE_SUB_TYPES](#ExpressionPOSSIBLE_SUB_TYPES) + +[TYPE](#ExpressionTYPE) + +[LOCALS](#StatementLOCALS) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +#### CEILING +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +ArrayRangeExpression--"CEILING¹"-->ArrayRangeExpressionCEILING[Expression]:::outer +``` +#### STEP +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +ArrayRangeExpression--"STEP¹"-->ArrayRangeExpressionSTEP[Expression]:::outer +``` +#### FLOOR +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +ArrayRangeExpression--"FLOOR¹"-->ArrayRangeExpressionFLOOR[Expression]:::outer +``` +## CallExpression +**Labels**:[Node](#enode) +[Statement](#estatement) +[Expression](#eexpression) +[CallExpression](#ecallexpression) + +### Children +[ExplicitConstructorInvocation](#eexplicitconstructorinvocation) +[ConstructExpression](#econstructexpression) +[MemberCallExpression](#emembercallexpression) + +### Relationships +[CALLEE](#CallExpressionCALLEE) + +[INVOKES](#CallExpressionINVOKES) + +[TEMPLATE_INSTANTIATION](#CallExpressionTEMPLATE_INSTANTIATION) + +[ARGUMENTS](#CallExpressionARGUMENTS) + +[TEMPLATE_PARAMETERS](#CallExpressionTEMPLATE_PARAMETERS) + +[POSSIBLE_SUB_TYPES](#ExpressionPOSSIBLE_SUB_TYPES) + +[TYPE](#ExpressionTYPE) + +[LOCALS](#StatementLOCALS) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +#### CALLEE +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +CallExpression--"CALLEE¹"-->CallExpressionCALLEE[Expression]:::outer +``` +#### INVOKES +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +CallExpression--"INVOKES*"-->CallExpressionINVOKES[FunctionDeclaration]:::outer +``` +#### TEMPLATE_INSTANTIATION +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +CallExpression--"TEMPLATE_INSTANTIATION¹"-->CallExpressionTEMPLATE_INSTANTIATION[TemplateDeclaration]:::outer +``` +#### ARGUMENTS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +CallExpression--"ARGUMENTS*"-->CallExpressionARGUMENTS[Expression]:::outer +``` +#### TEMPLATE_PARAMETERS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +CallExpression--"TEMPLATE_PARAMETERS*"-->CallExpressionTEMPLATE_PARAMETERS[Node]:::outer +``` +## ExplicitConstructorInvocation +**Labels**:[Node](#enode) +[Statement](#estatement) +[Expression](#eexpression) +[CallExpression](#ecallexpression) +[ExplicitConstructorInvocation](#eexplicitconstructorinvocation) + +### Relationships +[CALLEE](#CallExpressionCALLEE) + +[INVOKES](#CallExpressionINVOKES) + +[TEMPLATE_INSTANTIATION](#CallExpressionTEMPLATE_INSTANTIATION) + +[ARGUMENTS](#CallExpressionARGUMENTS) + +[TEMPLATE_PARAMETERS](#CallExpressionTEMPLATE_PARAMETERS) + +[POSSIBLE_SUB_TYPES](#ExpressionPOSSIBLE_SUB_TYPES) + +[TYPE](#ExpressionTYPE) + +[LOCALS](#StatementLOCALS) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +## ConstructExpression +**Labels**:[Node](#enode) +[Statement](#estatement) +[Expression](#eexpression) +[CallExpression](#ecallexpression) +[ConstructExpression](#econstructexpression) + +### Relationships +[INSTANTIATES](#ConstructExpressionINSTANTIATES) + +[CONSTRUCTOR](#ConstructExpressionCONSTRUCTOR) + +[ANOYMOUS_CLASS](#ConstructExpressionANOYMOUS_CLASS) + +[CALLEE](#CallExpressionCALLEE) + +[INVOKES](#CallExpressionINVOKES) + +[TEMPLATE_INSTANTIATION](#CallExpressionTEMPLATE_INSTANTIATION) + +[ARGUMENTS](#CallExpressionARGUMENTS) + +[TEMPLATE_PARAMETERS](#CallExpressionTEMPLATE_PARAMETERS) + +[POSSIBLE_SUB_TYPES](#ExpressionPOSSIBLE_SUB_TYPES) + +[TYPE](#ExpressionTYPE) + +[LOCALS](#StatementLOCALS) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +#### INSTANTIATES +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +ConstructExpression--"INSTANTIATES¹"-->ConstructExpressionINSTANTIATES[Declaration]:::outer +``` +#### CONSTRUCTOR +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +ConstructExpression--"CONSTRUCTOR¹"-->ConstructExpressionCONSTRUCTOR[ConstructorDeclaration]:::outer +``` +#### ANOYMOUS_CLASS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +ConstructExpression--"ANOYMOUS_CLASS¹"-->ConstructExpressionANOYMOUS_CLASS[RecordDeclaration]:::outer +``` +## MemberCallExpression +**Labels**:[Node](#enode) +[Statement](#estatement) +[Expression](#eexpression) +[CallExpression](#ecallexpression) +[MemberCallExpression](#emembercallexpression) + +### Relationships +[CALLEE](#CallExpressionCALLEE) + +[INVOKES](#CallExpressionINVOKES) + +[TEMPLATE_INSTANTIATION](#CallExpressionTEMPLATE_INSTANTIATION) + +[ARGUMENTS](#CallExpressionARGUMENTS) + +[TEMPLATE_PARAMETERS](#CallExpressionTEMPLATE_PARAMETERS) + +[POSSIBLE_SUB_TYPES](#ExpressionPOSSIBLE_SUB_TYPES) + +[TYPE](#ExpressionTYPE) + +[LOCALS](#StatementLOCALS) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +## DesignatedInitializerExpression +**Labels**:[Node](#enode) +[Statement](#estatement) +[Expression](#eexpression) +[DesignatedInitializerExpression](#edesignatedinitializerexpression) + +### Relationships +[LHS](#DesignatedInitializerExpressionLHS) + +[RHS](#DesignatedInitializerExpressionRHS) + +[POSSIBLE_SUB_TYPES](#ExpressionPOSSIBLE_SUB_TYPES) + +[TYPE](#ExpressionTYPE) + +[LOCALS](#StatementLOCALS) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +#### LHS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +DesignatedInitializerExpression--"LHS*"-->DesignatedInitializerExpressionLHS[Expression]:::outer +``` +#### RHS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +DesignatedInitializerExpression--"RHS¹"-->DesignatedInitializerExpressionRHS[Expression]:::outer +``` +## KeyValueExpression +**Labels**:[Node](#enode) +[Statement](#estatement) +[Expression](#eexpression) +[KeyValueExpression](#ekeyvalueexpression) + +### Relationships +[VALUE](#KeyValueExpressionVALUE) + +[KEY](#KeyValueExpressionKEY) + +[POSSIBLE_SUB_TYPES](#ExpressionPOSSIBLE_SUB_TYPES) + +[TYPE](#ExpressionTYPE) + +[LOCALS](#StatementLOCALS) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +#### VALUE +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +KeyValueExpression--"VALUE¹"-->KeyValueExpressionVALUE[Expression]:::outer +``` +#### KEY +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +KeyValueExpression--"KEY¹"-->KeyValueExpressionKEY[Expression]:::outer +``` +## AssignExpression +**Labels**:[Node](#enode) +[Statement](#estatement) +[Expression](#eexpression) +[AssignExpression](#eassignexpression) + +### Relationships +[DECLARATIONS](#AssignExpressionDECLARATIONS) + +[LHS](#AssignExpressionLHS) + +[RHS](#AssignExpressionRHS) + +[POSSIBLE_SUB_TYPES](#ExpressionPOSSIBLE_SUB_TYPES) + +[TYPE](#ExpressionTYPE) + +[LOCALS](#StatementLOCALS) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +#### DECLARATIONS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +AssignExpression--"DECLARATIONS*"-->AssignExpressionDECLARATIONS[VariableDeclaration]:::outer +``` +#### LHS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +AssignExpression--"LHS*"-->AssignExpressionLHS[Expression]:::outer +``` +#### RHS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +AssignExpression--"RHS*"-->AssignExpressionRHS[Expression]:::outer +``` +## CastExpression +**Labels**:[Node](#enode) +[Statement](#estatement) +[Expression](#eexpression) +[CastExpression](#ecastexpression) + +### Relationships +[CAST_TYPE](#CastExpressionCAST_TYPE) + +[EXPRESSION](#CastExpressionEXPRESSION) + +[POSSIBLE_SUB_TYPES](#ExpressionPOSSIBLE_SUB_TYPES) + +[TYPE](#ExpressionTYPE) + +[LOCALS](#StatementLOCALS) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +#### CAST_TYPE +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +CastExpression--"CAST_TYPE¹"-->CastExpressionCAST_TYPE[Type]:::outer +``` +#### EXPRESSION +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +CastExpression--"EXPRESSION¹"-->CastExpressionEXPRESSION[Expression]:::outer +``` +## ArrayCreationExpression +**Labels**:[Node](#enode) +[Statement](#estatement) +[Expression](#eexpression) +[ArrayCreationExpression](#earraycreationexpression) + +### Relationships +[INITIALIZER](#ArrayCreationExpressionINITIALIZER) + +[DIMENSIONS](#ArrayCreationExpressionDIMENSIONS) + +[POSSIBLE_SUB_TYPES](#ExpressionPOSSIBLE_SUB_TYPES) + +[TYPE](#ExpressionTYPE) + +[LOCALS](#StatementLOCALS) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +#### INITIALIZER +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +ArrayCreationExpression--"INITIALIZER¹"-->ArrayCreationExpressionINITIALIZER[Expression]:::outer +``` +#### DIMENSIONS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +ArrayCreationExpression--"DIMENSIONS*"-->ArrayCreationExpressionDIMENSIONS[Expression]:::outer +``` +## ArraySubscriptionExpression +**Labels**:[Node](#enode) +[Statement](#estatement) +[Expression](#eexpression) +[ArraySubscriptionExpression](#earraysubscriptionexpression) + +### Relationships +[ARRAY_EXPRESSION](#ArraySubscriptionExpressionARRAY_EXPRESSION) + +[SUBSCRIPT_EXPRESSION](#ArraySubscriptionExpressionSUBSCRIPT_EXPRESSION) + +[POSSIBLE_SUB_TYPES](#ExpressionPOSSIBLE_SUB_TYPES) + +[TYPE](#ExpressionTYPE) + +[LOCALS](#StatementLOCALS) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +#### ARRAY_EXPRESSION +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +ArraySubscriptionExpression--"ARRAY_EXPRESSION¹"-->ArraySubscriptionExpressionARRAY_EXPRESSION[Expression]:::outer +``` +#### SUBSCRIPT_EXPRESSION +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +ArraySubscriptionExpression--"SUBSCRIPT_EXPRESSION¹"-->ArraySubscriptionExpressionSUBSCRIPT_EXPRESSION[Expression]:::outer +``` +## TypeExpression +**Labels**:[Node](#enode) +[Statement](#estatement) +[Expression](#eexpression) +[TypeExpression](#etypeexpression) + +### Relationships +[POSSIBLE_SUB_TYPES](#ExpressionPOSSIBLE_SUB_TYPES) + +[TYPE](#ExpressionTYPE) + +[LOCALS](#StatementLOCALS) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +## BinaryOperator +**Labels**:[Node](#enode) +[Statement](#estatement) +[Expression](#eexpression) +[BinaryOperator](#ebinaryoperator) + +### Relationships +[LHS](#BinaryOperatorLHS) + +[RHS](#BinaryOperatorRHS) + +[POSSIBLE_SUB_TYPES](#ExpressionPOSSIBLE_SUB_TYPES) + +[TYPE](#ExpressionTYPE) + +[LOCALS](#StatementLOCALS) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +#### LHS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +BinaryOperator--"LHS¹"-->BinaryOperatorLHS[Expression]:::outer +``` +#### RHS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +BinaryOperator--"RHS¹"-->BinaryOperatorRHS[Expression]:::outer +``` +## ConditionalExpression +**Labels**:[Node](#enode) +[Statement](#estatement) +[Expression](#eexpression) +[ConditionalExpression](#econditionalexpression) + +### Relationships +[ELSE_EXPR](#ConditionalExpressionELSE_EXPR) + +[THEN_EXPR](#ConditionalExpressionTHEN_EXPR) + +[CONDITION](#ConditionalExpressionCONDITION) + +[POSSIBLE_SUB_TYPES](#ExpressionPOSSIBLE_SUB_TYPES) + +[TYPE](#ExpressionTYPE) + +[LOCALS](#StatementLOCALS) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +#### ELSE_EXPR +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +ConditionalExpression--"ELSE_EXPR¹"-->ConditionalExpressionELSE_EXPR[Expression]:::outer +``` +#### THEN_EXPR +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +ConditionalExpression--"THEN_EXPR¹"-->ConditionalExpressionTHEN_EXPR[Expression]:::outer +``` +#### CONDITION +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +ConditionalExpression--"CONDITION¹"-->ConditionalExpressionCONDITION[Expression]:::outer +``` +## DeclaredReferenceExpression +**Labels**:[Node](#enode) +[Statement](#estatement) +[Expression](#eexpression) +[DeclaredReferenceExpression](#edeclaredreferenceexpression) + +### Children +[MemberExpression](#ememberexpression) + +### Relationships +[REFERS_TO](#DeclaredReferenceExpressionREFERS_TO) + +[POSSIBLE_SUB_TYPES](#ExpressionPOSSIBLE_SUB_TYPES) + +[TYPE](#ExpressionTYPE) + +[LOCALS](#StatementLOCALS) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +#### REFERS_TO +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +DeclaredReferenceExpression--"REFERS_TO¹"-->DeclaredReferenceExpressionREFERS_TO[Declaration]:::outer +``` +## MemberExpression +**Labels**:[Node](#enode) +[Statement](#estatement) +[Expression](#eexpression) +[DeclaredReferenceExpression](#edeclaredreferenceexpression) +[MemberExpression](#ememberexpression) + +### Relationships +[BASE](#MemberExpressionBASE) + +[REFERS_TO](#DeclaredReferenceExpressionREFERS_TO) + +[POSSIBLE_SUB_TYPES](#ExpressionPOSSIBLE_SUB_TYPES) + +[TYPE](#ExpressionTYPE) + +[LOCALS](#StatementLOCALS) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +#### BASE +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +MemberExpression--"BASE¹"-->MemberExpressionBASE[Expression]:::outer +``` +## InitializerListExpression +**Labels**:[Node](#enode) +[Statement](#estatement) +[Expression](#eexpression) +[InitializerListExpression](#einitializerlistexpression) + +### Relationships +[INITIALIZERS](#InitializerListExpressionINITIALIZERS) + +[POSSIBLE_SUB_TYPES](#ExpressionPOSSIBLE_SUB_TYPES) + +[TYPE](#ExpressionTYPE) + +[LOCALS](#StatementLOCALS) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +#### INITIALIZERS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +InitializerListExpression--"INITIALIZERS*"-->InitializerListExpressionINITIALIZERS[Expression]:::outer +``` +## DeleteExpression +**Labels**:[Node](#enode) +[Statement](#estatement) +[Expression](#eexpression) +[DeleteExpression](#edeleteexpression) + +### Relationships +[OPERAND](#DeleteExpressionOPERAND) + +[POSSIBLE_SUB_TYPES](#ExpressionPOSSIBLE_SUB_TYPES) + +[TYPE](#ExpressionTYPE) + +[LOCALS](#StatementLOCALS) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +#### OPERAND +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +DeleteExpression--"OPERAND¹"-->DeleteExpressionOPERAND[Expression]:::outer +``` +## CompoundStatementExpression +**Labels**:[Node](#enode) +[Statement](#estatement) +[Expression](#eexpression) +[CompoundStatementExpression](#ecompoundstatementexpression) + +### Relationships +[STATEMENT](#CompoundStatementExpressionSTATEMENT) + +[POSSIBLE_SUB_TYPES](#ExpressionPOSSIBLE_SUB_TYPES) + +[TYPE](#ExpressionTYPE) + +[LOCALS](#StatementLOCALS) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +#### STATEMENT +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +CompoundStatementExpression--"STATEMENT¹"-->CompoundStatementExpressionSTATEMENT[Statement]:::outer +``` +## ProblemExpression +**Labels**:[Node](#enode) +[Statement](#estatement) +[Expression](#eexpression) +[ProblemExpression](#eproblemexpression) + +### Relationships +[POSSIBLE_SUB_TYPES](#ExpressionPOSSIBLE_SUB_TYPES) + +[TYPE](#ExpressionTYPE) + +[LOCALS](#StatementLOCALS) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +## Literal +**Labels**:[Node](#enode) +[Statement](#estatement) +[Expression](#eexpression) +[Literal](#eliteral) + +### Relationships +[POSSIBLE_SUB_TYPES](#ExpressionPOSSIBLE_SUB_TYPES) + +[TYPE](#ExpressionTYPE) + +[LOCALS](#StatementLOCALS) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +## TypeIdExpression +**Labels**:[Node](#enode) +[Statement](#estatement) +[Expression](#eexpression) +[TypeIdExpression](#etypeidexpression) + +### Relationships +[REFERENCED_TYPE](#TypeIdExpressionREFERENCED_TYPE) + +[POSSIBLE_SUB_TYPES](#ExpressionPOSSIBLE_SUB_TYPES) + +[TYPE](#ExpressionTYPE) + +[LOCALS](#StatementLOCALS) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +#### REFERENCED_TYPE +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +TypeIdExpression--"REFERENCED_TYPE¹"-->TypeIdExpressionREFERENCED_TYPE[Type]:::outer +``` +## ExpressionList +**Labels**:[Node](#enode) +[Statement](#estatement) +[Expression](#eexpression) +[ExpressionList](#eexpressionlist) + +### Relationships +[SUBEXPR](#ExpressionListSUBEXPR) + +[POSSIBLE_SUB_TYPES](#ExpressionPOSSIBLE_SUB_TYPES) + +[TYPE](#ExpressionTYPE) + +[LOCALS](#StatementLOCALS) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +#### SUBEXPR +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +ExpressionList--"SUBEXPR*"-->ExpressionListSUBEXPR[Statement]:::outer +``` +## IfStatement +**Labels**:[Node](#enode) +[Statement](#estatement) +[IfStatement](#eifstatement) + +### Relationships +[CONDITION_DECLARATION](#IfStatementCONDITION_DECLARATION) + +[INITIALIZER_STATEMENT](#IfStatementINITIALIZER_STATEMENT) + +[THEN_STATEMENT](#IfStatementTHEN_STATEMENT) + +[CONDITION](#IfStatementCONDITION) + +[ELSE_STATEMENT](#IfStatementELSE_STATEMENT) + +[LOCALS](#StatementLOCALS) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +#### CONDITION_DECLARATION +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +IfStatement--"CONDITION_DECLARATION¹"-->IfStatementCONDITION_DECLARATION[Declaration]:::outer +``` +#### INITIALIZER_STATEMENT +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +IfStatement--"INITIALIZER_STATEMENT¹"-->IfStatementINITIALIZER_STATEMENT[Statement]:::outer +``` +#### THEN_STATEMENT +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +IfStatement--"THEN_STATEMENT¹"-->IfStatementTHEN_STATEMENT[Statement]:::outer +``` +#### CONDITION +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +IfStatement--"CONDITION¹"-->IfStatementCONDITION[Expression]:::outer +``` +#### ELSE_STATEMENT +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +IfStatement--"ELSE_STATEMENT¹"-->IfStatementELSE_STATEMENT[Statement]:::outer +``` +## DeclarationStatement +**Labels**:[Node](#enode) +[Statement](#estatement) +[DeclarationStatement](#edeclarationstatement) + +### Children +[ASMDeclarationStatement](#easmdeclarationstatement) + +### Relationships +[DECLARATIONS](#DeclarationStatementDECLARATIONS) + +[LOCALS](#StatementLOCALS) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +#### DECLARATIONS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +DeclarationStatement--"DECLARATIONS*"-->DeclarationStatementDECLARATIONS[Declaration]:::outer +``` +## ASMDeclarationStatement +**Labels**:[Node](#enode) +[Statement](#estatement) +[DeclarationStatement](#edeclarationstatement) +[ASMDeclarationStatement](#easmdeclarationstatement) + +### Relationships +[DECLARATIONS](#DeclarationStatementDECLARATIONS) + +[LOCALS](#StatementLOCALS) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +## ForStatement +**Labels**:[Node](#enode) +[Statement](#estatement) +[ForStatement](#eforstatement) + +### Relationships +[CONDITION_DECLARATION](#ForStatementCONDITION_DECLARATION) + +[INITIALIZER_STATEMENT](#ForStatementINITIALIZER_STATEMENT) + +[ITERATION_STATEMENT](#ForStatementITERATION_STATEMENT) + +[CONDITION](#ForStatementCONDITION) + +[STATEMENT](#ForStatementSTATEMENT) + +[LOCALS](#StatementLOCALS) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +#### CONDITION_DECLARATION +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +ForStatement--"CONDITION_DECLARATION¹"-->ForStatementCONDITION_DECLARATION[Declaration]:::outer +``` +#### INITIALIZER_STATEMENT +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +ForStatement--"INITIALIZER_STATEMENT¹"-->ForStatementINITIALIZER_STATEMENT[Statement]:::outer +``` +#### ITERATION_STATEMENT +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +ForStatement--"ITERATION_STATEMENT¹"-->ForStatementITERATION_STATEMENT[Statement]:::outer +``` +#### CONDITION +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +ForStatement--"CONDITION¹"-->ForStatementCONDITION[Expression]:::outer +``` +#### STATEMENT +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +ForStatement--"STATEMENT¹"-->ForStatementSTATEMENT[Statement]:::outer +``` +## CatchClause +**Labels**:[Node](#enode) +[Statement](#estatement) +[CatchClause](#ecatchclause) + +### Relationships +[PARAMETER](#CatchClausePARAMETER) + +[BODY](#CatchClauseBODY) + +[LOCALS](#StatementLOCALS) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +#### PARAMETER +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +CatchClause--"PARAMETER¹"-->CatchClausePARAMETER[VariableDeclaration]:::outer +``` +#### BODY +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +CatchClause--"BODY¹"-->CatchClauseBODY[CompoundStatement]:::outer +``` +## SwitchStatement +**Labels**:[Node](#enode) +[Statement](#estatement) +[SwitchStatement](#eswitchstatement) + +### Relationships +[INITIALIZER_STATEMENT](#SwitchStatementINITIALIZER_STATEMENT) + +[SELECTOR_DECLARATION](#SwitchStatementSELECTOR_DECLARATION) + +[STATEMENT](#SwitchStatementSTATEMENT) + +[SELECTOR](#SwitchStatementSELECTOR) + +[LOCALS](#StatementLOCALS) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +#### INITIALIZER_STATEMENT +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +SwitchStatement--"INITIALIZER_STATEMENT¹"-->SwitchStatementINITIALIZER_STATEMENT[Statement]:::outer +``` +#### SELECTOR_DECLARATION +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +SwitchStatement--"SELECTOR_DECLARATION¹"-->SwitchStatementSELECTOR_DECLARATION[Declaration]:::outer +``` +#### STATEMENT +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +SwitchStatement--"STATEMENT¹"-->SwitchStatementSTATEMENT[Statement]:::outer +``` +#### SELECTOR +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +SwitchStatement--"SELECTOR¹"-->SwitchStatementSELECTOR[Expression]:::outer +``` +## GotoStatement +**Labels**:[Node](#enode) +[Statement](#estatement) +[GotoStatement](#egotostatement) + +### Relationships +[TARGET_LABEL](#GotoStatementTARGET_LABEL) + +[LOCALS](#StatementLOCALS) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +#### TARGET_LABEL +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +GotoStatement--"TARGET_LABEL¹"-->GotoStatementTARGET_LABEL[LabelStatement]:::outer +``` +## WhileStatement +**Labels**:[Node](#enode) +[Statement](#estatement) +[WhileStatement](#ewhilestatement) + +### Relationships +[CONDITION_DECLARATION](#WhileStatementCONDITION_DECLARATION) + +[CONDITION](#WhileStatementCONDITION) + +[STATEMENT](#WhileStatementSTATEMENT) + +[LOCALS](#StatementLOCALS) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +#### CONDITION_DECLARATION +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +WhileStatement--"CONDITION_DECLARATION¹"-->WhileStatementCONDITION_DECLARATION[Declaration]:::outer +``` +#### CONDITION +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +WhileStatement--"CONDITION¹"-->WhileStatementCONDITION[Expression]:::outer +``` +#### STATEMENT +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +WhileStatement--"STATEMENT¹"-->WhileStatementSTATEMENT[Statement]:::outer +``` +## CompoundStatement +**Labels**:[Node](#enode) +[Statement](#estatement) +[CompoundStatement](#ecompoundstatement) + +### Relationships +[STATEMENTS](#CompoundStatementSTATEMENTS) + +[LOCALS](#StatementLOCALS) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +#### STATEMENTS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +CompoundStatement--"STATEMENTS*"-->CompoundStatementSTATEMENTS[Statement]:::outer +``` +## ContinueStatement +**Labels**:[Node](#enode) +[Statement](#estatement) +[ContinueStatement](#econtinuestatement) + +### Relationships +[LOCALS](#StatementLOCALS) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +## DefaultStatement +**Labels**:[Node](#enode) +[Statement](#estatement) +[DefaultStatement](#edefaultstatement) + +### Relationships +[LOCALS](#StatementLOCALS) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +## SynchronizedStatement +**Labels**:[Node](#enode) +[Statement](#estatement) +[SynchronizedStatement](#esynchronizedstatement) + +### Relationships +[BLOCK_STATEMENT](#SynchronizedStatementBLOCK_STATEMENT) + +[EXPRESSION](#SynchronizedStatementEXPRESSION) + +[LOCALS](#StatementLOCALS) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +#### BLOCK_STATEMENT +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +SynchronizedStatement--"BLOCK_STATEMENT¹"-->SynchronizedStatementBLOCK_STATEMENT[CompoundStatement]:::outer +``` +#### EXPRESSION +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +SynchronizedStatement--"EXPRESSION¹"-->SynchronizedStatementEXPRESSION[Expression]:::outer +``` +## TryStatement +**Labels**:[Node](#enode) +[Statement](#estatement) +[TryStatement](#etrystatement) + +### Relationships +[RESOURCES](#TryStatementRESOURCES) + +[FINALLY_BLOCK](#TryStatementFINALLY_BLOCK) + +[TRY_BLOCK](#TryStatementTRY_BLOCK) + +[CATCH_CLAUSES](#TryStatementCATCH_CLAUSES) + +[LOCALS](#StatementLOCALS) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +#### RESOURCES +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +TryStatement--"RESOURCES*"-->TryStatementRESOURCES[Statement]:::outer +``` +#### FINALLY_BLOCK +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +TryStatement--"FINALLY_BLOCK¹"-->TryStatementFINALLY_BLOCK[CompoundStatement]:::outer +``` +#### TRY_BLOCK +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +TryStatement--"TRY_BLOCK¹"-->TryStatementTRY_BLOCK[CompoundStatement]:::outer +``` +#### CATCH_CLAUSES +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +TryStatement--"CATCH_CLAUSES*"-->TryStatementCATCH_CLAUSES[CatchClause]:::outer +``` +## ForEachStatement +**Labels**:[Node](#enode) +[Statement](#estatement) +[ForEachStatement](#eforeachstatement) + +### Relationships +[STATEMENT](#ForEachStatementSTATEMENT) + +[VARIABLE](#ForEachStatementVARIABLE) + +[ITERABLE](#ForEachStatementITERABLE) + +[LOCALS](#StatementLOCALS) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +#### STATEMENT +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +ForEachStatement--"STATEMENT¹"-->ForEachStatementSTATEMENT[Statement]:::outer +``` +#### VARIABLE +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +ForEachStatement--"VARIABLE¹"-->ForEachStatementVARIABLE[Statement]:::outer +``` +#### ITERABLE +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +ForEachStatement--"ITERABLE¹"-->ForEachStatementITERABLE[Statement]:::outer +``` +## LabelStatement +**Labels**:[Node](#enode) +[Statement](#estatement) +[LabelStatement](#elabelstatement) + +### Relationships +[SUB_STATEMENT](#LabelStatementSUB_STATEMENT) + +[LOCALS](#StatementLOCALS) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +#### SUB_STATEMENT +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +LabelStatement--"SUB_STATEMENT¹"-->LabelStatementSUB_STATEMENT[Statement]:::outer +``` +## BreakStatement +**Labels**:[Node](#enode) +[Statement](#estatement) +[BreakStatement](#ebreakstatement) + +### Relationships +[LOCALS](#StatementLOCALS) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +## EmptyStatement +**Labels**:[Node](#enode) +[Statement](#estatement) +[EmptyStatement](#eemptystatement) + +### Relationships +[LOCALS](#StatementLOCALS) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +## Declaration +**Labels**:[Node](#enode) +[Declaration](#edeclaration) + +### Children +[ValueDeclaration](#evaluedeclaration) +[TemplateDeclaration](#etemplatedeclaration) +[EnumDeclaration](#eenumdeclaration) +[TypedefDeclaration](#etypedefdeclaration) +[UsingDirective](#eusingdirective) +[NamespaceDeclaration](#enamespacedeclaration) +[RecordDeclaration](#erecorddeclaration) +[DeclarationSequence](#edeclarationsequence) +[TranslationUnitDeclaration](#etranslationunitdeclaration) +[IncludeDeclaration](#eincludedeclaration) + +### Relationships +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +## ValueDeclaration +**Labels**:[Node](#enode) +[Declaration](#edeclaration) +[ValueDeclaration](#evaluedeclaration) + +### Children +[FieldDeclaration](#efielddeclaration) +[VariableDeclaration](#evariabledeclaration) +[ProblemDeclaration](#eproblemdeclaration) +[EnumConstantDeclaration](#eenumconstantdeclaration) +[FunctionDeclaration](#efunctiondeclaration) +[ParamVariableDeclaration](#eparamvariabledeclaration) +[TypeParamDeclaration](#etypeparamdeclaration) + +### Relationships +[POSSIBLE_SUB_TYPES](#ValueDeclarationPOSSIBLE_SUB_TYPES) + +[TYPE](#ValueDeclarationTYPE) + +[USAGE](#ValueDeclarationUSAGE) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +#### POSSIBLE_SUB_TYPES +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +ValueDeclaration--"POSSIBLE_SUB_TYPES*"-->ValueDeclarationPOSSIBLE_SUB_TYPES[Type]:::outer +``` +#### TYPE +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +ValueDeclaration--"TYPE¹"-->ValueDeclarationTYPE[Type]:::outer +``` +#### USAGE +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +ValueDeclaration--"USAGE*"-->ValueDeclarationUSAGE[DeclaredReferenceExpression]:::outer +``` +## FieldDeclaration +**Labels**:[Node](#enode) +[Declaration](#edeclaration) +[ValueDeclaration](#evaluedeclaration) +[FieldDeclaration](#efielddeclaration) + +### Relationships +[INITIALIZER](#FieldDeclarationINITIALIZER) + +[DEFINES](#FieldDeclarationDEFINES) + +[POSSIBLE_SUB_TYPES](#ValueDeclarationPOSSIBLE_SUB_TYPES) + +[TYPE](#ValueDeclarationTYPE) + +[USAGE](#ValueDeclarationUSAGE) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +#### INITIALIZER +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +FieldDeclaration--"INITIALIZER¹"-->FieldDeclarationINITIALIZER[Expression]:::outer +``` +#### DEFINES +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +FieldDeclaration--"DEFINES¹"-->FieldDeclarationDEFINES[FieldDeclaration]:::outer +``` +## VariableDeclaration +**Labels**:[Node](#enode) +[Declaration](#edeclaration) +[ValueDeclaration](#evaluedeclaration) +[VariableDeclaration](#evariabledeclaration) + +### Relationships +[INITIALIZER](#VariableDeclarationINITIALIZER) + +[TEMPLATE_PARAMETERS](#VariableDeclarationTEMPLATE_PARAMETERS) + +[POSSIBLE_SUB_TYPES](#ValueDeclarationPOSSIBLE_SUB_TYPES) + +[TYPE](#ValueDeclarationTYPE) + +[USAGE](#ValueDeclarationUSAGE) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +#### INITIALIZER +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +VariableDeclaration--"INITIALIZER¹"-->VariableDeclarationINITIALIZER[Expression]:::outer +``` +#### TEMPLATE_PARAMETERS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +VariableDeclaration--"TEMPLATE_PARAMETERS*"-->VariableDeclarationTEMPLATE_PARAMETERS[Node]:::outer +``` +## ProblemDeclaration +**Labels**:[Node](#enode) +[Declaration](#edeclaration) +[ValueDeclaration](#evaluedeclaration) +[ProblemDeclaration](#eproblemdeclaration) + +### Relationships +[POSSIBLE_SUB_TYPES](#ValueDeclarationPOSSIBLE_SUB_TYPES) + +[TYPE](#ValueDeclarationTYPE) + +[USAGE](#ValueDeclarationUSAGE) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +## EnumConstantDeclaration +**Labels**:[Node](#enode) +[Declaration](#edeclaration) +[ValueDeclaration](#evaluedeclaration) +[EnumConstantDeclaration](#eenumconstantdeclaration) + +### Relationships +[INITIALIZER](#EnumConstantDeclarationINITIALIZER) + +[POSSIBLE_SUB_TYPES](#ValueDeclarationPOSSIBLE_SUB_TYPES) + +[TYPE](#ValueDeclarationTYPE) + +[USAGE](#ValueDeclarationUSAGE) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +#### INITIALIZER +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +EnumConstantDeclaration--"INITIALIZER¹"-->EnumConstantDeclarationINITIALIZER[Expression]:::outer +``` +## FunctionDeclaration +**Labels**:[Node](#enode) +[Declaration](#edeclaration) +[ValueDeclaration](#evaluedeclaration) +[FunctionDeclaration](#efunctiondeclaration) + +### Children +[MethodDeclaration](#emethoddeclaration) + +### Relationships +[THROWS_TYPES](#FunctionDeclarationTHROWS_TYPES) + +[OVERRIDES](#FunctionDeclarationOVERRIDES) + +[BODY](#FunctionDeclarationBODY) + +[RECORDS](#FunctionDeclarationRECORDS) + +[RETURN_TYPES](#FunctionDeclarationRETURN_TYPES) + +[PARAMETERS](#FunctionDeclarationPARAMETERS) + +[DEFINES](#FunctionDeclarationDEFINES) + +[POSSIBLE_SUB_TYPES](#ValueDeclarationPOSSIBLE_SUB_TYPES) + +[TYPE](#ValueDeclarationTYPE) + +[USAGE](#ValueDeclarationUSAGE) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +#### THROWS_TYPES +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +FunctionDeclaration--"THROWS_TYPES*"-->FunctionDeclarationTHROWS_TYPES[Type]:::outer +``` +#### OVERRIDES +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +FunctionDeclaration--"OVERRIDES*"-->FunctionDeclarationOVERRIDES[FunctionDeclaration]:::outer +``` +#### BODY +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +FunctionDeclaration--"BODY¹"-->FunctionDeclarationBODY[Statement]:::outer +``` +#### RECORDS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +FunctionDeclaration--"RECORDS*"-->FunctionDeclarationRECORDS[RecordDeclaration]:::outer +``` +#### RETURN_TYPES +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +FunctionDeclaration--"RETURN_TYPES*"-->FunctionDeclarationRETURN_TYPES[Type]:::outer +``` +#### PARAMETERS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +FunctionDeclaration--"PARAMETERS*"-->FunctionDeclarationPARAMETERS[ParamVariableDeclaration]:::outer +``` +#### DEFINES +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +FunctionDeclaration--"DEFINES¹"-->FunctionDeclarationDEFINES[FunctionDeclaration]:::outer +``` +## MethodDeclaration +**Labels**:[Node](#enode) +[Declaration](#edeclaration) +[ValueDeclaration](#evaluedeclaration) +[FunctionDeclaration](#efunctiondeclaration) +[MethodDeclaration](#emethoddeclaration) + +### Children +[ConstructorDeclaration](#econstructordeclaration) + +### Relationships +[RECEIVER](#MethodDeclarationRECEIVER) + +[RECORD_DECLARATION](#MethodDeclarationRECORD_DECLARATION) + +[THROWS_TYPES](#FunctionDeclarationTHROWS_TYPES) + +[OVERRIDES](#FunctionDeclarationOVERRIDES) + +[BODY](#FunctionDeclarationBODY) + +[RECORDS](#FunctionDeclarationRECORDS) + +[RETURN_TYPES](#FunctionDeclarationRETURN_TYPES) + +[PARAMETERS](#FunctionDeclarationPARAMETERS) + +[DEFINES](#FunctionDeclarationDEFINES) + +[POSSIBLE_SUB_TYPES](#ValueDeclarationPOSSIBLE_SUB_TYPES) + +[TYPE](#ValueDeclarationTYPE) + +[USAGE](#ValueDeclarationUSAGE) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +#### RECEIVER +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +MethodDeclaration--"RECEIVER¹"-->MethodDeclarationRECEIVER[VariableDeclaration]:::outer +``` +#### RECORD_DECLARATION +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +MethodDeclaration--"RECORD_DECLARATION¹"-->MethodDeclarationRECORD_DECLARATION[RecordDeclaration]:::outer +``` +## ConstructorDeclaration +**Labels**:[Node](#enode) +[Declaration](#edeclaration) +[ValueDeclaration](#evaluedeclaration) +[FunctionDeclaration](#efunctiondeclaration) +[MethodDeclaration](#emethoddeclaration) +[ConstructorDeclaration](#econstructordeclaration) + +### Relationships +[RECEIVER](#MethodDeclarationRECEIVER) + +[RECORD_DECLARATION](#MethodDeclarationRECORD_DECLARATION) + +[THROWS_TYPES](#FunctionDeclarationTHROWS_TYPES) + +[OVERRIDES](#FunctionDeclarationOVERRIDES) + +[BODY](#FunctionDeclarationBODY) + +[RECORDS](#FunctionDeclarationRECORDS) + +[RETURN_TYPES](#FunctionDeclarationRETURN_TYPES) + +[PARAMETERS](#FunctionDeclarationPARAMETERS) + +[DEFINES](#FunctionDeclarationDEFINES) + +[POSSIBLE_SUB_TYPES](#ValueDeclarationPOSSIBLE_SUB_TYPES) + +[TYPE](#ValueDeclarationTYPE) + +[USAGE](#ValueDeclarationUSAGE) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +## ParamVariableDeclaration +**Labels**:[Node](#enode) +[Declaration](#edeclaration) +[ValueDeclaration](#evaluedeclaration) +[ParamVariableDeclaration](#eparamvariabledeclaration) + +### Relationships +[DEFAULT](#ParamVariableDeclarationDEFAULT) + +[POSSIBLE_SUB_TYPES](#ValueDeclarationPOSSIBLE_SUB_TYPES) + +[TYPE](#ValueDeclarationTYPE) + +[USAGE](#ValueDeclarationUSAGE) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +#### DEFAULT +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +ParamVariableDeclaration--"DEFAULT¹"-->ParamVariableDeclarationDEFAULT[Expression]:::outer +``` +## TypeParamDeclaration +**Labels**:[Node](#enode) +[Declaration](#edeclaration) +[ValueDeclaration](#evaluedeclaration) +[TypeParamDeclaration](#etypeparamdeclaration) + +### Relationships +[DEFAULT](#TypeParamDeclarationDEFAULT) + +[POSSIBLE_SUB_TYPES](#ValueDeclarationPOSSIBLE_SUB_TYPES) + +[TYPE](#ValueDeclarationTYPE) + +[USAGE](#ValueDeclarationUSAGE) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +#### DEFAULT +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +TypeParamDeclaration--"DEFAULT¹"-->TypeParamDeclarationDEFAULT[Type]:::outer +``` +## TemplateDeclaration +**Labels**:[Node](#enode) +[Declaration](#edeclaration) +[TemplateDeclaration](#etemplatedeclaration) + +### Children +[ClassTemplateDeclaration](#eclasstemplatedeclaration) +[FunctionTemplateDeclaration](#efunctiontemplatedeclaration) + +### Relationships +[PARAMETERS](#TemplateDeclarationPARAMETERS) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +#### PARAMETERS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +TemplateDeclaration--"PARAMETERS*"-->TemplateDeclarationPARAMETERS[Declaration]:::outer +``` +## ClassTemplateDeclaration +**Labels**:[Node](#enode) +[Declaration](#edeclaration) +[TemplateDeclaration](#etemplatedeclaration) +[ClassTemplateDeclaration](#eclasstemplatedeclaration) + +### Relationships +[REALIZATION](#ClassTemplateDeclarationREALIZATION) + +[PARAMETERS](#TemplateDeclarationPARAMETERS) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +#### REALIZATION +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +ClassTemplateDeclaration--"REALIZATION*"-->ClassTemplateDeclarationREALIZATION[RecordDeclaration]:::outer +``` +## FunctionTemplateDeclaration +**Labels**:[Node](#enode) +[Declaration](#edeclaration) +[TemplateDeclaration](#etemplatedeclaration) +[FunctionTemplateDeclaration](#efunctiontemplatedeclaration) + +### Relationships +[REALIZATION](#FunctionTemplateDeclarationREALIZATION) + +[PARAMETERS](#TemplateDeclarationPARAMETERS) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +#### REALIZATION +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +FunctionTemplateDeclaration--"REALIZATION*"-->FunctionTemplateDeclarationREALIZATION[FunctionDeclaration]:::outer +``` +## EnumDeclaration +**Labels**:[Node](#enode) +[Declaration](#edeclaration) +[EnumDeclaration](#eenumdeclaration) + +### Relationships +[ENTRIES](#EnumDeclarationENTRIES) + +[SUPER_TYPE_DECLARATIONS](#EnumDeclarationSUPER_TYPE_DECLARATIONS) + +[SUPER_TYPES](#EnumDeclarationSUPER_TYPES) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +#### ENTRIES +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +EnumDeclaration--"ENTRIES*"-->EnumDeclarationENTRIES[EnumConstantDeclaration]:::outer +``` +#### SUPER_TYPE_DECLARATIONS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +EnumDeclaration--"SUPER_TYPE_DECLARATIONS*"-->EnumDeclarationSUPER_TYPE_DECLARATIONS[RecordDeclaration]:::outer +``` +#### SUPER_TYPES +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +EnumDeclaration--"SUPER_TYPES*"-->EnumDeclarationSUPER_TYPES[Type]:::outer +``` +## TypedefDeclaration +**Labels**:[Node](#enode) +[Declaration](#edeclaration) +[TypedefDeclaration](#etypedefdeclaration) + +### Relationships +[ALIAS](#TypedefDeclarationALIAS) + +[TYPE](#TypedefDeclarationTYPE) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +#### ALIAS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +TypedefDeclaration--"ALIAS¹"-->TypedefDeclarationALIAS[Type]:::outer +``` +#### TYPE +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +TypedefDeclaration--"TYPE¹"-->TypedefDeclarationTYPE[Type]:::outer +``` +## UsingDirective +**Labels**:[Node](#enode) +[Declaration](#edeclaration) +[UsingDirective](#eusingdirective) + +### Relationships +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +## NamespaceDeclaration +**Labels**:[Node](#enode) +[Declaration](#edeclaration) +[NamespaceDeclaration](#enamespacedeclaration) + +### Relationships +[STATEMENTS](#NamespaceDeclarationSTATEMENTS) + +[DECLARATIONS](#NamespaceDeclarationDECLARATIONS) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +#### STATEMENTS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +NamespaceDeclaration--"STATEMENTS*"-->NamespaceDeclarationSTATEMENTS[Statement]:::outer +``` +#### DECLARATIONS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +NamespaceDeclaration--"DECLARATIONS*"-->NamespaceDeclarationDECLARATIONS[Declaration]:::outer +``` +## RecordDeclaration +**Labels**:[Node](#enode) +[Declaration](#edeclaration) +[RecordDeclaration](#erecorddeclaration) + +### Relationships +[IMPORTS](#RecordDeclarationIMPORTS) + +[CONSTRUCTORS](#RecordDeclarationCONSTRUCTORS) + +[FIELDS](#RecordDeclarationFIELDS) + +[TEMPLATES](#RecordDeclarationTEMPLATES) + +[STATIC_IMPORTS](#RecordDeclarationSTATIC_IMPORTS) + +[RECORDS](#RecordDeclarationRECORDS) + +[SUPER_TYPE_DECLARATIONS](#RecordDeclarationSUPER_TYPE_DECLARATIONS) + +[STATEMENTS](#RecordDeclarationSTATEMENTS) + +[METHODS](#RecordDeclarationMETHODS) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +#### IMPORTS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +RecordDeclaration--"IMPORTS*"-->RecordDeclarationIMPORTS[Declaration]:::outer +``` +#### CONSTRUCTORS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +RecordDeclaration--"CONSTRUCTORS*"-->RecordDeclarationCONSTRUCTORS[ConstructorDeclaration]:::outer +``` +#### FIELDS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +RecordDeclaration--"FIELDS*"-->RecordDeclarationFIELDS[FieldDeclaration]:::outer +``` +#### TEMPLATES +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +RecordDeclaration--"TEMPLATES*"-->RecordDeclarationTEMPLATES[TemplateDeclaration]:::outer +``` +#### STATIC_IMPORTS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +RecordDeclaration--"STATIC_IMPORTS*"-->RecordDeclarationSTATIC_IMPORTS[ValueDeclaration]:::outer +``` +#### RECORDS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +RecordDeclaration--"RECORDS*"-->RecordDeclarationRECORDS[RecordDeclaration]:::outer +``` +#### SUPER_TYPE_DECLARATIONS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +RecordDeclaration--"SUPER_TYPE_DECLARATIONS*"-->RecordDeclarationSUPER_TYPE_DECLARATIONS[RecordDeclaration]:::outer +``` +#### STATEMENTS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +RecordDeclaration--"STATEMENTS*"-->RecordDeclarationSTATEMENTS[Statement]:::outer +``` +#### METHODS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +RecordDeclaration--"METHODS*"-->RecordDeclarationMETHODS[MethodDeclaration]:::outer +``` +## DeclarationSequence +**Labels**:[Node](#enode) +[Declaration](#edeclaration) +[DeclarationSequence](#edeclarationsequence) + +### Relationships +[CHILDREN](#DeclarationSequenceCHILDREN) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +#### CHILDREN +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +DeclarationSequence--"CHILDREN*"-->DeclarationSequenceCHILDREN[Declaration]:::outer +``` +## TranslationUnitDeclaration +**Labels**:[Node](#enode) +[Declaration](#edeclaration) +[TranslationUnitDeclaration](#etranslationunitdeclaration) + +### Relationships +[NAMESPACES](#TranslationUnitDeclarationNAMESPACES) + +[DECLARATIONS](#TranslationUnitDeclarationDECLARATIONS) + +[STATEMENTS](#TranslationUnitDeclarationSTATEMENTS) + +[INCLUDES](#TranslationUnitDeclarationINCLUDES) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +#### NAMESPACES +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +TranslationUnitDeclaration--"NAMESPACES*"-->TranslationUnitDeclarationNAMESPACES[NamespaceDeclaration]:::outer +``` +#### DECLARATIONS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +TranslationUnitDeclaration--"DECLARATIONS*"-->TranslationUnitDeclarationDECLARATIONS[Declaration]:::outer +``` +#### STATEMENTS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +TranslationUnitDeclaration--"STATEMENTS*"-->TranslationUnitDeclarationSTATEMENTS[Statement]:::outer +``` +#### INCLUDES +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +TranslationUnitDeclaration--"INCLUDES*"-->TranslationUnitDeclarationINCLUDES[IncludeDeclaration]:::outer +``` +## IncludeDeclaration +**Labels**:[Node](#enode) +[Declaration](#edeclaration) +[IncludeDeclaration](#eincludedeclaration) + +### Relationships +[INCLUDES](#IncludeDeclarationINCLUDES) + +[PROBLEMS](#IncludeDeclarationPROBLEMS) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +#### INCLUDES +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +IncludeDeclaration--"INCLUDES*"-->IncludeDeclarationINCLUDES[IncludeDeclaration]:::outer +``` +#### PROBLEMS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +IncludeDeclaration--"PROBLEMS*"-->IncludeDeclarationPROBLEMS[ProblemDeclaration]:::outer +``` +## Type +**Labels**:[Node](#enode) +[Type](#etype) + +### Children +[UnknownType](#eunknowntype) +[ObjectType](#eobjecttype) +[ParameterizedType](#eparameterizedtype) +[PointerType](#epointertype) +[FunctionPointerType](#efunctionpointertype) +[TupleType](#etupletype) +[IncompleteType](#eincompletetype) +[ReferenceType](#ereferencetype) +[FunctionType](#efunctiontype) + +### Relationships +[SUPER_TYPE](#TypeSUPER_TYPE) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +#### SUPER_TYPE +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +Type--"SUPER_TYPE*"-->TypeSUPER_TYPE[Type]:::outer +``` +## UnknownType +**Labels**:[Node](#enode) +[Type](#etype) +[UnknownType](#eunknowntype) + +### Relationships +[SUPER_TYPE](#TypeSUPER_TYPE) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +## ObjectType +**Labels**:[Node](#enode) +[Type](#etype) +[ObjectType](#eobjecttype) + +### Children +[NumericType](#enumerictype) +[StringType](#estringtype) + +### Relationships +[GENERICS](#ObjectTypeGENERICS) + +[RECORD_DECLARATION](#ObjectTypeRECORD_DECLARATION) + +[SUPER_TYPE](#TypeSUPER_TYPE) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +#### GENERICS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +ObjectType--"GENERICS*"-->ObjectTypeGENERICS[Type]:::outer +``` +#### RECORD_DECLARATION +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +ObjectType--"RECORD_DECLARATION¹"-->ObjectTypeRECORD_DECLARATION[RecordDeclaration]:::outer +``` +## NumericType +**Labels**:[Node](#enode) +[Type](#etype) +[ObjectType](#eobjecttype) +[NumericType](#enumerictype) + +### Children +[IntegerType](#eintegertype) +[FloatingPointType](#efloatingpointtype) +[BooleanType](#ebooleantype) + +### Relationships +[GENERICS](#ObjectTypeGENERICS) + +[RECORD_DECLARATION](#ObjectTypeRECORD_DECLARATION) + +[SUPER_TYPE](#TypeSUPER_TYPE) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +## IntegerType +**Labels**:[Node](#enode) +[Type](#etype) +[ObjectType](#eobjecttype) +[NumericType](#enumerictype) +[IntegerType](#eintegertype) + +### Relationships +[GENERICS](#ObjectTypeGENERICS) + +[RECORD_DECLARATION](#ObjectTypeRECORD_DECLARATION) + +[SUPER_TYPE](#TypeSUPER_TYPE) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +## FloatingPointType +**Labels**:[Node](#enode) +[Type](#etype) +[ObjectType](#eobjecttype) +[NumericType](#enumerictype) +[FloatingPointType](#efloatingpointtype) + +### Relationships +[GENERICS](#ObjectTypeGENERICS) + +[RECORD_DECLARATION](#ObjectTypeRECORD_DECLARATION) + +[SUPER_TYPE](#TypeSUPER_TYPE) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +## BooleanType +**Labels**:[Node](#enode) +[Type](#etype) +[ObjectType](#eobjecttype) +[NumericType](#enumerictype) +[BooleanType](#ebooleantype) + +### Relationships +[GENERICS](#ObjectTypeGENERICS) + +[RECORD_DECLARATION](#ObjectTypeRECORD_DECLARATION) + +[SUPER_TYPE](#TypeSUPER_TYPE) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +## StringType +**Labels**:[Node](#enode) +[Type](#etype) +[ObjectType](#eobjecttype) +[StringType](#estringtype) + +### Relationships +[GENERICS](#ObjectTypeGENERICS) + +[RECORD_DECLARATION](#ObjectTypeRECORD_DECLARATION) + +[SUPER_TYPE](#TypeSUPER_TYPE) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +## ParameterizedType +**Labels**:[Node](#enode) +[Type](#etype) +[ParameterizedType](#eparameterizedtype) + +### Relationships +[SUPER_TYPE](#TypeSUPER_TYPE) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +## PointerType +**Labels**:[Node](#enode) +[Type](#etype) +[PointerType](#epointertype) + +### Relationships +[ELEMENT_TYPE](#PointerTypeELEMENT_TYPE) + +[SUPER_TYPE](#TypeSUPER_TYPE) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +#### ELEMENT_TYPE +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +PointerType--"ELEMENT_TYPE¹"-->PointerTypeELEMENT_TYPE[Type]:::outer +``` +## FunctionPointerType +**Labels**:[Node](#enode) +[Type](#etype) +[FunctionPointerType](#efunctionpointertype) + +### Relationships +[PARAMETERS](#FunctionPointerTypePARAMETERS) + +[RETURN_TYPE](#FunctionPointerTypeRETURN_TYPE) + +[SUPER_TYPE](#TypeSUPER_TYPE) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +#### PARAMETERS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +FunctionPointerType--"PARAMETERS*"-->FunctionPointerTypePARAMETERS[Type]:::outer +``` +#### RETURN_TYPE +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +FunctionPointerType--"RETURN_TYPE¹"-->FunctionPointerTypeRETURN_TYPE[Type]:::outer +``` +## TupleType +**Labels**:[Node](#enode) +[Type](#etype) +[TupleType](#etupletype) + +### Relationships +[TYPES](#TupleTypeTYPES) + +[SUPER_TYPE](#TypeSUPER_TYPE) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +#### TYPES +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +TupleType--"TYPES*"-->TupleTypeTYPES[Type]:::outer +``` +## IncompleteType +**Labels**:[Node](#enode) +[Type](#etype) +[IncompleteType](#eincompletetype) + +### Relationships +[SUPER_TYPE](#TypeSUPER_TYPE) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +## ReferenceType +**Labels**:[Node](#enode) +[Type](#etype) +[ReferenceType](#ereferencetype) + +### Relationships +[REFERENCE](#ReferenceTypeREFERENCE) + +[SUPER_TYPE](#TypeSUPER_TYPE) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +#### REFERENCE +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +ReferenceType--"REFERENCE¹"-->ReferenceTypeREFERENCE[Type]:::outer +``` +## FunctionType +**Labels**:[Node](#enode) +[Type](#etype) +[FunctionType](#efunctiontype) + +### Relationships +[RETURN_TYPES](#FunctionTypeRETURN_TYPES) + +[PARAMETERS](#FunctionTypePARAMETERS) + +[SUPER_TYPE](#TypeSUPER_TYPE) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +#### RETURN_TYPES +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +FunctionType--"RETURN_TYPES*"-->FunctionTypeRETURN_TYPES[Type]:::outer +``` +#### PARAMETERS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +FunctionType--"PARAMETERS*"-->FunctionTypePARAMETERS[Type]:::outer +``` +## AnnotationMember +**Labels**:[Node](#enode) +[AnnotationMember](#eannotationmember) + +### Relationships +[VALUE](#AnnotationMemberVALUE) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +#### VALUE +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +AnnotationMember--"VALUE¹"-->AnnotationMemberVALUE[Expression]:::outer +``` +## Component +**Labels**:[Node](#enode) +[Component](#ecomponent) + +### Relationships +[OUTGOING_INTERACTIONS](#ComponentOUTGOING_INTERACTIONS) + +[INCOMING_INTERACTIONS](#ComponentINCOMING_INTERACTIONS) + +[TRANSLATION_UNITS](#ComponentTRANSLATION_UNITS) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +#### OUTGOING_INTERACTIONS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +Component--"OUTGOING_INTERACTIONS*"-->ComponentOUTGOING_INTERACTIONS[Node]:::outer +``` +#### INCOMING_INTERACTIONS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +Component--"INCOMING_INTERACTIONS*"-->ComponentINCOMING_INTERACTIONS[Node]:::outer +``` +#### TRANSLATION_UNITS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +Component--"TRANSLATION_UNITS*"-->ComponentTRANSLATION_UNITS[TranslationUnitDeclaration]:::outer +``` +## Annotation +**Labels**:[Node](#enode) +[Annotation](#eannotation) + +### Relationships +[MEMBERS](#AnnotationMEMBERS) + +[DFG](#NodeDFG) + +[EOG](#NodeEOG) + +[ANNOTATIONS](#NodeANNOTATIONS) + +[AST](#NodeAST) + +[SCOPE](#NodeSCOPE) + +[TYPEDEFS](#NodeTYPEDEFS) + +#### MEMBERS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +Annotation--"MEMBERS*"-->AnnotationMEMBERS[AnnotationMember]:::outer +``` diff --git a/docs/docs/CPG/specs/index.md b/docs/docs/CPG/specs/index.md new file mode 100755 index 0000000000..89971d3543 --- /dev/null +++ b/docs/docs/CPG/specs/index.md @@ -0,0 +1,19 @@ +--- +title: "Specifications" +linkTitle: "Specifications" +weight: 20 +no_list: false +menu: + main: + weight: 20 +description: > + The CPG library is a language-agnostic graph representation of source code. +--- + +# Specifications +The core of the code property graph are its nodes and edges. Here, you find the +links to the specifications of the following concepts: + +* Explore our [Graph Model](./graph) +* [Data Flow Graph (DFG)](./dfg) +* [Evaluation Order Graph (EOG)](./eog) diff --git a/docs/docs/CPG/specs/schema.md b/docs/docs/CPG/specs/schema.md new file mode 100644 index 0000000000..dcb736047a --- /dev/null +++ b/docs/docs/CPG/specs/schema.md @@ -0,0 +1,10952 @@ +# CPG Schema +This file shows all node labels and relationships between them that are persisted from the in memory CPG to the Neo4j database. The specification is generated automatically and always up to date. +# Node +## Children +[Statement](#estatement) [Declaration](#edeclaration) [Type](#etype) [AnnotationMember](#eannotationmember) [Component](#ecomponent) [Annotation](#eannotation) +## Relationships +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +### DFG +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +Node--"DFG*"-->NodeDFG[Node]:::outer +``` +### EOG +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +Node--"EOG*"-->NodeEOG[Node]:::outer +``` +### ANNOTATIONS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +Node--"ANNOTATIONS*"-->NodeANNOTATIONS[Annotation]:::outer +``` +### AST +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +Node--"AST*"-->NodeAST[Node]:::outer +``` +### SCOPE +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +Node--"SCOPE¹"-->NodeSCOPE[Node]:::outer +``` +### TYPEDEFS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +Node--"TYPEDEFS*"-->NodeTYPEDEFS[TypedefDeclaration]:::outer +``` +# Statement +**Labels**:[Node](#enode) [Statement](#estatement) +## Children +[AssertStatement](#eassertstatement) [DoStatement](#edostatement) [CaseStatement](#ecasestatement) [ReturnStatement](#ereturnstatement) [Expression](#eexpression) [IfStatement](#eifstatement) [DeclarationStatement](#edeclarationstatement) [ForStatement](#eforstatement) [CatchClause](#ecatchclause) [SwitchStatement](#eswitchstatement) [GotoStatement](#egotostatement) [WhileStatement](#ewhilestatement) [CompoundStatement](#ecompoundstatement) [ContinueStatement](#econtinuestatement) [DefaultStatement](#edefaultstatement) [SynchronizedStatement](#esynchronizedstatement) [TryStatement](#etrystatement) [ForEachStatement](#eforeachstatement) [LabelStatement](#elabelstatement) [BreakStatement](#ebreakstatement) [EmptyStatement](#eemptystatement) +## Relationships +[LOCALS](#StatementLOCALS) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +### LOCALS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +Statement--"LOCALS*"-->StatementLOCALS[VariableDeclaration]:::outer +``` +# AssertStatement +**Labels**:[Node](#enode) [Statement](#estatement) [AssertStatement](#eassertstatement) +## Relationships +[CONDITION](#AssertStatementCONDITION) +[MESSAGE](#AssertStatementMESSAGE) +[LOCALS](#StatementLOCALS) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +### CONDITION +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +AssertStatement--"CONDITION¹"-->AssertStatementCONDITION[Expression]:::outer +``` +### MESSAGE +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +AssertStatement--"MESSAGE¹"-->AssertStatementMESSAGE[Statement]:::outer +``` +# DoStatement +**Labels**:[Node](#enode) [Statement](#estatement) [DoStatement](#edostatement) +## Relationships +[CONDITION](#DoStatementCONDITION) +[STATEMENT](#DoStatementSTATEMENT) +[LOCALS](#StatementLOCALS) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +### CONDITION +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +DoStatement--"CONDITION¹"-->DoStatementCONDITION[Expression]:::outer +``` +### STATEMENT +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +DoStatement--"STATEMENT¹"-->DoStatementSTATEMENT[Statement]:::outer +``` +# CaseStatement +**Labels**:[Node](#enode) [Statement](#estatement) [CaseStatement](#ecasestatement) +## Relationships +[CASE_EXPRESSION](#CaseStatementCASE_EXPRESSION) +[LOCALS](#StatementLOCALS) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +### CASE_EXPRESSION +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +CaseStatement--"CASE_EXPRESSION¹"-->CaseStatementCASE_EXPRESSION[Expression]:::outer +``` +# ReturnStatement +**Labels**:[Node](#enode) [Statement](#estatement) [ReturnStatement](#ereturnstatement) +## Relationships +[RETURN_VALUES](#ReturnStatementRETURN_VALUES) +[LOCALS](#StatementLOCALS) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +### RETURN_VALUES +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +ReturnStatement--"RETURN_VALUES*"-->ReturnStatementRETURN_VALUES[Expression]:::outer +``` +# Expression +**Labels**:[Node](#enode) [Statement](#estatement) [Expression](#eexpression) +## Children +[NewExpression](#enewexpression) [LambdaExpression](#elambdaexpression) [UnaryOperator](#eunaryoperator) [ArrayRangeExpression](#earrayrangeexpression) [CallExpression](#ecallexpression) [DesignatedInitializerExpression](#edesignatedinitializerexpression) [KeyValueExpression](#ekeyvalueexpression) [AssignExpression](#eassignexpression) [CastExpression](#ecastexpression) [ArrayCreationExpression](#earraycreationexpression) [ArraySubscriptionExpression](#earraysubscriptionexpression) [TypeExpression](#etypeexpression) [BinaryOperator](#ebinaryoperator) [ConditionalExpression](#econditionalexpression) [DeclaredReferenceExpression](#edeclaredreferenceexpression) [InitializerListExpression](#einitializerlistexpression) [DeleteExpression](#edeleteexpression) [CompoundStatementExpression](#ecompoundstatementexpression) [ProblemExpression](#eproblemexpression) [Literal](#eliteral) [TypeIdExpression](#etypeidexpression) [ExpressionList](#eexpressionlist) +## Relationships +[POSSIBLE_SUB_TYPES](#ExpressionPOSSIBLE_SUB_TYPES) +[TYPE](#ExpressionTYPE) +[LOCALS](#StatementLOCALS) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +### POSSIBLE_SUB_TYPES +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +Expression--"POSSIBLE_SUB_TYPES*"-->ExpressionPOSSIBLE_SUB_TYPES[Type]:::outer +``` +### TYPE +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +Expression--"TYPE¹"-->ExpressionTYPE[Type]:::outer +``` +# NewExpression +**Labels**:[Node](#enode) [Statement](#estatement) [Expression](#eexpression) [NewExpression](#enewexpression) +## Relationships +[INITIALIZER](#NewExpressionINITIALIZER) +[TEMPLATE_PARAMETERS](#NewExpressionTEMPLATE_PARAMETERS) +[POSSIBLE_SUB_TYPES](#ExpressionPOSSIBLE_SUB_TYPES) +[TYPE](#ExpressionTYPE) +[LOCALS](#StatementLOCALS) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +### INITIALIZER +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +NewExpression--"INITIALIZER¹"-->NewExpressionINITIALIZER[Expression]:::outer +``` +### TEMPLATE_PARAMETERS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +NewExpression--"TEMPLATE_PARAMETERS*"-->NewExpressionTEMPLATE_PARAMETERS[Node]:::outer +``` +# LambdaExpression +**Labels**:[Node](#enode) [Statement](#estatement) [Expression](#eexpression) [LambdaExpression](#elambdaexpression) +## Relationships +[MUTABLE_VARIABLES](#LambdaExpressionMUTABLE_VARIABLES) +[FUNCTION](#LambdaExpressionFUNCTION) +[POSSIBLE_SUB_TYPES](#ExpressionPOSSIBLE_SUB_TYPES) +[TYPE](#ExpressionTYPE) +[LOCALS](#StatementLOCALS) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +### MUTABLE_VARIABLES +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +LambdaExpression--"MUTABLE_VARIABLES*"-->LambdaExpressionMUTABLE_VARIABLES[ValueDeclaration]:::outer +``` +### FUNCTION +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +LambdaExpression--"FUNCTION¹"-->LambdaExpressionFUNCTION[FunctionDeclaration]:::outer +``` +# UnaryOperator +**Labels**:[Node](#enode) [Statement](#estatement) [Expression](#eexpression) [UnaryOperator](#eunaryoperator) +## Relationships +[INPUT](#UnaryOperatorINPUT) +[POSSIBLE_SUB_TYPES](#ExpressionPOSSIBLE_SUB_TYPES) +[TYPE](#ExpressionTYPE) +[LOCALS](#StatementLOCALS) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +### INPUT +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +UnaryOperator--"INPUT¹"-->UnaryOperatorINPUT[Expression]:::outer +``` +# ArrayRangeExpression +**Labels**:[Node](#enode) [Statement](#estatement) [Expression](#eexpression) [ArrayRangeExpression](#earrayrangeexpression) +## Relationships +[CEILING](#ArrayRangeExpressionCEILING) +[STEP](#ArrayRangeExpressionSTEP) +[FLOOR](#ArrayRangeExpressionFLOOR) +[POSSIBLE_SUB_TYPES](#ExpressionPOSSIBLE_SUB_TYPES) +[TYPE](#ExpressionTYPE) +[LOCALS](#StatementLOCALS) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +### CEILING +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +ArrayRangeExpression--"CEILING¹"-->ArrayRangeExpressionCEILING[Expression]:::outer +``` +### STEP +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +ArrayRangeExpression--"STEP¹"-->ArrayRangeExpressionSTEP[Expression]:::outer +``` +### FLOOR +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +ArrayRangeExpression--"FLOOR¹"-->ArrayRangeExpressionFLOOR[Expression]:::outer +``` +# CallExpression +**Labels**:[Node](#enode) [Statement](#estatement) [Expression](#eexpression) [CallExpression](#ecallexpression) +## Children +[ExplicitConstructorInvocation](#eexplicitconstructorinvocation) [ConstructExpression](#econstructexpression) [MemberCallExpression](#emembercallexpression) +## Relationships +[CALLEE](#CallExpressionCALLEE) +[INVOKES](#CallExpressionINVOKES) +[TEMPLATE_INSTANTIATION](#CallExpressionTEMPLATE_INSTANTIATION) +[ARGUMENTS](#CallExpressionARGUMENTS) +[TEMPLATE_PARAMETERS](#CallExpressionTEMPLATE_PARAMETERS) +[POSSIBLE_SUB_TYPES](#ExpressionPOSSIBLE_SUB_TYPES) +[TYPE](#ExpressionTYPE) +[LOCALS](#StatementLOCALS) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +### CALLEE +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +CallExpression--"CALLEE¹"-->CallExpressionCALLEE[Expression]:::outer +``` +### INVOKES +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +CallExpression--"INVOKES*"-->CallExpressionINVOKES[FunctionDeclaration]:::outer +``` +### TEMPLATE_INSTANTIATION +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +CallExpression--"TEMPLATE_INSTANTIATION¹"-->CallExpressionTEMPLATE_INSTANTIATION[TemplateDeclaration]:::outer +``` +### ARGUMENTS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +CallExpression--"ARGUMENTS*"-->CallExpressionARGUMENTS[Expression]:::outer +``` +### TEMPLATE_PARAMETERS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +CallExpression--"TEMPLATE_PARAMETERS*"-->CallExpressionTEMPLATE_PARAMETERS[Node]:::outer +``` +# ExplicitConstructorInvocation +**Labels**:[Node](#enode) [Statement](#estatement) [Expression](#eexpression) [CallExpression](#ecallexpression) [ExplicitConstructorInvocation](#eexplicitconstructorinvocation) +## Relationships +[CALLEE](#CallExpressionCALLEE) +[INVOKES](#CallExpressionINVOKES) +[TEMPLATE_INSTANTIATION](#CallExpressionTEMPLATE_INSTANTIATION) +[ARGUMENTS](#CallExpressionARGUMENTS) +[TEMPLATE_PARAMETERS](#CallExpressionTEMPLATE_PARAMETERS) +[POSSIBLE_SUB_TYPES](#ExpressionPOSSIBLE_SUB_TYPES) +[TYPE](#ExpressionTYPE) +[LOCALS](#StatementLOCALS) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +# ConstructExpression +**Labels**:[Node](#enode) [Statement](#estatement) [Expression](#eexpression) [CallExpression](#ecallexpression) [ConstructExpression](#econstructexpression) +## Relationships +[INSTANTIATES](#ConstructExpressionINSTANTIATES) +[CONSTRUCTOR](#ConstructExpressionCONSTRUCTOR) +[ANOYMOUS_CLASS](#ConstructExpressionANOYMOUS_CLASS) +[CALLEE](#CallExpressionCALLEE) +[INVOKES](#CallExpressionINVOKES) +[TEMPLATE_INSTANTIATION](#CallExpressionTEMPLATE_INSTANTIATION) +[ARGUMENTS](#CallExpressionARGUMENTS) +[TEMPLATE_PARAMETERS](#CallExpressionTEMPLATE_PARAMETERS) +[POSSIBLE_SUB_TYPES](#ExpressionPOSSIBLE_SUB_TYPES) +[TYPE](#ExpressionTYPE) +[LOCALS](#StatementLOCALS) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +### INSTANTIATES +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +ConstructExpression--"INSTANTIATES¹"-->ConstructExpressionINSTANTIATES[Declaration]:::outer +``` +### CONSTRUCTOR +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +ConstructExpression--"CONSTRUCTOR¹"-->ConstructExpressionCONSTRUCTOR[ConstructorDeclaration]:::outer +``` +### ANOYMOUS_CLASS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +ConstructExpression--"ANOYMOUS_CLASS¹"-->ConstructExpressionANOYMOUS_CLASS[RecordDeclaration]:::outer +``` +# MemberCallExpression +**Labels**:[Node](#enode) [Statement](#estatement) [Expression](#eexpression) [CallExpression](#ecallexpression) [MemberCallExpression](#emembercallexpression) +## Relationships +[CALLEE](#CallExpressionCALLEE) +[INVOKES](#CallExpressionINVOKES) +[TEMPLATE_INSTANTIATION](#CallExpressionTEMPLATE_INSTANTIATION) +[ARGUMENTS](#CallExpressionARGUMENTS) +[TEMPLATE_PARAMETERS](#CallExpressionTEMPLATE_PARAMETERS) +[POSSIBLE_SUB_TYPES](#ExpressionPOSSIBLE_SUB_TYPES) +[TYPE](#ExpressionTYPE) +[LOCALS](#StatementLOCALS) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +# DesignatedInitializerExpression +**Labels**:[Node](#enode) [Statement](#estatement) [Expression](#eexpression) [DesignatedInitializerExpression](#edesignatedinitializerexpression) +## Relationships +[LHS](#DesignatedInitializerExpressionLHS) +[RHS](#DesignatedInitializerExpressionRHS) +[POSSIBLE_SUB_TYPES](#ExpressionPOSSIBLE_SUB_TYPES) +[TYPE](#ExpressionTYPE) +[LOCALS](#StatementLOCALS) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +### LHS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +DesignatedInitializerExpression--"LHS*"-->DesignatedInitializerExpressionLHS[Expression]:::outer +``` +### RHS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +DesignatedInitializerExpression--"RHS¹"-->DesignatedInitializerExpressionRHS[Expression]:::outer +``` +# KeyValueExpression +**Labels**:[Node](#enode) [Statement](#estatement) [Expression](#eexpression) [KeyValueExpression](#ekeyvalueexpression) +## Relationships +[VALUE](#KeyValueExpressionVALUE) +[KEY](#KeyValueExpressionKEY) +[POSSIBLE_SUB_TYPES](#ExpressionPOSSIBLE_SUB_TYPES) +[TYPE](#ExpressionTYPE) +[LOCALS](#StatementLOCALS) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +### VALUE +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +KeyValueExpression--"VALUE¹"-->KeyValueExpressionVALUE[Expression]:::outer +``` +### KEY +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +KeyValueExpression--"KEY¹"-->KeyValueExpressionKEY[Expression]:::outer +``` +# AssignExpression +**Labels**:[Node](#enode) [Statement](#estatement) [Expression](#eexpression) [AssignExpression](#eassignexpression) +## Relationships +[DECLARATIONS](#AssignExpressionDECLARATIONS) +[LHS](#AssignExpressionLHS) +[RHS](#AssignExpressionRHS) +[POSSIBLE_SUB_TYPES](#ExpressionPOSSIBLE_SUB_TYPES) +[TYPE](#ExpressionTYPE) +[LOCALS](#StatementLOCALS) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +### DECLARATIONS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +AssignExpression--"DECLARATIONS*"-->AssignExpressionDECLARATIONS[VariableDeclaration]:::outer +``` +### LHS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +AssignExpression--"LHS*"-->AssignExpressionLHS[Expression]:::outer +``` +### RHS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +AssignExpression--"RHS*"-->AssignExpressionRHS[Expression]:::outer +``` +# CastExpression +**Labels**:[Node](#enode) [Statement](#estatement) [Expression](#eexpression) [CastExpression](#ecastexpression) +## Relationships +[CAST_TYPE](#CastExpressionCAST_TYPE) +[EXPRESSION](#CastExpressionEXPRESSION) +[POSSIBLE_SUB_TYPES](#ExpressionPOSSIBLE_SUB_TYPES) +[TYPE](#ExpressionTYPE) +[LOCALS](#StatementLOCALS) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +### CAST_TYPE +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +CastExpression--"CAST_TYPE¹"-->CastExpressionCAST_TYPE[Type]:::outer +``` +### EXPRESSION +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +CastExpression--"EXPRESSION¹"-->CastExpressionEXPRESSION[Expression]:::outer +``` +# ArrayCreationExpression +**Labels**:[Node](#enode) [Statement](#estatement) [Expression](#eexpression) [ArrayCreationExpression](#earraycreationexpression) +## Relationships +[INITIALIZER](#ArrayCreationExpressionINITIALIZER) +[DIMENSIONS](#ArrayCreationExpressionDIMENSIONS) +[POSSIBLE_SUB_TYPES](#ExpressionPOSSIBLE_SUB_TYPES) +[TYPE](#ExpressionTYPE) +[LOCALS](#StatementLOCALS) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +### INITIALIZER +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +ArrayCreationExpression--"INITIALIZER¹"-->ArrayCreationExpressionINITIALIZER[Expression]:::outer +``` +### DIMENSIONS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +ArrayCreationExpression--"DIMENSIONS*"-->ArrayCreationExpressionDIMENSIONS[Expression]:::outer +``` +# ArraySubscriptionExpression +**Labels**:[Node](#enode) [Statement](#estatement) [Expression](#eexpression) [ArraySubscriptionExpression](#earraysubscriptionexpression) +## Relationships +[ARRAY_EXPRESSION](#ArraySubscriptionExpressionARRAY_EXPRESSION) +[SUBSCRIPT_EXPRESSION](#ArraySubscriptionExpressionSUBSCRIPT_EXPRESSION) +[POSSIBLE_SUB_TYPES](#ExpressionPOSSIBLE_SUB_TYPES) +[TYPE](#ExpressionTYPE) +[LOCALS](#StatementLOCALS) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +### ARRAY_EXPRESSION +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +ArraySubscriptionExpression--"ARRAY_EXPRESSION¹"-->ArraySubscriptionExpressionARRAY_EXPRESSION[Expression]:::outer +``` +### SUBSCRIPT_EXPRESSION +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +ArraySubscriptionExpression--"SUBSCRIPT_EXPRESSION¹"-->ArraySubscriptionExpressionSUBSCRIPT_EXPRESSION[Expression]:::outer +``` +# TypeExpression +**Labels**:[Node](#enode) [Statement](#estatement) [Expression](#eexpression) [TypeExpression](#etypeexpression) +## Relationships +[POSSIBLE_SUB_TYPES](#ExpressionPOSSIBLE_SUB_TYPES) +[TYPE](#ExpressionTYPE) +[LOCALS](#StatementLOCALS) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +# BinaryOperator +**Labels**:[Node](#enode) [Statement](#estatement) [Expression](#eexpression) [BinaryOperator](#ebinaryoperator) +## Relationships +[LHS](#BinaryOperatorLHS) +[RHS](#BinaryOperatorRHS) +[POSSIBLE_SUB_TYPES](#ExpressionPOSSIBLE_SUB_TYPES) +[TYPE](#ExpressionTYPE) +[LOCALS](#StatementLOCALS) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +### LHS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +BinaryOperator--"LHS¹"-->BinaryOperatorLHS[Expression]:::outer +``` +### RHS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +BinaryOperator--"RHS¹"-->BinaryOperatorRHS[Expression]:::outer +``` +# ConditionalExpression +**Labels**:[Node](#enode) [Statement](#estatement) [Expression](#eexpression) [ConditionalExpression](#econditionalexpression) +## Relationships +[ELSE_EXPR](#ConditionalExpressionELSE_EXPR) +[THEN_EXPR](#ConditionalExpressionTHEN_EXPR) +[CONDITION](#ConditionalExpressionCONDITION) +[POSSIBLE_SUB_TYPES](#ExpressionPOSSIBLE_SUB_TYPES) +[TYPE](#ExpressionTYPE) +[LOCALS](#StatementLOCALS) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +### ELSE_EXPR +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +ConditionalExpression--"ELSE_EXPR¹"-->ConditionalExpressionELSE_EXPR[Expression]:::outer +``` +### THEN_EXPR +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +ConditionalExpression--"THEN_EXPR¹"-->ConditionalExpressionTHEN_EXPR[Expression]:::outer +``` +### CONDITION +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +ConditionalExpression--"CONDITION¹"-->ConditionalExpressionCONDITION[Expression]:::outer +``` +# DeclaredReferenceExpression +**Labels**:[Node](#enode) [Statement](#estatement) [Expression](#eexpression) [DeclaredReferenceExpression](#edeclaredreferenceexpression) +## Children +[MemberExpression](#ememberexpression) +## Relationships +[REFERS_TO](#DeclaredReferenceExpressionREFERS_TO) +[POSSIBLE_SUB_TYPES](#ExpressionPOSSIBLE_SUB_TYPES) +[TYPE](#ExpressionTYPE) +[LOCALS](#StatementLOCALS) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +### REFERS_TO +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +DeclaredReferenceExpression--"REFERS_TO¹"-->DeclaredReferenceExpressionREFERS_TO[Declaration]:::outer +``` +# MemberExpression +**Labels**:[Node](#enode) [Statement](#estatement) [Expression](#eexpression) [DeclaredReferenceExpression](#edeclaredreferenceexpression) [MemberExpression](#ememberexpression) +## Relationships +[BASE](#MemberExpressionBASE) +[REFERS_TO](#DeclaredReferenceExpressionREFERS_TO) +[POSSIBLE_SUB_TYPES](#ExpressionPOSSIBLE_SUB_TYPES) +[TYPE](#ExpressionTYPE) +[LOCALS](#StatementLOCALS) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +### BASE +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +MemberExpression--"BASE¹"-->MemberExpressionBASE[Expression]:::outer +``` +# InitializerListExpression +**Labels**:[Node](#enode) [Statement](#estatement) [Expression](#eexpression) [InitializerListExpression](#einitializerlistexpression) +## Relationships +[INITIALIZERS](#InitializerListExpressionINITIALIZERS) +[POSSIBLE_SUB_TYPES](#ExpressionPOSSIBLE_SUB_TYPES) +[TYPE](#ExpressionTYPE) +[LOCALS](#StatementLOCALS) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +### INITIALIZERS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +InitializerListExpression--"INITIALIZERS*"-->InitializerListExpressionINITIALIZERS[Expression]:::outer +``` +# DeleteExpression +**Labels**:[Node](#enode) [Statement](#estatement) [Expression](#eexpression) [DeleteExpression](#edeleteexpression) +## Relationships +[OPERAND](#DeleteExpressionOPERAND) +[POSSIBLE_SUB_TYPES](#ExpressionPOSSIBLE_SUB_TYPES) +[TYPE](#ExpressionTYPE) +[LOCALS](#StatementLOCALS) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +### OPERAND +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +DeleteExpression--"OPERAND¹"-->DeleteExpressionOPERAND[Expression]:::outer +``` +# CompoundStatementExpression +**Labels**:[Node](#enode) [Statement](#estatement) [Expression](#eexpression) [CompoundStatementExpression](#ecompoundstatementexpression) +## Relationships +[STATEMENT](#CompoundStatementExpressionSTATEMENT) +[POSSIBLE_SUB_TYPES](#ExpressionPOSSIBLE_SUB_TYPES) +[TYPE](#ExpressionTYPE) +[LOCALS](#StatementLOCALS) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +### STATEMENT +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +CompoundStatementExpression--"STATEMENT¹"-->CompoundStatementExpressionSTATEMENT[Statement]:::outer +``` +# ProblemExpression +**Labels**:[Node](#enode) [Statement](#estatement) [Expression](#eexpression) [ProblemExpression](#eproblemexpression) +## Relationships +[POSSIBLE_SUB_TYPES](#ExpressionPOSSIBLE_SUB_TYPES) +[TYPE](#ExpressionTYPE) +[LOCALS](#StatementLOCALS) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +# Literal +**Labels**:[Node](#enode) [Statement](#estatement) [Expression](#eexpression) [Literal](#eliteral) +## Relationships +[POSSIBLE_SUB_TYPES](#ExpressionPOSSIBLE_SUB_TYPES) +[TYPE](#ExpressionTYPE) +[LOCALS](#StatementLOCALS) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +# TypeIdExpression +**Labels**:[Node](#enode) [Statement](#estatement) [Expression](#eexpression) [TypeIdExpression](#etypeidexpression) +## Relationships +[REFERENCED_TYPE](#TypeIdExpressionREFERENCED_TYPE) +[POSSIBLE_SUB_TYPES](#ExpressionPOSSIBLE_SUB_TYPES) +[TYPE](#ExpressionTYPE) +[LOCALS](#StatementLOCALS) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +### REFERENCED_TYPE +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +TypeIdExpression--"REFERENCED_TYPE¹"-->TypeIdExpressionREFERENCED_TYPE[Type]:::outer +``` +# ExpressionList +**Labels**:[Node](#enode) [Statement](#estatement) [Expression](#eexpression) [ExpressionList](#eexpressionlist) +## Relationships +[SUBEXPR](#ExpressionListSUBEXPR) +[POSSIBLE_SUB_TYPES](#ExpressionPOSSIBLE_SUB_TYPES) +[TYPE](#ExpressionTYPE) +[LOCALS](#StatementLOCALS) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +### SUBEXPR +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +ExpressionList--"SUBEXPR*"-->ExpressionListSUBEXPR[Statement]:::outer +``` +# IfStatement +**Labels**:[Node](#enode) [Statement](#estatement) [IfStatement](#eifstatement) +## Relationships +[CONDITION_DECLARATION](#IfStatementCONDITION_DECLARATION) +[INITIALIZER_STATEMENT](#IfStatementINITIALIZER_STATEMENT) +[THEN_STATEMENT](#IfStatementTHEN_STATEMENT) +[CONDITION](#IfStatementCONDITION) +[ELSE_STATEMENT](#IfStatementELSE_STATEMENT) +[LOCALS](#StatementLOCALS) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +### CONDITION_DECLARATION +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +IfStatement--"CONDITION_DECLARATION¹"-->IfStatementCONDITION_DECLARATION[Declaration]:::outer +``` +### INITIALIZER_STATEMENT +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +IfStatement--"INITIALIZER_STATEMENT¹"-->IfStatementINITIALIZER_STATEMENT[Statement]:::outer +``` +### THEN_STATEMENT +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +IfStatement--"THEN_STATEMENT¹"-->IfStatementTHEN_STATEMENT[Statement]:::outer +``` +### CONDITION +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +IfStatement--"CONDITION¹"-->IfStatementCONDITION[Expression]:::outer +``` +### ELSE_STATEMENT +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +IfStatement--"ELSE_STATEMENT¹"-->IfStatementELSE_STATEMENT[Statement]:::outer +``` +# DeclarationStatement +**Labels**:[Node](#enode) [Statement](#estatement) [DeclarationStatement](#edeclarationstatement) +## Children +[ASMDeclarationStatement](#easmdeclarationstatement) +## Relationships +[DECLARATIONS](#DeclarationStatementDECLARATIONS) +[LOCALS](#StatementLOCALS) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +### DECLARATIONS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +DeclarationStatement--"DECLARATIONS*"-->DeclarationStatementDECLARATIONS[Declaration]:::outer +``` +# ASMDeclarationStatement +**Labels**:[Node](#enode) [Statement](#estatement) [DeclarationStatement](#edeclarationstatement) [ASMDeclarationStatement](#easmdeclarationstatement) +## Relationships +[DECLARATIONS](#DeclarationStatementDECLARATIONS) +[LOCALS](#StatementLOCALS) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +# ForStatement +**Labels**:[Node](#enode) [Statement](#estatement) [ForStatement](#eforstatement) +## Relationships +[CONDITION_DECLARATION](#ForStatementCONDITION_DECLARATION) +[INITIALIZER_STATEMENT](#ForStatementINITIALIZER_STATEMENT) +[ITERATION_STATEMENT](#ForStatementITERATION_STATEMENT) +[CONDITION](#ForStatementCONDITION) +[STATEMENT](#ForStatementSTATEMENT) +[LOCALS](#StatementLOCALS) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +### CONDITION_DECLARATION +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +ForStatement--"CONDITION_DECLARATION¹"-->ForStatementCONDITION_DECLARATION[Declaration]:::outer +``` +### INITIALIZER_STATEMENT +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +ForStatement--"INITIALIZER_STATEMENT¹"-->ForStatementINITIALIZER_STATEMENT[Statement]:::outer +``` +### ITERATION_STATEMENT +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +ForStatement--"ITERATION_STATEMENT¹"-->ForStatementITERATION_STATEMENT[Statement]:::outer +``` +### CONDITION +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +ForStatement--"CONDITION¹"-->ForStatementCONDITION[Expression]:::outer +``` +### STATEMENT +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +ForStatement--"STATEMENT¹"-->ForStatementSTATEMENT[Statement]:::outer +``` +# CatchClause +**Labels**:[Node](#enode) [Statement](#estatement) [CatchClause](#ecatchclause) +## Relationships +[PARAMETER](#CatchClausePARAMETER) +[BODY](#CatchClauseBODY) +[LOCALS](#StatementLOCALS) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +### PARAMETER +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +CatchClause--"PARAMETER¹"-->CatchClausePARAMETER[VariableDeclaration]:::outer +``` +### BODY +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +CatchClause--"BODY¹"-->CatchClauseBODY[CompoundStatement]:::outer +``` +# SwitchStatement +**Labels**:[Node](#enode) [Statement](#estatement) [SwitchStatement](#eswitchstatement) +## Relationships +[INITIALIZER_STATEMENT](#SwitchStatementINITIALIZER_STATEMENT) +[SELECTOR_DECLARATION](#SwitchStatementSELECTOR_DECLARATION) +[STATEMENT](#SwitchStatementSTATEMENT) +[SELECTOR](#SwitchStatementSELECTOR) +[LOCALS](#StatementLOCALS) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +### INITIALIZER_STATEMENT +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +SwitchStatement--"INITIALIZER_STATEMENT¹"-->SwitchStatementINITIALIZER_STATEMENT[Statement]:::outer +``` +### SELECTOR_DECLARATION +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +SwitchStatement--"SELECTOR_DECLARATION¹"-->SwitchStatementSELECTOR_DECLARATION[Declaration]:::outer +``` +### STATEMENT +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +SwitchStatement--"STATEMENT¹"-->SwitchStatementSTATEMENT[Statement]:::outer +``` +### SELECTOR +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +SwitchStatement--"SELECTOR¹"-->SwitchStatementSELECTOR[Expression]:::outer +``` +# GotoStatement +**Labels**:[Node](#enode) [Statement](#estatement) [GotoStatement](#egotostatement) +## Relationships +[TARGET_LABEL](#GotoStatementTARGET_LABEL) +[LOCALS](#StatementLOCALS) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +### TARGET_LABEL +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +GotoStatement--"TARGET_LABEL¹"-->GotoStatementTARGET_LABEL[LabelStatement]:::outer +``` +# WhileStatement +**Labels**:[Node](#enode) [Statement](#estatement) [WhileStatement](#ewhilestatement) +## Relationships +[CONDITION_DECLARATION](#WhileStatementCONDITION_DECLARATION) +[CONDITION](#WhileStatementCONDITION) +[STATEMENT](#WhileStatementSTATEMENT) +[LOCALS](#StatementLOCALS) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +### CONDITION_DECLARATION +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +WhileStatement--"CONDITION_DECLARATION¹"-->WhileStatementCONDITION_DECLARATION[Declaration]:::outer +``` +### CONDITION +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +WhileStatement--"CONDITION¹"-->WhileStatementCONDITION[Expression]:::outer +``` +### STATEMENT +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +WhileStatement--"STATEMENT¹"-->WhileStatementSTATEMENT[Statement]:::outer +``` +# CompoundStatement +**Labels**:[Node](#enode) [Statement](#estatement) [CompoundStatement](#ecompoundstatement) +## Relationships +[STATEMENTS](#CompoundStatementSTATEMENTS) +[LOCALS](#StatementLOCALS) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +### STATEMENTS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +CompoundStatement--"STATEMENTS*"-->CompoundStatementSTATEMENTS[Statement]:::outer +``` +# ContinueStatement +**Labels**:[Node](#enode) [Statement](#estatement) [ContinueStatement](#econtinuestatement) +## Relationships +[LOCALS](#StatementLOCALS) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +# DefaultStatement +**Labels**:[Node](#enode) [Statement](#estatement) [DefaultStatement](#edefaultstatement) +## Relationships +[LOCALS](#StatementLOCALS) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +# SynchronizedStatement +**Labels**:[Node](#enode) [Statement](#estatement) [SynchronizedStatement](#esynchronizedstatement) +## Relationships +[BLOCK_STATEMENT](#SynchronizedStatementBLOCK_STATEMENT) +[EXPRESSION](#SynchronizedStatementEXPRESSION) +[LOCALS](#StatementLOCALS) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +### BLOCK_STATEMENT +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +SynchronizedStatement--"BLOCK_STATEMENT¹"-->SynchronizedStatementBLOCK_STATEMENT[CompoundStatement]:::outer +``` +### EXPRESSION +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +SynchronizedStatement--"EXPRESSION¹"-->SynchronizedStatementEXPRESSION[Expression]:::outer +``` +# TryStatement +**Labels**:[Node](#enode) [Statement](#estatement) [TryStatement](#etrystatement) +## Relationships +[RESOURCES](#TryStatementRESOURCES) +[FINALLY_BLOCK](#TryStatementFINALLY_BLOCK) +[TRY_BLOCK](#TryStatementTRY_BLOCK) +[CATCH_CLAUSES](#TryStatementCATCH_CLAUSES) +[LOCALS](#StatementLOCALS) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +### RESOURCES +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +TryStatement--"RESOURCES*"-->TryStatementRESOURCES[Statement]:::outer +``` +### FINALLY_BLOCK +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +TryStatement--"FINALLY_BLOCK¹"-->TryStatementFINALLY_BLOCK[CompoundStatement]:::outer +``` +### TRY_BLOCK +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +TryStatement--"TRY_BLOCK¹"-->TryStatementTRY_BLOCK[CompoundStatement]:::outer +``` +### CATCH_CLAUSES +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +TryStatement--"CATCH_CLAUSES*"-->TryStatementCATCH_CLAUSES[CatchClause]:::outer +``` +# ForEachStatement +**Labels**:[Node](#enode) [Statement](#estatement) [ForEachStatement](#eforeachstatement) +## Relationships +[STATEMENT](#ForEachStatementSTATEMENT) +[VARIABLE](#ForEachStatementVARIABLE) +[ITERABLE](#ForEachStatementITERABLE) +[LOCALS](#StatementLOCALS) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +### STATEMENT +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +ForEachStatement--"STATEMENT¹"-->ForEachStatementSTATEMENT[Statement]:::outer +``` +### VARIABLE +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +ForEachStatement--"VARIABLE¹"-->ForEachStatementVARIABLE[Statement]:::outer +``` +### ITERABLE +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +ForEachStatement--"ITERABLE¹"-->ForEachStatementITERABLE[Statement]:::outer +``` +# LabelStatement +**Labels**:[Node](#enode) [Statement](#estatement) [LabelStatement](#elabelstatement) +## Relationships +[SUB_STATEMENT](#LabelStatementSUB_STATEMENT) +[LOCALS](#StatementLOCALS) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +### SUB_STATEMENT +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +LabelStatement--"SUB_STATEMENT¹"-->LabelStatementSUB_STATEMENT[Statement]:::outer +``` +# BreakStatement +**Labels**:[Node](#enode) [Statement](#estatement) [BreakStatement](#ebreakstatement) +## Relationships +[LOCALS](#StatementLOCALS) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +# EmptyStatement +**Labels**:[Node](#enode) [Statement](#estatement) [EmptyStatement](#eemptystatement) +## Relationships +[LOCALS](#StatementLOCALS) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +# Declaration +**Labels**:[Node](#enode) [Declaration](#edeclaration) +## Children +[ValueDeclaration](#evaluedeclaration) [TemplateDeclaration](#etemplatedeclaration) [EnumDeclaration](#eenumdeclaration) [TypedefDeclaration](#etypedefdeclaration) [UsingDirective](#eusingdirective) [NamespaceDeclaration](#enamespacedeclaration) [RecordDeclaration](#erecorddeclaration) [DeclarationSequence](#edeclarationsequence) [TranslationUnitDeclaration](#etranslationunitdeclaration) [IncludeDeclaration](#eincludedeclaration) +## Relationships +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +# ValueDeclaration +**Labels**:[Node](#enode) [Declaration](#edeclaration) [ValueDeclaration](#evaluedeclaration) +## Children +[FieldDeclaration](#efielddeclaration) [VariableDeclaration](#evariabledeclaration) [ProblemDeclaration](#eproblemdeclaration) [EnumConstantDeclaration](#eenumconstantdeclaration) [FunctionDeclaration](#efunctiondeclaration) [ParamVariableDeclaration](#eparamvariabledeclaration) [TypeParamDeclaration](#etypeparamdeclaration) +## Relationships +[POSSIBLE_SUB_TYPES](#ValueDeclarationPOSSIBLE_SUB_TYPES) +[TYPE](#ValueDeclarationTYPE) +[USAGE](#ValueDeclarationUSAGE) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +### POSSIBLE_SUB_TYPES +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +ValueDeclaration--"POSSIBLE_SUB_TYPES*"-->ValueDeclarationPOSSIBLE_SUB_TYPES[Type]:::outer +``` +### TYPE +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +ValueDeclaration--"TYPE¹"-->ValueDeclarationTYPE[Type]:::outer +``` +### USAGE +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +ValueDeclaration--"USAGE*"-->ValueDeclarationUSAGE[DeclaredReferenceExpression]:::outer +``` +# FieldDeclaration +**Labels**:[Node](#enode) [Declaration](#edeclaration) [ValueDeclaration](#evaluedeclaration) [FieldDeclaration](#efielddeclaration) +## Relationships +[INITIALIZER](#FieldDeclarationINITIALIZER) +[DEFINES](#FieldDeclarationDEFINES) +[POSSIBLE_SUB_TYPES](#ValueDeclarationPOSSIBLE_SUB_TYPES) +[TYPE](#ValueDeclarationTYPE) +[USAGE](#ValueDeclarationUSAGE) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +### INITIALIZER +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +FieldDeclaration--"INITIALIZER¹"-->FieldDeclarationINITIALIZER[Expression]:::outer +``` +### DEFINES +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +FieldDeclaration--"DEFINES¹"-->FieldDeclarationDEFINES[FieldDeclaration]:::outer +``` +# VariableDeclaration +**Labels**:[Node](#enode) [Declaration](#edeclaration) [ValueDeclaration](#evaluedeclaration) [VariableDeclaration](#evariabledeclaration) +## Relationships +[INITIALIZER](#VariableDeclarationINITIALIZER) +[TEMPLATE_PARAMETERS](#VariableDeclarationTEMPLATE_PARAMETERS) +[POSSIBLE_SUB_TYPES](#ValueDeclarationPOSSIBLE_SUB_TYPES) +[TYPE](#ValueDeclarationTYPE) +[USAGE](#ValueDeclarationUSAGE) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +### INITIALIZER +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +VariableDeclaration--"INITIALIZER¹"-->VariableDeclarationINITIALIZER[Expression]:::outer +``` +### TEMPLATE_PARAMETERS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +VariableDeclaration--"TEMPLATE_PARAMETERS*"-->VariableDeclarationTEMPLATE_PARAMETERS[Node]:::outer +``` +# ProblemDeclaration +**Labels**:[Node](#enode) [Declaration](#edeclaration) [ValueDeclaration](#evaluedeclaration) [ProblemDeclaration](#eproblemdeclaration) +## Relationships +[POSSIBLE_SUB_TYPES](#ValueDeclarationPOSSIBLE_SUB_TYPES) +[TYPE](#ValueDeclarationTYPE) +[USAGE](#ValueDeclarationUSAGE) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +# EnumConstantDeclaration +**Labels**:[Node](#enode) [Declaration](#edeclaration) [ValueDeclaration](#evaluedeclaration) [EnumConstantDeclaration](#eenumconstantdeclaration) +## Relationships +[INITIALIZER](#EnumConstantDeclarationINITIALIZER) +[POSSIBLE_SUB_TYPES](#ValueDeclarationPOSSIBLE_SUB_TYPES) +[TYPE](#ValueDeclarationTYPE) +[USAGE](#ValueDeclarationUSAGE) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +### INITIALIZER +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +EnumConstantDeclaration--"INITIALIZER¹"-->EnumConstantDeclarationINITIALIZER[Expression]:::outer +``` +# FunctionDeclaration +**Labels**:[Node](#enode) [Declaration](#edeclaration) [ValueDeclaration](#evaluedeclaration) [FunctionDeclaration](#efunctiondeclaration) +## Children +[MethodDeclaration](#emethoddeclaration) +## Relationships +[THROWS_TYPES](#FunctionDeclarationTHROWS_TYPES) +[OVERRIDES](#FunctionDeclarationOVERRIDES) +[BODY](#FunctionDeclarationBODY) +[RECORDS](#FunctionDeclarationRECORDS) +[RETURN_TYPES](#FunctionDeclarationRETURN_TYPES) +[PARAMETERS](#FunctionDeclarationPARAMETERS) +[DEFINES](#FunctionDeclarationDEFINES) +[POSSIBLE_SUB_TYPES](#ValueDeclarationPOSSIBLE_SUB_TYPES) +[TYPE](#ValueDeclarationTYPE) +[USAGE](#ValueDeclarationUSAGE) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +### THROWS_TYPES +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +FunctionDeclaration--"THROWS_TYPES*"-->FunctionDeclarationTHROWS_TYPES[Type]:::outer +``` +### OVERRIDES +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +FunctionDeclaration--"OVERRIDES*"-->FunctionDeclarationOVERRIDES[FunctionDeclaration]:::outer +``` +### BODY +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +FunctionDeclaration--"BODY¹"-->FunctionDeclarationBODY[Statement]:::outer +``` +### RECORDS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +FunctionDeclaration--"RECORDS*"-->FunctionDeclarationRECORDS[RecordDeclaration]:::outer +``` +### RETURN_TYPES +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +FunctionDeclaration--"RETURN_TYPES*"-->FunctionDeclarationRETURN_TYPES[Type]:::outer +``` +### PARAMETERS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +FunctionDeclaration--"PARAMETERS*"-->FunctionDeclarationPARAMETERS[ParamVariableDeclaration]:::outer +``` +### DEFINES +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +FunctionDeclaration--"DEFINES¹"-->FunctionDeclarationDEFINES[FunctionDeclaration]:::outer +``` +# MethodDeclaration +**Labels**:[Node](#enode) [Declaration](#edeclaration) [ValueDeclaration](#evaluedeclaration) [FunctionDeclaration](#efunctiondeclaration) [MethodDeclaration](#emethoddeclaration) +## Children +[ConstructorDeclaration](#econstructordeclaration) +## Relationships +[RECEIVER](#MethodDeclarationRECEIVER) +[RECORD_DECLARATION](#MethodDeclarationRECORD_DECLARATION) +[THROWS_TYPES](#FunctionDeclarationTHROWS_TYPES) +[OVERRIDES](#FunctionDeclarationOVERRIDES) +[BODY](#FunctionDeclarationBODY) +[RECORDS](#FunctionDeclarationRECORDS) +[RETURN_TYPES](#FunctionDeclarationRETURN_TYPES) +[PARAMETERS](#FunctionDeclarationPARAMETERS) +[DEFINES](#FunctionDeclarationDEFINES) +[POSSIBLE_SUB_TYPES](#ValueDeclarationPOSSIBLE_SUB_TYPES) +[TYPE](#ValueDeclarationTYPE) +[USAGE](#ValueDeclarationUSAGE) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +### RECEIVER +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +MethodDeclaration--"RECEIVER¹"-->MethodDeclarationRECEIVER[VariableDeclaration]:::outer +``` +### RECORD_DECLARATION +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +MethodDeclaration--"RECORD_DECLARATION¹"-->MethodDeclarationRECORD_DECLARATION[RecordDeclaration]:::outer +``` +# ConstructorDeclaration +**Labels**:[Node](#enode) [Declaration](#edeclaration) [ValueDeclaration](#evaluedeclaration) [FunctionDeclaration](#efunctiondeclaration) [MethodDeclaration](#emethoddeclaration) [ConstructorDeclaration](#econstructordeclaration) +## Relationships +[RECEIVER](#MethodDeclarationRECEIVER) +[RECORD_DECLARATION](#MethodDeclarationRECORD_DECLARATION) +[THROWS_TYPES](#FunctionDeclarationTHROWS_TYPES) +[OVERRIDES](#FunctionDeclarationOVERRIDES) +[BODY](#FunctionDeclarationBODY) +[RECORDS](#FunctionDeclarationRECORDS) +[RETURN_TYPES](#FunctionDeclarationRETURN_TYPES) +[PARAMETERS](#FunctionDeclarationPARAMETERS) +[DEFINES](#FunctionDeclarationDEFINES) +[POSSIBLE_SUB_TYPES](#ValueDeclarationPOSSIBLE_SUB_TYPES) +[TYPE](#ValueDeclarationTYPE) +[USAGE](#ValueDeclarationUSAGE) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +# ParamVariableDeclaration +**Labels**:[Node](#enode) [Declaration](#edeclaration) [ValueDeclaration](#evaluedeclaration) [ParamVariableDeclaration](#eparamvariabledeclaration) +## Relationships +[DEFAULT](#ParamVariableDeclarationDEFAULT) +[POSSIBLE_SUB_TYPES](#ValueDeclarationPOSSIBLE_SUB_TYPES) +[TYPE](#ValueDeclarationTYPE) +[USAGE](#ValueDeclarationUSAGE) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +### DEFAULT +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +ParamVariableDeclaration--"DEFAULT¹"-->ParamVariableDeclarationDEFAULT[Expression]:::outer +``` +# TypeParamDeclaration +**Labels**:[Node](#enode) [Declaration](#edeclaration) [ValueDeclaration](#evaluedeclaration) [TypeParamDeclaration](#etypeparamdeclaration) +## Relationships +[DEFAULT](#TypeParamDeclarationDEFAULT) +[POSSIBLE_SUB_TYPES](#ValueDeclarationPOSSIBLE_SUB_TYPES) +[TYPE](#ValueDeclarationTYPE) +[USAGE](#ValueDeclarationUSAGE) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +### DEFAULT +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +TypeParamDeclaration--"DEFAULT¹"-->TypeParamDeclarationDEFAULT[Type]:::outer +``` +# TemplateDeclaration +**Labels**:[Node](#enode) [Declaration](#edeclaration) [TemplateDeclaration](#etemplatedeclaration) +## Children +[ClassTemplateDeclaration](#eclasstemplatedeclaration) [FunctionTemplateDeclaration](#efunctiontemplatedeclaration) +## Relationships +[PARAMETERS](#TemplateDeclarationPARAMETERS) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +### PARAMETERS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +TemplateDeclaration--"PARAMETERS*"-->TemplateDeclarationPARAMETERS[Declaration]:::outer +``` +# ClassTemplateDeclaration +**Labels**:[Node](#enode) [Declaration](#edeclaration) [TemplateDeclaration](#etemplatedeclaration) [ClassTemplateDeclaration](#eclasstemplatedeclaration) +## Relationships +[REALIZATION](#ClassTemplateDeclarationREALIZATION) +[PARAMETERS](#TemplateDeclarationPARAMETERS) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +### REALIZATION +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +ClassTemplateDeclaration--"REALIZATION*"-->ClassTemplateDeclarationREALIZATION[RecordDeclaration]:::outer +``` +# FunctionTemplateDeclaration +**Labels**:[Node](#enode) [Declaration](#edeclaration) [TemplateDeclaration](#etemplatedeclaration) [FunctionTemplateDeclaration](#efunctiontemplatedeclaration) +## Relationships +[REALIZATION](#FunctionTemplateDeclarationREALIZATION) +[PARAMETERS](#TemplateDeclarationPARAMETERS) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +### REALIZATION +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +FunctionTemplateDeclaration--"REALIZATION*"-->FunctionTemplateDeclarationREALIZATION[FunctionDeclaration]:::outer +``` +# EnumDeclaration +**Labels**:[Node](#enode) [Declaration](#edeclaration) [EnumDeclaration](#eenumdeclaration) +## Relationships +[ENTRIES](#EnumDeclarationENTRIES) +[SUPER_TYPE_DECLARATIONS](#EnumDeclarationSUPER_TYPE_DECLARATIONS) +[SUPER_TYPES](#EnumDeclarationSUPER_TYPES) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +### ENTRIES +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +EnumDeclaration--"ENTRIES*"-->EnumDeclarationENTRIES[EnumConstantDeclaration]:::outer +``` +### SUPER_TYPE_DECLARATIONS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +EnumDeclaration--"SUPER_TYPE_DECLARATIONS*"-->EnumDeclarationSUPER_TYPE_DECLARATIONS[RecordDeclaration]:::outer +``` +### SUPER_TYPES +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +EnumDeclaration--"SUPER_TYPES*"-->EnumDeclarationSUPER_TYPES[Type]:::outer +``` +# TypedefDeclaration +**Labels**:[Node](#enode) [Declaration](#edeclaration) [TypedefDeclaration](#etypedefdeclaration) +## Relationships +[ALIAS](#TypedefDeclarationALIAS) +[TYPE](#TypedefDeclarationTYPE) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +### ALIAS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +TypedefDeclaration--"ALIAS¹"-->TypedefDeclarationALIAS[Type]:::outer +``` +### TYPE +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +TypedefDeclaration--"TYPE¹"-->TypedefDeclarationTYPE[Type]:::outer +``` +# UsingDirective +**Labels**:[Node](#enode) [Declaration](#edeclaration) [UsingDirective](#eusingdirective) +## Relationships +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +# NamespaceDeclaration +**Labels**:[Node](#enode) [Declaration](#edeclaration) [NamespaceDeclaration](#enamespacedeclaration) +## Relationships +[STATEMENTS](#NamespaceDeclarationSTATEMENTS) +[DECLARATIONS](#NamespaceDeclarationDECLARATIONS) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +### STATEMENTS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +NamespaceDeclaration--"STATEMENTS*"-->NamespaceDeclarationSTATEMENTS[Statement]:::outer +``` +### DECLARATIONS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +NamespaceDeclaration--"DECLARATIONS*"-->NamespaceDeclarationDECLARATIONS[Declaration]:::outer +``` +# RecordDeclaration +**Labels**:[Node](#enode) [Declaration](#edeclaration) [RecordDeclaration](#erecorddeclaration) +## Relationships +[IMPORTS](#RecordDeclarationIMPORTS) +[CONSTRUCTORS](#RecordDeclarationCONSTRUCTORS) +[FIELDS](#RecordDeclarationFIELDS) +[TEMPLATES](#RecordDeclarationTEMPLATES) +[STATIC_IMPORTS](#RecordDeclarationSTATIC_IMPORTS) +[RECORDS](#RecordDeclarationRECORDS) +[SUPER_TYPE_DECLARATIONS](#RecordDeclarationSUPER_TYPE_DECLARATIONS) +[STATEMENTS](#RecordDeclarationSTATEMENTS) +[METHODS](#RecordDeclarationMETHODS) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +### IMPORTS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +RecordDeclaration--"IMPORTS*"-->RecordDeclarationIMPORTS[Declaration]:::outer +``` +### CONSTRUCTORS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +RecordDeclaration--"CONSTRUCTORS*"-->RecordDeclarationCONSTRUCTORS[ConstructorDeclaration]:::outer +``` +### FIELDS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +RecordDeclaration--"FIELDS*"-->RecordDeclarationFIELDS[FieldDeclaration]:::outer +``` +### TEMPLATES +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +RecordDeclaration--"TEMPLATES*"-->RecordDeclarationTEMPLATES[TemplateDeclaration]:::outer +``` +### STATIC_IMPORTS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +RecordDeclaration--"STATIC_IMPORTS*"-->RecordDeclarationSTATIC_IMPORTS[ValueDeclaration]:::outer +``` +### RECORDS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +RecordDeclaration--"RECORDS*"-->RecordDeclarationRECORDS[RecordDeclaration]:::outer +``` +### SUPER_TYPE_DECLARATIONS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +RecordDeclaration--"SUPER_TYPE_DECLARATIONS*"-->RecordDeclarationSUPER_TYPE_DECLARATIONS[RecordDeclaration]:::outer +``` +### STATEMENTS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +RecordDeclaration--"STATEMENTS*"-->RecordDeclarationSTATEMENTS[Statement]:::outer +``` +### METHODS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +RecordDeclaration--"METHODS*"-->RecordDeclarationMETHODS[MethodDeclaration]:::outer +``` +# DeclarationSequence +**Labels**:[Node](#enode) [Declaration](#edeclaration) [DeclarationSequence](#edeclarationsequence) +## Relationships +[CHILDREN](#DeclarationSequenceCHILDREN) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +### CHILDREN +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +DeclarationSequence--"CHILDREN*"-->DeclarationSequenceCHILDREN[Declaration]:::outer +``` +# TranslationUnitDeclaration +**Labels**:[Node](#enode) [Declaration](#edeclaration) [TranslationUnitDeclaration](#etranslationunitdeclaration) +## Relationships +[NAMESPACES](#TranslationUnitDeclarationNAMESPACES) +[DECLARATIONS](#TranslationUnitDeclarationDECLARATIONS) +[STATEMENTS](#TranslationUnitDeclarationSTATEMENTS) +[INCLUDES](#TranslationUnitDeclarationINCLUDES) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +### NAMESPACES +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +TranslationUnitDeclaration--"NAMESPACES*"-->TranslationUnitDeclarationNAMESPACES[NamespaceDeclaration]:::outer +``` +### DECLARATIONS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +TranslationUnitDeclaration--"DECLARATIONS*"-->TranslationUnitDeclarationDECLARATIONS[Declaration]:::outer +``` +### STATEMENTS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +TranslationUnitDeclaration--"STATEMENTS*"-->TranslationUnitDeclarationSTATEMENTS[Statement]:::outer +``` +### INCLUDES +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +TranslationUnitDeclaration--"INCLUDES*"-->TranslationUnitDeclarationINCLUDES[IncludeDeclaration]:::outer +``` +# IncludeDeclaration +**Labels**:[Node](#enode) [Declaration](#edeclaration) [IncludeDeclaration](#eincludedeclaration) +## Relationships +[INCLUDES](#IncludeDeclarationINCLUDES) +[PROBLEMS](#IncludeDeclarationPROBLEMS) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +### INCLUDES +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +IncludeDeclaration--"INCLUDES*"-->IncludeDeclarationINCLUDES[IncludeDeclaration]:::outer +``` +### PROBLEMS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +IncludeDeclaration--"PROBLEMS*"-->IncludeDeclarationPROBLEMS[ProblemDeclaration]:::outer +``` +# Type +**Labels**:[Node](#enode) [Type](#etype) +## Children +[UnknownType](#eunknowntype) [ObjectType](#eobjecttype) [ParameterizedType](#eparameterizedtype) [PointerType](#epointertype) [FunctionPointerType](#efunctionpointertype) [TupleType](#etupletype) [IncompleteType](#eincompletetype) [ReferenceType](#ereferencetype) [FunctionType](#efunctiontype) +## Relationships +[SUPER_TYPE](#TypeSUPER_TYPE) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +### SUPER_TYPE +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +Type--"SUPER_TYPE*"-->TypeSUPER_TYPE[Type]:::outer +``` +# UnknownType +**Labels**:[Node](#enode) [Type](#etype) [UnknownType](#eunknowntype) +## Relationships +[SUPER_TYPE](#TypeSUPER_TYPE) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +# ObjectType +**Labels**:[Node](#enode) [Type](#etype) [ObjectType](#eobjecttype) +## Children +[NumericType](#enumerictype) [StringType](#estringtype) +## Relationships +[GENERICS](#ObjectTypeGENERICS) +[RECORD_DECLARATION](#ObjectTypeRECORD_DECLARATION) +[SUPER_TYPE](#TypeSUPER_TYPE) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +### GENERICS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +ObjectType--"GENERICS*"-->ObjectTypeGENERICS[Type]:::outer +``` +### RECORD_DECLARATION +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +ObjectType--"RECORD_DECLARATION¹"-->ObjectTypeRECORD_DECLARATION[RecordDeclaration]:::outer +``` +# NumericType +**Labels**:[Node](#enode) [Type](#etype) [ObjectType](#eobjecttype) [NumericType](#enumerictype) +## Children +[IntegerType](#eintegertype) [FloatingPointType](#efloatingpointtype) [BooleanType](#ebooleantype) +## Relationships +[GENERICS](#ObjectTypeGENERICS) +[RECORD_DECLARATION](#ObjectTypeRECORD_DECLARATION) +[SUPER_TYPE](#TypeSUPER_TYPE) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +# IntegerType +**Labels**:[Node](#enode) [Type](#etype) [ObjectType](#eobjecttype) [NumericType](#enumerictype) [IntegerType](#eintegertype) +## Relationships +[GENERICS](#ObjectTypeGENERICS) +[RECORD_DECLARATION](#ObjectTypeRECORD_DECLARATION) +[SUPER_TYPE](#TypeSUPER_TYPE) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +# FloatingPointType +**Labels**:[Node](#enode) [Type](#etype) [ObjectType](#eobjecttype) [NumericType](#enumerictype) [FloatingPointType](#efloatingpointtype) +## Relationships +[GENERICS](#ObjectTypeGENERICS) +[RECORD_DECLARATION](#ObjectTypeRECORD_DECLARATION) +[SUPER_TYPE](#TypeSUPER_TYPE) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +# BooleanType +**Labels**:[Node](#enode) [Type](#etype) [ObjectType](#eobjecttype) [NumericType](#enumerictype) [BooleanType](#ebooleantype) +## Relationships +[GENERICS](#ObjectTypeGENERICS) +[RECORD_DECLARATION](#ObjectTypeRECORD_DECLARATION) +[SUPER_TYPE](#TypeSUPER_TYPE) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +# StringType +**Labels**:[Node](#enode) [Type](#etype) [ObjectType](#eobjecttype) [StringType](#estringtype) +## Relationships +[GENERICS](#ObjectTypeGENERICS) +[RECORD_DECLARATION](#ObjectTypeRECORD_DECLARATION) +[SUPER_TYPE](#TypeSUPER_TYPE) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +# ParameterizedType +**Labels**:[Node](#enode) [Type](#etype) [ParameterizedType](#eparameterizedtype) +## Relationships +[SUPER_TYPE](#TypeSUPER_TYPE) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +# PointerType +**Labels**:[Node](#enode) [Type](#etype) [PointerType](#epointertype) +## Relationships +[ELEMENT_TYPE](#PointerTypeELEMENT_TYPE) +[SUPER_TYPE](#TypeSUPER_TYPE) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +### ELEMENT_TYPE +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +PointerType--"ELEMENT_TYPE¹"-->PointerTypeELEMENT_TYPE[Type]:::outer +``` +# FunctionPointerType +**Labels**:[Node](#enode) [Type](#etype) [FunctionPointerType](#efunctionpointertype) +## Relationships +[PARAMETERS](#FunctionPointerTypePARAMETERS) +[RETURN_TYPE](#FunctionPointerTypeRETURN_TYPE) +[SUPER_TYPE](#TypeSUPER_TYPE) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +### PARAMETERS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +FunctionPointerType--"PARAMETERS*"-->FunctionPointerTypePARAMETERS[Type]:::outer +``` +### RETURN_TYPE +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +FunctionPointerType--"RETURN_TYPE¹"-->FunctionPointerTypeRETURN_TYPE[Type]:::outer +``` +# TupleType +**Labels**:[Node](#enode) [Type](#etype) [TupleType](#etupletype) +## Relationships +[TYPES](#TupleTypeTYPES) +[SUPER_TYPE](#TypeSUPER_TYPE) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +### TYPES +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +TupleType--"TYPES*"-->TupleTypeTYPES[Type]:::outer +``` +# IncompleteType +**Labels**:[Node](#enode) [Type](#etype) [IncompleteType](#eincompletetype) +## Relationships +[SUPER_TYPE](#TypeSUPER_TYPE) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +# ReferenceType +**Labels**:[Node](#enode) [Type](#etype) [ReferenceType](#ereferencetype) +## Relationships +[REFERENCE](#ReferenceTypeREFERENCE) +[SUPER_TYPE](#TypeSUPER_TYPE) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +### REFERENCE +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +ReferenceType--"REFERENCE¹"-->ReferenceTypeREFERENCE[Type]:::outer +``` +# FunctionType +**Labels**:[Node](#enode) [Type](#etype) [FunctionType](#efunctiontype) +## Relationships +[RETURN_TYPES](#FunctionTypeRETURN_TYPES) +[PARAMETERS](#FunctionTypePARAMETERS) +[SUPER_TYPE](#TypeSUPER_TYPE) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +### RETURN_TYPES +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +FunctionType--"RETURN_TYPES*"-->FunctionTypeRETURN_TYPES[Type]:::outer +``` +### PARAMETERS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +FunctionType--"PARAMETERS*"-->FunctionTypePARAMETERS[Type]:::outer +``` +# AnnotationMember +**Labels**:[Node](#enode) [AnnotationMember](#eannotationmember) +## Relationships +[VALUE](#AnnotationMemberVALUE) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +### VALUE +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +AnnotationMember--"VALUE¹"-->AnnotationMemberVALUE[Expression]:::outer +``` +# Component +**Labels**:[Node](#enode) [Component](#ecomponent) +## Relationships +[OUTGOING_INTERACTIONS](#ComponentOUTGOING_INTERACTIONS) +[INCOMING_INTERACTIONS](#ComponentINCOMING_INTERACTIONS) +[TRANSLATION_UNITS](#ComponentTRANSLATION_UNITS) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +### OUTGOING_INTERACTIONS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +Component--"OUTGOING_INTERACTIONS*"-->ComponentOUTGOING_INTERACTIONS[Node]:::outer +``` +### INCOMING_INTERACTIONS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +Component--"INCOMING_INTERACTIONS*"-->ComponentINCOMING_INTERACTIONS[Node]:::outer +``` +### TRANSLATION_UNITS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +Component--"TRANSLATION_UNITS*"-->ComponentTRANSLATION_UNITS[TranslationUnitDeclaration]:::outer +``` +# Annotation +**Labels**:[Node](#enode) [Annotation](#eannotation) +## Relationships +[MEMBERS](#AnnotationMEMBERS) +[DFG](#NodeDFG) +[EOG](#NodeEOG) +[ANNOTATIONS](#NodeANNOTATIONS) +[AST](#NodeAST) +[SCOPE](#NodeSCOPE) +[TYPEDEFS](#NodeTYPEDEFS) +### MEMBERS +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; +Annotation--"MEMBERS*"-->AnnotationMEMBERS[AnnotationMember]:::outer +``` diff --git a/docs/docs/Contributing/index.md b/docs/docs/Contributing/index.md new file mode 100644 index 0000000000..b072496082 --- /dev/null +++ b/docs/docs/Contributing/index.md @@ -0,0 +1,156 @@ +--- +title: "Contributing" +linkTitle: "Contributing" +no_list: true +weight: 1 +date: 2017-01-05 +description: > + Contributing +--- + +## Prerequsites + +* git +* Java 17 (OpenSDK) + +## Build and Run + +### Getting the source + +First, create a fork of this repository and clone the fork: + +``` +git clone https://github.com/<<>>/TODO.git +``` + +Add the upstream repository as a second remote, so you can incorporate upstream changes into your fork: + +``` +git remote add upstream https://github.com/Fraunhofer-AISEC/cpg.git +``` + +### Build + +Make sure you can build the repository + +``` +./gradlew clean spotlessApply build publishToMavenLocal +``` + +This project requires Java 17. If Java 17 is not your default Java version, make sure to configure gradle to use it by setting its java.home variable: + +``` +./gradlew -Dorg.gradle.java.home="/usr/lib/jvm/java-17-openjdk-amd64/" build +``` + +## Copyright Notice + +This project has the convention of including a license notice header in all source files: +```java +/* + * Copyright (c) 2020, 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. + * + * $$$$$$\ $$$$$$$\ $$$$$$\ + * $$ __$$\ $$ __$$\ $$ __$$\ + * $$ / \__|$$ | $$ |$$ / \__| + * $$ | $$$$$$$ |$$ |$$$$\ + * $$ | $$ ____/ $$ |\_$$ | + * $$ | $$\ $$ | $$ | $$ | + * \$$$$$ |$$ | \$$$$$ | + * \______/ \__| \______/ + * + */ +``` + +If you are using IntelliJ IDEA, you can import `style/copyright.xml` as a copyright profile to automate the header creation process. +Click [here](https://www.jetbrains.com/help/idea/copyright.html) for further information on copyright profiles. + +## Code Guidelines + +Most of our code is written in Kotlin and if you develop new nodes, one should follow the following guidelines. + +### Property Edges + +On some edges, we want to store additional information (e.g., if a `EOG` node is "unreachable"). In this case, a simple list of nodes for a `@Relationship` is not enough and instead a list of `PropertyEdge` objects is needed. To have a consistent naming, the property holding the edges should be named the singular of the property name + "Edges", e.g. `parameterEdges`. To make it more convenient for users to also access the connected nodes without property edges, the Kotlin delegation feature, with a `PropertyEdgeDelegate` can be used. This property should then be named after the property (plural), e.g. `parameters`. + +```kotlin +/** The list of function parameters. */ +@Relationship(value = "PARAMETERS", direction = Relationship.Direction.OUTGOING) +@field:SubGraph("AST") +var parameterEdges = mutableListOf>() + +/** Virtual property for accessing [parameterEdges] without property edges. */ +var parameters by PropertyEdgeDelegate(FunctionDeclaration::parameterEdges) +``` + +Note: We actually want list property to be immutable so that they can only be modified by the node class itself. However, it is currently not possible to have them immutable on the public getter, but mutable for the class itself. There is a Kotlin issue tracking this feature request. Once https://youtrack.jetbrains.com/issue/KT-14663 is resolved, we should set the public type for all those lists to `List` instead of `MutableList`. Properties delegated by `PropertyEdgeDelegate` are already immutable. + +### Required Properties + +Properties which can be considered as a required part of an expression, should be non-nullable and be initialized to a `ProblemNode`. In this case we can represent parsing problems in the graph and still avoid too many null checks. For example in the `MemberExpression`: +```kotlin +var base: Expression = ProblemExpression("could not parse base expression") +``` + +There might be cases, where either one or the other property might be required (if a property can either be an `Expression` or a `Declaration`, in this case we need to resort of having both properties nullable. + +Note: In the future, we might move required properties into the constructor of a node. + +### `equals` and `hashCode` + +Because of the special nature of the `PropertyEdge`, one needs to be careful in comparing them in `equals`, to avoid stack overflows. Therefore, the special function `propertyEqualsList` needs to be used: +```kotlin +return (super.equals(other) && + parameters == other.parameters && + propertyEqualsList(parameterEdges, other.parameterEdges) +``` + +`hashCode` needs to include all properties that are also compared in `equals`. For easier readability, we should use the Kotlin expression body feature: +```kotlin +override fun hashCode() = Objects.hash(super.hashCode(), constructor, arguments) +``` + +## Pull Requests + +Before we can accept a pull request from you, you'll need to sign a Contributor License Agreement (CLA). It is an automated process and you only need to do it once. + +To enable us to quickly review and accept your pull requests, always create one pull request per issue and link the issue in the pull request. +Never merge multiple requests in one unless they have the same root cause. Be sure your code is formatted correctly using the respective formatting task. +Keep code changes as small as possible. +Pull requests should contain tests whenever possible. + +### Change-Log +Every PR that changes the graph or interaction with one of the classes that run the analysis has to be documented in the changelog. For this, one should add the appropriated change type (added, changed, removed) under the heading of the thematic change (Graph-changes, Interface-changes). Fixes for specific issues should also be mentioned but their inclusion in the release changelog is optional. An example of a PR-changelog: + +#### Graph-changes +##### Added +* New node `A` with edges of name `B` and `C` to its ast-children. +##### Changed +* Property of Node `A` that describes the name changed from `name` to `simple-name`. +#### Interface-changes +##### Added +* function `loadIncludes` which persists nodes to the graph comming from in-file includes. + +## Language + +Please stick to English for all discussions and comments. This helps to make the project accessible for a larger audience. + +## Publishing + +To publish a release, push a tag that contains the version number beginning with `v`, i.e. `v2.0.0`. The GitHub Actions workflow will then automatically build a release zip and create a GitHub release. Afterwards it would be good to adjust the release text to include a minimal changelog. + +### Versioning +The versioning number is split up in major, minor and bugfix releases: `major.minor.bugfix`. Most releases will have the form `major.minor.0`, and bugfixes will be either included in a future version, and the bugfix release number will only be used to ship bug fixes for older versions when necessary. + diff --git a/docs/docs/GettingStarted/cli.md b/docs/docs/GettingStarted/cli.md new file mode 100755 index 0000000000..9c8588360e --- /dev/null +++ b/docs/docs/GettingStarted/cli.md @@ -0,0 +1,10 @@ +--- +title: "Using the Interactive CLI" +linkTitle: "Using the Interactive CLI" +no_list: true +weight: 2 +date: 2020-01-30 +description: > + Using the Interactive CLI (cpg-console) +--- + diff --git a/docs/docs/GettingStarted/index.md b/docs/docs/GettingStarted/index.md new file mode 100644 index 0000000000..bcb90b7493 --- /dev/null +++ b/docs/docs/GettingStarted/index.md @@ -0,0 +1,19 @@ +--- +title: "Getting Started" +linkTitle: "Getting Started" +no_list: true +weight: 2 +date: 2020-01-30 +description: > + In CLI mode, Codyze integrates into scripts and automated build processes. +--- + + +# Getting Started + +After [installing the library](./installation), it can be used in different ways: + +* [As a library for Kotlin/Java](./library) +* [Via an interactive command line interface](./cli) +* [With custom automated analyses using the Query API](./query) + diff --git a/docs/docs/GettingStarted/installation.md b/docs/docs/GettingStarted/installation.md new file mode 100755 index 0000000000..ed0e189a88 --- /dev/null +++ b/docs/docs/GettingStarted/installation.md @@ -0,0 +1,29 @@ +--- +title: "Installing the CPG library" +linkTitle: "Installing the CPG library" +no_list: true +weight: 1 +date: 2017-01-05 +description: > + Installing the CPG as a library +--- + + +You can install the library from pre-built releases or build it from the source +code. + +## Get Pre-Built Releases + +You can find the releases in our [github +repository](https://github.com/Fraunhofer-AISEC/cpg/releases) or on +[maven](https://mvnrepository.com/artifact/de.fraunhofer.aisec/cpg). + +## Building from Source + +1. Clone the repository from GitHub with `git clone git@github.com:Fraunhofer-AISEC/cpg.git`. +2. Generate a `gradle.properties` file locally. We provide a sample file + [here](https://github.com/Fraunhofer-AISEC/cpg/blob/main/gradle.properties.example) + or you can use the `configure_frontends.sh` scripts to generate the file. +3. Build the project using `./gradlew build` or install it with + `./gradlew installDist`. You could also build selected submodules. + diff --git a/docs/docs/GettingStarted/library.md b/docs/docs/GettingStarted/library.md new file mode 100644 index 0000000000..00e3f89821 --- /dev/null +++ b/docs/docs/GettingStarted/library.md @@ -0,0 +1,77 @@ +--- +title: "Usage as library" +linkTitle: "Usage as library" +no_list: true +weight: 1 +date: 2017-01-05 +description: > + Usage as library +--- + +You can use the CPG library in your kotlin project. + +## 1. Add the CPG library to your dependencies + +First, get the required dependencies, e.g. by installing either the whole +project or selected submodules from mavencentral. +Here's an excerpt from a `build.gradle.kts` file: +```kotlin +... +repositories { + mavenCentral() + ... +} + +dependencies { + implementation("de.fraunhofer.aisec:cpg:6.2.1") // Install everything + // OR + implementation("de.fraunhofer.aisec:cpg-core:6.2.1") // Only cpg-core + implementation("de.fraunhofer.aisec:cpg-language-java:6.2.1") // Only the java language frontend + ... +} +``` + +## 2. Configuring the translation + +Before constructing the CPG, you have to configure how you want to translate the +code to the CPG. You have to use the `TranslationConfiguration` and the +`InferenceConfiguration`. It allows you to specify which frontends, and passes +you want to use and can steer some analyses. + +The following lines give you a small example: +```kotlin +val inferenceConfig = InferenceConfiguration + .builder() + .guessCastExpression(true) + .inferRecords(true) + .inferDfgForUnresolvedSymbols(true) + .build() + +val translationConfig = TranslationConfiguration + .builder() + .inferenceConfiguration(inferenceConfig) + .defaultPasses() + .registerPass() + .registerFrontend() + .sourceLocations(filePaths) + .build() +``` + +For a complete list of available methods, please check the KDoc. + +## 3. Running the analysis + +Now it's time to get the CPG. All you have to do is to run the analysis with the +given configuration. +```kotlin +val translationResult = TranslationManager + .builder() + .config(translationConfig) + .build() + .analyze() + .get() +``` + +The CPG is available in the `translationResult`. You can now run analyses or +explore the graph. + diff --git a/docs/docs/GettingStarted/query.md b/docs/docs/GettingStarted/query.md new file mode 100755 index 0000000000..eb47ed1b58 --- /dev/null +++ b/docs/docs/GettingStarted/query.md @@ -0,0 +1,169 @@ +--- +title: "Implementation and Concepts - Query API" +linkTitle: "Implementation and Concepts - Query API" +weight: 20 +no_list: false +menu: + main: + weight: 20 +description: > + The CPG library is a language-agnostic graph representation of source code. +--- + +# The Query API +The Query API serves as an easy-to-use interface to explore the graph and check if +certain properties hold. This allows you to assemble a set of queries that you +can use to identify bugs or vulnerabilities in the code under analysis. You can +use a number of operations that you know from arithmetics, logics and many +programming languages. + +The Query API provides a way validate if nodes in the graph fulfil certain +requirements. It is a mixture of typical logical expressions (e.g. and, or, xor, +implies), quantors (e.g. forall, exists), comparisons (e.g. <, >, ==, !=), some +special operations (e.g., `in` to check for collections or `is` for types) and a +couple of operations. + +## Operation modes +The Query API has two modes of operations which determine the depth of the output: + +1. The detailed mode reasons about every single step performed to check if the + query is fulfilled. +2. The less detailed mode only provides the final output (true, false) and the + nodes which serve as input. + +To use the detailed mode, it is necessary to use specific operators in a textual +representation whereas the other modes relies on the operators as known from any +programming language. + +The following example output from the test case `testMemcpyTooLargeQuery2` shows +the difference: + +**Less detailed:** +``` +[CallExpression[name=memcpy,location=vulnerable.cpp(3:5-3:38),type=UNKNOWN,base=]] +``` + +**Detailed mode:** +``` +all (==> false) +-------- + Starting at CallExpression[name=memcpy,location=vulnerable.cpp(3:5-3:38),type=UNKNOWN,base=]: 5 > 11 (==> false) +------------------------ + sizeof(DeclaredReferenceExpression[DeclaredReferenceExpression[name=array,location=vulnerable.cpp(3:12-3:17),type=PointerType[name=char[]]],refersTo=VariableDeclaration[name=array,location=vulnerable.cpp(2:10-2:28),initializer=Literal[location=vulnerable.cpp(2:21-2:28),type=PointerType[name=char[]],value=hello]]]) (==> 5) +---------------------------------------- +------------------------ + sizeof(Literal[location=vulnerable.cpp(3:19-3:32),type=PointerType[name=char[]],value=Hello world]) (==> 11) +---------------------------------------- +------------------------ +-------- +``` + +## Operators of the detailed mode + +Numerous methods allow to evaluate the queries while keeping track of all the +steps. Currently, the following operations are supported: + +- **eq**: Equality of two values. +- **ne**: Inequality of two values. +- **IN**: Checks if a value is contained in a [Collection] +- **IS**: Checks if a value implements a type ([Class]). + +Additionally, some functions are available only for certain types of values. + +For boolean values: + +- **and**: Logical and operation (&&) +- **or**: Logical or operation (||) +- **xor**: Logical exclusive or operation (xor) +- **implies**: Logical implication + +For numeric values: + +- **gt**: Grater than (>) +- **ge**: Grater than or equal (>=) +- **lt**: Less than (<) +- **le**: Less than or equal (<=) + +**Note:** The detailed mode and its operators require the user to take care of +the correct order. I.e., the user has to put the brackets! + +## Operators of the less detailed mode + +Numerous methods allow to evaluate the queries: + +- **==**: Equality of two values. +- **!=**: Inequality of two values. +- **in** : Checks if a value is contained in a [Collection]. The value of a + query tree has to be accessed by the property `value`. +- **is**: Checks if a value implements a type ([Class]). The value of a query + tree has to be accessed by the property `value`. +- **&&**: Logical and operation +- **||**: Logical or operation +- **xor**: Logical exclusive or operation +- **>**: Grater than +- **>=**: Grater than or equal +- **<**: Less than +- **<=**: Less than or equal + +## Functions of the Query API + +Since these operators cannot cover all interesting values, we provide an initial +set of analyses and functions to use them. These are: + +- **min(n: Node)**: Minimal value of a node +- **max(n: Node)**: Maximal value of a node +- **sizeof(n: Node)**: The length of an array or string +- **dataFlow(from: Node, to: Node)**: Checks if a data flow is possible between + the nodes `from` as a source and `to` as sink. +- **executionPath(from: Node, to: Node)**: Checks if a path of execution flow is + possible between the nodes `from` and `to`. +- **executionPath(from: Node, predicate: (Node) -> Boolean)**: Checks if a path + of execution flow is possible starting at node `from` and fulfilling the + requirement specified in `predicate`. + +## Running a query + +The query can use any of these operators and functions and additionally operate +on the fields of a node. To simplify the generation of queries, we provide an +initial set of extensions for certain nodes. + +An example for such a query could look as follows for the detailed mode: +```kotlin +val memcpyTooLargeQuery = { node: CallExpression -> + sizeof(node.arguments[0]) gt sizeof(node.arguments[1]) +} +``` + +The same query in the less detailed mode: +```kotlin +val memcpyTooLargeQuery = { node: CallExpression -> + sizeof(node.arguments[0]) > sizeof(node.arguments[1]) +} +``` + +After assembling a query of the respective operators and functions, we want to +run it for a subset of nodes in the graph. We therefore provide two operators: +`all` (or `allExtended` for the detailed output) and `exists` (or +`existsExtended` for the detailed output). Both are used in a similar way. +They enable the user to optionally specify conditions to determine on which +nodes we want to run a query (e.g., only on `CallExpression`s which call a +function called "memcpy"). + +The following snippets use the queries from above to run them on all calls of +the function "memcpy" contained in the `TranslationResult` `result`: +```kotlin +val queryTreeResult = + result.allExtended( + { it.name == "memcpy" }, + { sizeof(it.arguments[0]) gt sizeof(it.arguments[1]) } + ) +``` + +Less detailled: +```kotlin +val queryTreeResult = + result.all( + { it.name == "memcpy" }, + { sizeof(it.arguments[0]) > sizeof(it.arguments[1]) } + ) +``` diff --git a/docs/docs/assets/img/Institut-AISEC-Gebaeude-Nacht.jpg b/docs/docs/assets/img/Institut-AISEC-Gebaeude-Nacht.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d372112040f9b0719f89ed94dd44e9e198a6e503 GIT binary patch literal 29701 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 literal 0 HcmV?d00001 diff --git a/docs/docs/assets/img/cpg-flow-graphs.drawio b/docs/docs/assets/img/cpg-flow-graphs.drawio new file mode 100755 index 0000000000..d6c7367c07 --- /dev/null +++ b/docs/docs/assets/img/cpg-flow-graphs.drawio @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/docs/docs/assets/img/cpg-flow-no-backend.drawio b/docs/docs/assets/img/cpg-flow-no-backend.drawio new file mode 100755 index 0000000000..7ccd5f0e77 --- /dev/null +++ b/docs/docs/assets/img/cpg-flow-no-backend.drawio @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/docs/docs/assets/img/cpg-flow-no-backend.png b/docs/docs/assets/img/cpg-flow-no-backend.png new file mode 100755 index 0000000000000000000000000000000000000000..b53637c326d445e6b44578f889950836476eb0d9 GIT binary patch literal 882240 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 z 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

sw8VL8$TUpL6H#6@h`Z&xx4(RHuZ7NB|eZD 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

1yPAWu8;Cb2CZ4d85ct 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%0Acq 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 b*$0gXz|PZWsYo)_=JQD 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 literal 0 HcmV?d00001 diff --git a/docs/docs/assets/img/cpg-flow.drawio b/docs/docs/assets/img/cpg-flow.drawio new file mode 100755 index 0000000000..3279eed19c --- /dev/null +++ b/docs/docs/assets/img/cpg-flow.drawio @@ -0,0 +1 @@ +7Vptl5o4FP41frQHiCB8VGemtWdm63Z2pz37LUIEtkjYEF/or98bCCoGO9M9Kiz2nJkj3IQYnhe8uaGHJsvte4aT4Il6JOoZmrftobueYejI1OBDRLIi4uioCPgs9GSnfeA5/E5kUF7nr0KPpJWOnNKIh0k16NI4Ji6vxDBjdFPttqBR9VsT7BMl8OziSI1+CT0eFFG7vC0R/0BCPyi/WddkyxKXnWUgDbBHNwchdN9DE0YpL46W2wmJBHglLsV1DydadxNjJOZvueCvyafp4qs/yr5/SdHHryNsLR77cpQ1jlbyhnuGFcF44wWFYWHWPJNQWP+saNnQT3OiRtBBtxMge7xvhyNffuYDzcvAI479lYDb0B4YDEJir+wDs54fXwexYg5l2KhMxwA8E3GYcpJAe0JYuCScMBma7c/HmyDk5DnBrui/AaFCLODLCM50cUfhlpTSK86jaEIjyvIvQp5JbG8A8ZQz+o0ctNjGHFnWbnJrwjjZnqRH35EObiEUJscy6CIvcKRMslL/8nyzV93OFMGB4mwZw1Lo/m7kvRbgQMrhJ6RhXFEaM5ym4PL/hRowsRdunRos1ybzxXnUgLTX5WBZ15QDUuTwEa+xwgOjq9gjnsTuNaiB0RJp+z8Zj8LYIRcgwY1fwodGDfC6UwP8wLwQ8IOTPvTCdekHMTT8j/O/nWMOOnSfKLNpokyFqFnGAxp3HnrUuEcsBfr3tPuwN654W4H9jyyB7JmFCe88/IPGVe8o8D8+vjz1p5+7j33j0i9TcSUf0u63kGbGWIwg3AAfH0LCMHOD7LK8vJ6Ynp+X4zy1+XRJV5e0E7pMGElhfaEJg3SfhRbY4/Tq8SAplf74TFIaAQDXyFwZ5ZiHkJShu76jtYU/26z+shvWu6GpUGg4w3fmNUlU13ynSTx4yP0idDDQWkmoupacLhPKeIWz7rPjtNNu6gKyxm73ornA1NA+MU8wpuXF95t1m2m2023qqrSGzzvMRdb4ENHNzRNpae005vAtRE4AYUajvmTymcRpyMO1+G3c1btZTVm7QvEvefxIHlY7fa6WQWqIfMEsxPNIyOHPtNgFu/k0aai30+9qYaXO7zgf6ZjE1z3efVqHrfSpoZZslI3Is21xjly3qDiIOee/ALLDGLvfip3v/RWnhVPd86zbHj1S09X2QRe2S9xaZc1tUxQgzlP3O3rk1+2D6sOaysbltsXV+tI07j+RJWWXre+9AfAL1F2P8IeVrYq/ec3KkqFWliDzgmcwuQH0Hadp9NWS0G+EDv7uPvbIbFz5avXm9xXJ5zyaTbvPwEBvnIHTFZoLZBAxjrLqW1HtzQaI7plkWCcQxxoifKZ35HQdtS4dUIs8ZVHusFJ3QW++Afrze1NhAtZydTn+lf2pFmpe8sMbIwPZbSBDLYuIRI3j/Lk4Y4CDfzOE1K+Az0UInO7fI8/bDt7GR/f/Ag== \ No newline at end of file diff --git a/docs/docs/assets/img/cpg-flow.png b/docs/docs/assets/img/cpg-flow.png new file mode 100755 index 0000000000000000000000000000000000000000..ca5a199136a2127bbd4425785777c6007893d0cb GIT binary patch literal 726273 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 z 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

BRDYi=HdV>=eXePS} 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

rhtisf4P9W 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 z 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%0DU9vVI6<~2C3GQu8%auvrsMgS)R?b9 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 literal 0 HcmV?d00001 diff --git a/docs/docs/assets/img/graph.svg b/docs/docs/assets/img/graph.svg new file mode 100644 index 0000000000..c1a050545f --- /dev/null +++ b/docs/docs/assets/img/graph.svg @@ -0,0 +1 @@ +Neo4j Graph VisualizationCreated using Neo4j (http://www.neo4j.com/)BODYASTEOGDFGDFGASTPARAMETERSREFERS_TOUSAGEDFGASTSTATEMENTSASTTHEN_STATEMENTELSE_STATEMENTASTEOGEOGASTEOGCONDITIONDFGDFGASTRETURN_VALUESUSAGEASTSTATEMENTSRETURN_VALUESASTEOGDFGCALLEEASTEOGDFGBASEASTDFGEOGDFGUSAGEDFGEOGASTSTATEMENTSLHSASTASTEOGRHSDFGSTATEMENTSASTEOGDFGDFGEOGUSAGEDFGARGUMENTSASTEOGDFGEOGEOGDFGCALLEEASTASTCALLEEEOGEOGDFG arg.trim() length equals trim = arg arg arg arg Impleme… Test 1 arg.equa… arg.lengt… if(arg.eq… arg \ No newline at end of file diff --git a/docs/docs/assets/img/logo-aisec.jpg b/docs/docs/assets/img/logo-aisec.jpg new file mode 100755 index 0000000000000000000000000000000000000000..8de553c5ef8d6cdb80db2f3cb6481d06c1c47e98 GIT binary patch literal 63139 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¥LC{IP6vRlWTn(j$j8NNbo(QZW3uW>T*;*oZsu=~mzUa>T!U1`wJ>H&wF* z>&Icp2XHtmK>53>Oue&5Ccg)z=tq9iI#xS+G000P2Bf>E14pW;nNOQPqJ}%Ybkz_OIWTVHOmT+jRY(k+W!NO8q!u z_t6u&0TJ^StX+=HR`rdgOm({v1Xra7_O+}lLpyWi^@jB&P=2z&u&1O57Di>r$>uX` zs`#B!;V8W0vBONuz3kH3iIg850>ultGay-I8nHl41^qE!yB2@CG7lBz*LMRSW?koP z5_lHf%{_C|Uf~gLspandTlQY&E6`H)bx~Twm|Q2Hgct3%QE%RUD9u1u0yI& zd4p-mi#w?rUMkSxF=fMrVTsA_d<)(1rV~t#Zb(FjEAEY~-ybM7wVV~$p?Zzf>gEa0 zm}BAn1-+aV_diGKRrkoVkSav{noDFonhOxYUi@=^W4bDzzPe%KFtuZJy3FwMu}n`f z8muzo09Ep152p_98;isgX?hB+DxwNdS1JcfQ;Pj9e!Q-GyTtNZ!oQ!VWH9)~HC(4? zr0L$#*p8h+fBJjMA5JGvv_S*b`06|kEUp>;HWD@qz0o4w6&7RD2>mc@tglm4Tv{4vm6-)hd0A|laHZuh z@nhtZh@Kni=B#bpN`ur6oT4KqJt9G+c4Nw7WJye~nL7l3sdizAsJ3voOs&eg>Z!e~ z9Jy>{+;m(MR@d2yGuTEr#V)aGv=?8OOZm%Eo<86gkM2vupwy>3?rN_)r9=p<0LnYFzt zldP?i9}5T?>Su{tV&Ge+=;aU*27DE9i~2}S*VviNgw(>GMyW3`j~8Q*CZa}$xNF&H zNEex&cG($K)y?D}H0dd+cR(hg@fgoIlrz`PfSH~2@F{J{4Q2VyCi%xq>lPPzUL!jk z(3^T4gAt>TD_orG)?b)!>=2(-l%$n!p!Vb`icO+M-s(mpf*i;Wlt>O^IG0P-N16gU{GR7subX zlG{{V#h;wiX%6PeORw_-q$atK;>#&$R`}wGV=F+tWe*Fg`1~?MKfD;}?Y?NQ80WKu zuV9EpLSUU=)6%Ejkk%FA=0=U1zy|QgFweb#m{l@q(T$RR>twQWxL;wUh)B0u80`R2 zu{*j)P7y*1v=Q_Mb<3(NPBS}Wv6|j}v}0!gT2-G~DWL@1%QR`w8Y_C>=BGV5_8s0g zNt^25zbBeAS_eUtdv6t$6cXR{NWjsmDOOdVxKx)k68s(8UxnSY9i8595y`AST(vz|Jk_0M>-d)1>%qdv zu{Esd&Ct-L0m4>tMFdWTFj4L$&_WkwPO=cQqTAaEjoWaLifrp{_d1pQ(i`!z!OrXl zB0%EZDyU6+UwvaP` zOFPCx>7x};TDHQqeucZy$i(pINv|o!Ao98olk{@fz`IEXCbl4X!H1nesdAzjP@cIAUB}QK_+kW9gy!!?Z%(~Bh31! z@9se6DXg8TpDe^{Rdy=rXSd?On&lawSInvlQRv@Zde!>B@#42bu_2&%BHjVho8_o_ z8wn)7%DAo~WSZ=IPhDzcMirj79Sb50OpYi9$wdg&84(+#v)WXD>iaW9#-b=BmhPOy zdyvz^-rI#(?~i6W#NzBy449y+AIJ!cL2y}DIXWcE)d`zX4aHMPhFPajAhN-2dT%{6 z66{d*Q6Ok8kts;aUdD^`;BSK z9&uYR9aL+HwkjX3D3;}HZP=P#II3rH$+s=x+VJl0jbBl)FwYixC z9Ni5Auk5rGZ_Ok`{d93(lq$`cOW;;K@V!NyIY=3vV(2c*FVApSHnr9ZSCJtOCy_bS zugLKZHEr1s8n$VkyAI@lf<|Z-a)&yKAS1bYIJFnGARB-;$ys5Nctn4{3)=kf8a zJ9#xXj=8UPu@IAcCuwfAxtH>@v>46isb*?Aw9b)O_;xp4paf~!#)=X1mzZpv(~Cy~ zbz|;$3gID+0RXs^yqPNL@J25eZGgRE zxQW6S`M<%L>;qP>5d&Wx_lE21*NdIZyCNh(dm2E0|1&_%{%;mcS`8cg3mUA=Zy+jo ziTq*Ktv!svNkJe;M&UN;0FhIgK#GX6n6o*0w`?I~!u{m6XW5w96V}$s;)K&a ziO@5^y{MSh&xJ)fgTzVJuFd`YcCluv;&41O)Y10b4?;R>NlbHysdHFOxS5;j6qcs? zwD#rdyz4jQ?@$%pD%lq8moIrzYE9C<5!Weg=CVTvb_qBt>=8K@3T|xc&r?HKF^iks z5n*p$&m+BAEyz>uZ;IsR-=XuY`sC{P{o_gGiiVW+y;va!-j^jf$7)C|#69&$0 znb_TFqw7tnX|0+QYnLq*om2QZwNEsJi=0NcnIIHxvnUDs$TfBmBOV8g&%qvH!(gN5 zK5q|7H3_Af0oSO>A zQ4#Sl{VdfK(|Nt@SZgLcytMY=k1lzY+QJkYN08PYqcbI#!;x$8%>mFcPUgU(DtY4N zlc6N%lrfk2@BtuMVqvn671TG3SUg;8@o4+-N(VfINKIeTsc+WEcyjL|MV3ee|G2WEZfbg`3;M!NxN_X3zFXsT)q7{^|?o_(00H<6&H zmWo~4H(sz&s(evEm^KL*c<3?I1&(ulwCph--a~m3lKBzyVDQghI#Kb(JGS3kZ;O0b z@e$W8Y83g|9o~KqrL~&P$&ugddoMmT;;Ys=O#HwyQR)6vIy>&D{ulVozMZB0)adn{ zW*Jm7)_?sUKvwaqMN09^?>9R6HWtUL{sFw&UYpl78~pOvHN+QDx_0Lmc%kU=|Kou` zY4CPh7j2eCMg#?OSW-oMJR$Y!rp7BWKWHde7YoT=G)$v~$#&|&TPk$nB?pBozew@l z2C1YapRxmYWj-u^1zHJikFRfn3mTLR0S{w%4P(!>p3L^M4I*^KgphpE`Lk;#PdHZF zZp?R(Hte6p;KG2OGwOzxi*6tYuXnzgpjG!|yRu8Kc60U?+R|L7ve9aBtiq*7m#D46 zP9Ec??5u~yOnhtbd*IP#@x;8Zb{!ZIZ(N$&r$?4wP+7++wQItz2ZWfqX?i(?$>rdmHp;HU<`2wNQ7*wZL1IZ}i?MjVpUEo-sf4IqTA7fs>nq5yBEN>%o^W}7 z=KvoepS<)`JjV_G-~X9+VgHu^t@Fi+zzW3Jg2>A5L4;3`G3GL)d%KR=o!bS^z{=Z8RkXPel`&m(+s z4!U7k?n#FRYHKZ-r@+4^z*g~My&Vi@gIdkvLeqK;j_Xxo_xGS{V+Ln{^^g-ThoRCB z{`2u)tcx&HBmo;-2gEG_>=qqaOJH1Mo~9a%zxK!4)P4kCh(>ZC28-~A^JQ;uwItv# zL>G2i#H3NbwJeFVU<9RJ-%~vnc9Yp!8vSb;});qlm1*Q~& zOs~GvYZ4I^1fisv`&a4G_xTnuTO}5W43JtUDhH-VwAR)P#STGCjWzPU;I*_C)$N-% zz4~$9d>=3LI=cdg!!V-;LHxRqR@dk-z0DFMBg(*L+I@^_>uQOpGqrzXaPKObNFHPV ze11YrtQ;|hhhZLk?o~6h>-*Ug2P8f?chd?}D2p1MYRj(n#u0~l!FOzJ19cYBAagW+ zAUW{i8KB+z7_zv>iXIakRo_fOhM9v-f*@=1>}kv^y~9yC39}Xh$-b8afN437DU;Zmi=MP@yYU z7rM9iHog@*j66ztw!%@L#sisWEC(}<&Gz${DOd}%;a#5XJHMYtB;{K7H&NgGs$0l8g<+&U}sKxsK+gX*dEbKmy>o7c0p@!&suv#;Yx3!_ZUef$;Ys>?F_)!Xz%2! zh}-4!RPE`B?svEAP!%|j>JL`5v!+Uwf1JXD%0ZHkN3M0A=DC$)D)#tVpqehzLN*83 zFE6LdN_Y|p(|(B}Ha@P`aJ;Ll-5CT!Ub{WtHDkqcjtBCx%Op@GX~gtt;BOgKJ1Hnx zM=jUn6qIl|P_vXzv45!jqQO@C8b5oQ!N7rSyxTF%@o;Fi(>ahwwpBC?R2ZA~Nw~jl z&E|lWxmna#oHbPmJ*W~#mdCgYL#r1&CU$yK*drbXd=U22g?+TQhTvDehj6-mjx^S} zWtRU-#~g@T_;LBVypFW5|Di8+{of@oG5$?w{7YrF;wRF}+*`(d9zWK{^AkE=+P}9x zYSD5A!v-hhD?QySRalw=&=USrn$ZF|W%?N|R72;&b9* za-^Q1#)h~eNH%U_k57n0c*kzQKAFd1&^bLuHbu^9>vrqVROree~h2`JylgbW+~gOk%vhW zi5*R6>eA+vdgbet!)hHeNG@s=5?Zrg%tk3sC82MK2wDIhi#%n8kA8|YtzM`R)bCpr zc?V2oK{&0fl^rz%3x$QoJWh$U^s`P_FwC-X_ZH}!luO<~@~P4|2Ui*RG;G63CY#)@ zfBrKBe`31ux4`7|`RT++>Bq(f4f%qqP2tab(e7|i7>^_-H1@te4EJc~?p8T#w3R!Lp!MQH8u68(rx#ei;w$3JQ5T+3Xap^k^;#BDauf8 z+{zd*Dqw>AwC#K9Xa|dyin+YnZbTe@<)?-RsgcDcz=sdXv*sM|b=&D!gt1}xFyZag zUd3jdfV%3P^zkka1?{}peJSH_`7)~$gHmiSYfdGL`F3|j1opMf8Wny0Hl^cdS-89g zz>_TYQYNV)rl#vhw-oOrSCO>Too88`6FQfKlc_&~W3=hpV{`|=T6H~-1Ll(xVj@y= z99XW-vVas6MQf@G=GY3B)9w23%Xm?_Pr}lB;#kI0Y6GPe5kbyNb?cD3S%H(g9uk?w zIwct_P!VtTD7A0Wyaf*9(TVRheUWb0m^IvCgxh0YzwNkO$%ufn@=e6q=~Oohv5hHF z4O7f0g_|-@L8Q%wCaH03%HvxX=TLftkA~sbVFbzRYnr$xwKz|)MuMbHhtN;&{U-6? ziIV8@#Bj1Ziv-6HRJWMWvk={LFWe)rAD2Zn^RR3zr8P9JO$CB#Nz$* z4dn(P(8Z8V_j7|H4c@jLQ&_P|JdgdQ4(&0IfXqwW`6jkS3PK7dP|67|nd5sz2~BTn zwB>X%q{Ao{bzi|~qJIUt%PSu;>aB19r8@(BJD&YG;s_e?!$k(#=T)L-Oiw&^!8B+K zdi>Hfqo-G@>RwISK-?SD%zK}xy-eptjUuep^@|WmLrGV?uYd-?h2KG8cB7QK)}~?7 z>~?>CNeOCb8A1!N(^2QlQUpj9XSFd;`8yayT-DK+y9l4?pA!&(OIObGN#r-hRp4ictcN<8ACj z+V$!LJ}fVn>iI+b{P;YYMB^}jYx2E~fDE{C)zi)Zp<9Y#J0x4YdRaysrhIa>Z#?L- zH(bqZvsLog@x-ivnG0c1Q18MO?qH> zZF8ECrPw5m;=MveV3RoVxnX`}?z-)ymAf^i?dYRYiBcy*#iK3iH?dCRZ!S_a7JWJ> zM-8n7PajtWI|B{StX@{{yXpFOkzCQ7{-J`JQk7$i`$(Ht)X_TmaksRk_XL(zskP!# z*msCUrutLWZ4##kINEiq@&w_R!|aTKbEeWF!)E}cMhEcuVibh}Yg)L;pd};O9 zf_ky$puJUrW0Re^`2Lc`jV58|n=mg+vT*D&MwONl{#@nJEVGbA%;J2;gHvLA>i&|1 zuU+K&?}2B4gZ6-VF?&DQ7x|6;H~VCciAbC)kH1_82TRuR-3Lmit{eyL$sCY5tz-8j zMM<@N&Lz{9@rWX|rra~YK~l{-Kf|qZmx{2~0xbFqO=%bOw0NEdCGng7+84Yo%=m0o1k2^B^QK zh$Iiu!*R{?D)aNTJ&Ugpmg7Ra{6(JsF6equZ$N#EdGp;9gBvgY!P~s7XG2x^Cjel} z{xMAor+{_edZ{IL+wGUPd0zHUJr_P6>UZEmrVV;ch<3wuW)+@ZN`t>w6uM$%72zc< zgkFxu&Ks*Mcb*yr_8bDH006`nPil1Nw3HFNa*W_(o{Hni!6LHz7?*70%}uz&{9MH1 zw>c#?+UT3xOwIs`04ebg#v_v@4E-R3EY+UjlsDZ-`pm$Gu?FBMuE^}){wXB?Ug>HN zAK^CODzY+s-oEH<->O%M{09^8X^!F4qLKtdp>O3)5&DtW*VGIHCkOG|LOw$neoFG* zqQv&rGs#sOX#RluEs$&47q1? zv_Z$;3pU^-6K?uOlEK^|8fMZ*323zU9_G#P)}xmQhHdzW$2PPFzBqe5dt~$coQbWY zG{I;n!_~MF94%#0Rz2Wqvt67Y%p9l-3kl?Q2UAi?t!lov@h~=s3fjS^z3`kLd1`yh z$`V}804VAQhs-~&wjPy(@dt|uR1)YPIp`lKDuaP5EY7+i`?p`-8`$_8-Pl5LfAV@* z?(TUZ&j7hjKJqEbd-$MY=@h?|w5WHRozGhi^v{Ufi?wOX;NejVi#Amp&ay_L(x&APf43x6&DLbrhP71rFtX>4 z+_;&aijAaPk68>hCu%eI(HTIdeJB7>x49E&pKo&0hxOIYXcn7vx_!E=k^ni4@Q^2N zRWZsT!ogL9Q`lydQ)uWg;ZlBSB#MC6z%qY#Ug9mUoicJf)LB`8g8P0YlyQBRF-KHqPCVZz?{22!mUJx3Z)L9 z-X`a@xfh=F9gLWz$7Yq`%{T!so|_1^*M@EtT&RdUW~^Tlu?j_Q2tkFvswcs!2Q-T9fIx8(S&Vp#mD>fmW1G9WcY zKpo0!X+YUw+QF}g?BNuV*R<1dCjq)XSrk^b=zAVD(9-c#+O2>nT#vd)U2nY}6C{@_4e7Z*s66 z?h{$pUA_$l6s2Iz2qe@MlrZInuu$d`+b3h8^8%%UAmBK4VChfG;@6`OP~(OHYdrI7 z`ZMX`O;u&(4Wd;0!QAeUt-FLeWTS0f(tsg7>UyKQDj)a!l+(Z&U;rvFAuh?Xw#Pda zLjbq$xEQjO8m|fK5VJrei08Hg4~r9J)h&}!lwe>*)BtCaf0m^ViSt{PcOG=tdGZeS zq^&eP0Npk~OLjgsl}BnnM5rbE#zcLQ=9!dLqxMMDyo%CY1DEISVNJEyG1`Rh<=nMG zSC96^zV$AVL|wd4CwMM{-=1QA>7swPoc))p+r3K}gm8fxXBYzUtnY!vQwtWb|5X3> z_NUxmy_`dIWN%e$>yVq8vUs zxMUmtv+7e3=PTd&x3*!z_y_ThW@9-1&LDL;@$6|`74>HI>;rYh`!a7`{*K(G91_A~ zI~B4OB>RakHvQ<Z_!>>jt)#mVO9DR33ml54OVeF%dv{u9EEYFvJ_T%ElDtGc^jAGdG*~(@{5+bZ zHPr;y=qRt+cNH-u7N?|sN#&jR5_!8E2mbbTiQf=8vMakFjQ{p;+Yc9i?I2&&r?JgB z1AK@kMLamny+HRxIqxd~z})-~TgdNIX`a9#KA@$!Zz(SUfD0Q_UU3ohQ29K)&EeL5 zc6wM+0rL96?4zj2g76Cac`E$Rf%g6#%mQHxC@IXDQ{m-w9w8QTKLNb@VQGQa<_rLb z6Ana9RTRqwL+g#K)!j?2TRirEf1Pjrg4Mx9wh>Q%)3*z*niFBsxnNKlk+d+`$*!!lVI0K{>kt!tnQ4(2_YZ&@f@f`1ee7wE-U)HY6+2u%wOzyi& zw}L$%XQn56XVx}&>=eOne))m^f+nM{#<&S#4I}9ZH=1sXP+r>nW~tVr@eutcBfQ03 z(z+HuelzME=ZB|Pr8h)Qe}Dbea!E1DL@qBKQy?(H46Bo7kaUNYR_RTc?ceU*)KX%x z(A!d_)pPe!j?)NI&8yj+uN3)lW#+bMv@=y>DI2dLD+nT5!W+*&_G$Z-6Vag%(UhYGWgJUKk_i z3!hQC3*$f4oOsmtUMRe)Ke>PPKd6Ae#^O+z9V5k6Em6QoN5?C97;;jeRTVD&wrmM0 zb%M-!Gq2YgqNj;`g>p+va*g9{5%z<-?_P*{8!WK>`Mbnos|5elfWZKNvAs>Dh}3T6 zhd9m;13Xay*rrp?Gr;#-eYeEnJ@U3YJTjxEQy$K`WGzzIcFZw-C3c`QdNVg&BYVOSz_7@L8BGKtABdoM!^+2W`)Q+Oy%4J zPz4nIlU>;DZ}-E_!w>A9kE}i={`@ALY}{#-Dx($N$rhxS!pY5QVhe&2_uW~w(+xCC zXd5Z9M7QYN*j|j;lMf~?`fdX_;f;a z@wa6MQPz(NLs<7bzM8Jw?J$Ks!spg6)>cKB4CiELJ|rGre>*0O<(Ro2=5H}MD$dUQ z!PwHnAaA`*!$%&d0d#{eV4OlWxuO+GGFRrLV(bPa_fYFw>!z%?zb$jx1R9$RL7GEQ z%2Wd811RZPh1)8?yTp$n)^7{|_vmCmXchyMUS8I965N$FY3p}bD zi1u~fZLs}dKIRb{IlX$gIhMR9(OZ@@i<5Hwr2M;vf$0{E9>B8~zR&XwJ`< zF|S|SdY^w_;rUu0L6s83GQcRAw!%X9a$YZMISjnu_-^UVo{NKSlWTaLk*3U#`pi8$ zEBC3RSl`%zb9)W`@Z4T|E*KNF?wjSv{h>P!wiu;L2rxr~aVY)xP!GAyFt?EN-IDl6 z$>*jz0?%jN9$ByeGFWr12%Oq)zJU~4gyB4)ggb3lC_B=eLQ{zmtO@y_%q&Jt1}%5H zbt&;tVPFHRN<_4&g|5BrN|xmpZ4)CA@U&j=wjiG-58Z)Qb{WQbYr{`H>I@({?UiHO zFdYzSdYGBV>c(3u(_JzU96LRNl2v$hq&M+QaCOG$8>+xg8hZxt!dD#>FKbBfmv|{* z9_QVIHt9`kEzZLFd;4QKtk#lzO}!cD`JItk5(~48_d@di!B{7vA;wekHk03oTVbL4hm7Svifj)=R?@0Q#1?)+j@Emy+jPyvY* z_O6>9Wm=~7yv=tTRvmLk%ocR(7@X1w#_Eq!H5sX35#drjy(aQdR#0jiHgINf_3<=o z80fyBq84Sq_-{fExHQw@w;i?7lB7^{=Z_bIoc}I)%ly3J#oyf|H;|sILXO(clL7^P z!lr?1qYz43)Za~%zjwvdmF>^z6S!NmfI^ZOxa*Rb zWk`kYen|TnV6$csec{#;oK{93r^nHH+)Zi0`D6PR_c|5Jnd>(zC437^7KBV{7TifG zHO4=#OfN;HCce~~=M~QX#+A^l3SRJKO z-CbYhFW!STw{H6)qU~=R@5#UX!g?v5FLG2y7hV^~?@Ym(BkVG?6yQfK?!`|vb5};h z=H)gsY#})}1Nl5l;@y$VeGup_1goa#NP?(^%=IIK(zg7CQpMSt#3M#MO7HlCk1zo*qe)SEnjD>j{M7MR>``R4agAQ!$ub*ty07bI8Ia_$dA9 z9Y&+^N<%;D-?+|grpeb;BX4a4?ikKJ0rlR=>~&u4U7JTqUKc)eY@r)k6^AfSt-m{L`4S*0W;4=$_Wqh)fkn|@>ArT2TH3g4^|3TKr_lV8dfzt@Pdu~{ zNKEzGlMj(wn0$(JHrqCd1m%=Q*4%7ZdyE~HPIM>xm{f>B41XH9E$Snnlon^x;k`jt zm8-XYo|GicC+dDHqFc~wcS7Oh3QRuNoQDIUek;$)S_MAHs(rYU^6js=-dJ!3ARl*@T_O^gt&gSKj)lMsw#B}D zJT&n7(x^}XTgbdD;g#E5czKHhEx{q-ccO zj{F72J6#f3?p1hQx$<=uA}xy&?JvcK8S7$IOL#!p{K|&W&e4vlX-RJmi9CxFFwa-j zqJ@!_CG3eqcmKkYR>_UEt0VGVpAmJ8n>aeNj{F6mJ( zxi;YH3>*#u?ny$svw*_x{07lsmh_D!<6IR(Y`dvFCU@$&A#Gfco(4R+%YA$dwP=3- zD8;G|;{r3M8BoKj(v4^hZuot*GQ_=U#l@STBu5|bIq^?Sz%edorvo%Kd&HDXU!< zlaRWo^Yv1SEwAFHmfWtwZNFj1dY?m_WHbfgeHiii>htf(mYvMfWj*zx#^ih?1$%Q0 zHj*75!Y5sG9gk4f2-*7tEi@y}p5LP4m+{j+5#Vrk*`bE6sbWmW_)}2JUK)XAZ<2pw z-X0q5FD7UaN$S1m8+>(6F<}{8J9?p}1~D@~ST!^ifB#0hU($S>Um9%QUMlJxLZz2s z)~I5kA41-o9D7)fFD61Z#Q$c7|KD~MW8|U3X{<%2)IhV&wfp6vJ8Yo|LF6$W6t76I z=9`M)n*^EHqqICPHygGXI8|H8o(1EG*6f zSo&_bNx92(#ZHhS5#@W>t9q}qyp*w%X~)<4ennj4ib`2JuU;??bABW_A5wjjSTbT? z3fS4G3b@y_O70i(^9*o`55cWK!AX&?3nx)uI6GF!o^FObnTy)b+yVSl0C_VzyqsJ# ztbnAuG&|ZAI$JqzIAIh{VRIRFK|>9DDcy^)nUzCoTafNFri+I5rP+OBKo8d6v#y;} z6f@jor@qjMW7?V-iIsISsZxJx=>z|0++CSpmgNeAP$BvIUZZX_ZaPO#bYtJNGy!$t zk}X*x_B^n`;6IvphbRL$3xWP*PpL|bU$f82if~!+ei@!x)@eZX9pch#{TjT0mDOj2 ziOO? z%PTBF6))eh_0qt7GT2e&0Z|W6_T|I@yCT9*uPEsE!=bf5lP4x$_j=$Gw@#L4KaEuw z!QyxBOTBl_PqmKI1Kna1q`{iVSc!U zg)UO4rexM~q<5*qmnAVRXLLBZ9+%x8zYp~2)^1UhWLeQt6>^C!FWHB#dE5sa!pyXY zc5!yx{Bh>nv%)8>+M)0^FhQJ9NnXNA&roc2E`B|i7=A7G%d;HfKjyG~lzvUk-A;as zyaj;ikcIr~TLis(ZTqCt`dk)Fpr@1)Juiv zJEd^twxCrtLZI2brXQ~`Q5$y`w4; zD_?|^)x2q}o6c1)HRSz~w(;*NIu)3rIsP$QCxQOsJkBn={bH$WgJhrarCnpk_bZRg zA4^8J8l-)K^%@)%mQ+cCFNnOb%F<}h&-}VBVOxn9+T0g|Q;lgm{D84YV;;R$h!1x6{UcO(L-zC>HIVlU~s#c>_J2i1pvt$2IWa^2O z$qem4PtwHbZFQgR`Z|-RS#_purh(E#o8ZCSjCRX!3;HF^eOsamGCBST75(y9#`+S< zBUlzD(h;#c62cf_$+*|YNmM8<1M()ZJxI? zst~pO?)U3yy?qDqw?3z%P4Aa!(z+6S>0Es>{6MzO)(V;}h9_?h0zJlks8(ThE6^-Q zhy!^AxeJKG)nN7a_KYFM=KZ?{uA{ec0zo_dbk^1Cc$Tnab*JT!Sh}N(@&N|QPf(Q9 za^Z>#PLm0%HnJ>s^?GP85u@|Dlvmde(@*U*E+q3>_2pkvOQ4D1rrv|sXqMWsOO1|6 z@}obt7Mi1@kJYb7C1VOGND+vOM)(IeCbeJVgIjk#byB`@g#W<;>c-r^2GL*7dBtCk zf5RWN4MvaJC9-tfWoCS!{b53e1XRnkv?{VTc4*P-+D{NC`u>@>#a$&K5}dj?|7W_( z>$Yw}wK=XzH8y*Bg|h2W+h!=Z;w^IHYhN|6;ulzReu`o9wytA&QDk{VNTi!~EN{J^ z5m#gQ>jYFv4ht+X3Ckxvl7eqq00aZijN?1*Tkf}9-!`@Qwm7D%1$X$|jnx>?FTMn?86+fjv#4L5drHOadHaGc<2iQ_`L>$`A;SX}SI z1s`*NJT%vWeE00`Z&qEr<0HMj@&Es?=B9OF>7;{hnYP0BCKzx+mD}$@IpA!Gzx^%i692C+m0X*mooMS!urWi(5q=7J`4H<{ptj}Y_IR6`tMlZ&&UApNrHxfh;_C(|7jK~&ls~@l zZVijkan|?Wz5sDDJyzCd~vbWElkp zx_IVy?zF^6Fc;1~+pTb_iJ61BU6X#vA~)YX{Q&ED1*QTZwRi3uM|LY2Uc%yFloViv z4PVM1O_&>kcjY;I87=GWtc~Gx!jf(-Og3l%Ylk7E2sim0JJVU8=8h!M1Sln~LZK)o zx$6YkEs*E+M{9Vb(~B!nLOg0TC3Yke1=1+Oz7H5+@;=EQPI)KZ`-}I-<&=il2uX`( zeP(S-HtyscPZw{FtAiSUR31@{c3RH&6{IhirC5$T_GY8r88o}{sP?r}SW37*(LB}o z0p>KnyiHsTQRyvc3e(&1&I;C)*UYkacw@{6(3E%hHU&dw6C7=4U706g9JDOXW9jd@ zFkPAbd|D=~;C-taDN4QbeLImva#hzCeGynfMT~3!)$flyR{_4}SkeNd-8t46%RL;G zin8ViMdqigyP^QNr^X&Y@p#jJ1Aed^10nZA%wM$mBO-w`t#`yvI^r~M% zS#x27Vr$T$7qhqBKVo(D0@*0c0dR9SI>G#5q+W5)Ky)hpmzhE0;^ElNXE=mSPfCuAhi zXx`s@aqyxN%k$;K&)tWlJ6ATnv&`Sg?-+VTrEUA8rt3oX)#!R7|ZbZbo+7s~0**>axfEPu_k>{ROr{ABM z*BTgj^!e)pHgmt*SzE7`&NZIsK4T9Ttq$!uP}@ic9eMg6*`;=0%D=|Gul>^Ze(i~j z);xQ2$hq|y#`SW)$82Bk`%SB!+1sSu8-0-B$1i%VG0v=E{`fa9&^o}bXSy`mDl+ME z5?S1Gr`CQ&!9R@0(%HzhqpYj+?R0q&c`l_~NS~dTEe>?%pFk9|ZoLWbk;)6deY0)d zmoInyx);6Ve7}5LtvHD_8f@8423-Y$kh;Jo-;ROvgL9so#pp0a4?(x8@+#UT-N&mX z%jvyCSDmuqefA4NF~?QxYyt+TF*0VIk{BNO_;Y4?3r_e&wpCTATtxTVQH54}_B<;{ zMxw3>jc`%XqETQYte^84vT!UBgXoPZpDX{9^%>Bue?ve8e?dTh{NErTJUZK>p)F;J za)7wa>!-FTRYt_9r*v(;h(HS>cjSp#{2lXWJ3vdJa$5TIHO@X7X1uJ=$_feKR=L8Q zcP6zW1#E|oy`NNec{Ly|$%Y6fl7bn0S-kH|SGX)?S8f94;4PjM^Cb&D0mJExx2U5W z*Lzm0u5;{hg_fwC|AcLK2#&o*92WySVAfp~T=wo)Duv@!hc}9|njDVT?#U_ZPAJl$ zK0^jxv|#d7Dl{jrtRSjL*389K=a^@OURx)XD7#MktKBLAv}6tmVdFv`h>;=K{blYy znclO0c@xfd^*+&ut8{05YZq7!HiO)}pj8)}ltSY7elk%tK-}v3P4`I7^D96j zGVu~z4~;q=D%|=`q|LKR5i3ls&@+ucQ}`#)0-&6^vGsXfBFM|4zvqvZcO3KD6V`O6 zNYDH0+^6u#ttg$5~< zx|(M*HndHC1uWz>sw?ks zaH+{!M{mp?7}e+ou6j{IP2agGb6-N%#Hx84Uedr@))kDdH-%Y0OMWIC}517>#lY1Ix<~{kGr|+$D*b2%Qosnz1WMp z7OB;r*=)p5u5@kM$cAL|$FUS*wdZi7fNw-vk-g#XauBDUm`Z)Ua*S+%YU@4c^AkiSkY7oo@_x3DIZMQ0s_ey3eR-_dPzCxA(0C+YQfvY$8)H{`|bUmg$bvy^-5L zCvaAE+0ZL$8*y2{^c|C|o!=q5M-%K|HhCQV z9Lv8+TGD}U1LH*3RP{RoiwC1d*wVn*!^Wh%fYo_wf5M;t)|063;dxql1waW?& z`RE}tquPm>%3f<$s4oy-xt^JS33Rsv1G)~yU?g7~%|a91_pCf~7KP&8HD?p3fb|5u z#$?1Y6Vn!vGehrO^S>J^Z?b%oOnyw5McEU=CMvX?X!F7nN_r!`A)Yg>HGc8yi%Uif zWy$rUqxw)Mr^paj^yeCI@h7^ipoNDnGj{;~Gkg6?!F~7wessIFWKlooaKBmv3g(Q` zKOdp-VH&s`(47c$Mht#K(fF2#Md@-~NlpIZh;pS~A=cWSzb9xJDe!vp)zmpLb5^$E zH$MDG!X;XM=LmHod70^RW@4Xqa8xX3QI83HgE()ph3IhWg{iybPI4sn*UNem`CbMC4r;uJIzm<8DIOapz9VT`6u zJ!P;+bf-NYTs)b_x@_49`>62{#E{wNt!FLbXzz2r4Lx#P;6>%#YnzTJ;y1A!lQ!zi z(!;|`A)~U&Q8LqV@)BTGMRZA;iEaPrlMe~}V5yk()r$h}ZX0fBY(6Bua{dXr>#R1_ zr?`Q(|LFH?m-ZPOMJT2qmwQNtDdk#_VOO+@xH4P5TCG6NzHFc&x`?PfN|HpRJeDd(H#4)-LnI7B7=vt zvHP?M54a0fBT+sV*i_r(6~+4=Z}pQk ztuQY0ADNI!2e>qjVC58?=}AVyTdA&j+(h3?BP(iwms|Rc!hH6YvOA0cEv7e$(-q1P z`@`+jO5PPpwBd`|#r-SA7_ZoCV`n+(xTHn8G*{@d?h8l`>3>eAbXOF(SQ3(*-zN5$ z@ti+s6-nJOOej=XiJM`THAF^`J{}S)Q!g~2zLH5d6CnE!^MBV%n0b0;Skvy>yHKT@ zT9Il`&sw`!3@txtLEp)Xw@ZFv@%DCM8BX|QBTBFEZR(ir>&02#b(HS#pQEaI4tF#+ zFV^El)`sKZ6r8*RQvaqw6lldau1;mnQrm;Tr~c<`(}eySLRs%gx+= z@V(z4myX|po)e#o(5gxHg7p+HLiHxbmRiGH>z2^oQd;?;6Zj+fjJY!Dyu9*W#s!m6cN)+7dGGsdlZ4VG^-F&Y<@_E^@8oKtHlLrTNGXM^h6V?G}4dT^~qshy4DrfI{dt$B!6QIB<%kl7;oxEXTJMINo!fblv0(?yaFrMKLNrnge z3>$Zh`@LAkO_r`~U%p8%dPi|Ho5VayHc6P91bddGve`8D&MRNdh_G{gJ_~gM))G?X zG$AEC)9B=vasF495@Tn*B0%T9g2v%^c}c7u@o^9KLT(mMTF|-pK}(eT`-$uMwaWsO zu{Lh}oSGpQA+g*3Eyt-6W}ngR@nEr30!$&XefjnHO-v3Efp9ieEShgwMx4;~kv>ed z?nW0@U5j3uwSH}=Q=G+()kBo&;)NtBNn<`T)3$-pncT5XyO`M<`dBzbMIf^gfQJ<6}qp&P4KZs}&9n|cTjZyrt!m~l8K8bo% zv%aeZz~;I@GOaLs!|LhQG*936{V>@%Ml30(nJJ1)xJAO>7k``TjPWxOzM8(_5VW8L zQ68E;@}}{UR8xGgaMF?ZY>ZOSbSoXU4ihtM^o{b^NN3z>?5JAP*W2Z|-cqicr$=f- z_A|mviFN`VzF4(kz6YH#fv%kLl?gWYM-D!SUG?G-o_VrryxyggoJOC@7=Lr&k0-?rTMHWsWJmSR!M2)`@K(FtunwLuAde7MGn3qlM*Vpgf9Sj`SG`}E2YEiM^tgUkoyf174JRVpPoIYE zSVPxl;5l-v@w$sMOw~xPvUOp@+MWi(54u8sO^4rS$STzp{S#z#|4VOks$0DEsF7b^ zUH5-8m%n5sSMEgSE!ppa_(pyqLyu_8wmxmK%buIt)~!Apd(7)|?tk{ri=K0iLdV$@ zb#>}vmE6Mtu9JgUbN0Y9o&61=W2Hor;&0p^|Et+KO%%v@ug}8&M88Hf!G%k!mp)IR zIUThWk;m(ch1?ss#F>cU9QNI#%HocIlzcA}&r}Dp;BjcnfXd2myCrw#bo!#@5QNB_ zt+!Gpd2`~aYWSd^AYp(J*RaslQk3Hk_~Mi>o2d)gt7zmTOfvGW+k0{+pr0%WTq&&0 zKVXnW<7qYI#YVG~-_qT^F!h)U5#VCkAtRlSgBx6lnuL<{#==dE0^ApOA#;_Sy^dVRLPu1%Ppp~@WW$>gSv?}=I zPig0x`Te5k@LOzm@=^tD*b&!1tVl-{J zZ)x?wEte!Nq`Ww29b%icLXORRXq)j@r+~is@ZWzjk#Y(`h>!o1s6m|?AR_l|8feD( zJqW}Y)8uVw#qMsaC|1Z}RPhU4czN1PUsdms7?y<4!Kz=?Nt)UUv4~|D{t>~g+90oF?!lT|#gigr zVIe$OhM@)enfy>IpRRH0theJuWtVS3dYEH)2;dgBA+vGor~S# zh(*Rm1CmaEb_RI?)EDE|%oGD|-7S?CfvA!yiJ^}P8$_8Oy#X^#-q&=uP|5?`0Z#5Dlv|I2IWxmu_frwoq@if?LMN?gdKZ;^gHlM(Sqq9}`2Uf>&Qbpj{_Z znfqn&`K_jjDaDMscDv8EoJSJ+#I$n=hbU`$ULtQ5ACOU7 zr54ie1};_bo3!KA$vp3j8nEgKW$KQvLlW(SG4<<>&SBD4U}Us;+_u^`FOhWD@s&!69$qm)a&tSDwKxIOX{l*t#=iw~F{a(3KS z;f^+n7rrX#3bCu-OeCXfP#Hz>gZctL$~0)hSYy_Py_MP&jrr6<#)RgZ7WrCYnWeWL zZ;7_h+KOB&Daik%Nol9hM6*cteaJdr1m;*g^ReeCXn!?#d39+KZuaz)ZXir=)Au@w zUu6>-{G(^-x%@mwL6Dp(d-MGQRmZP65Y?G6FPN2_WZJ|S+bs81*Pi%d*IXuI7H}BN zRCq%fH;7hRwDeSU5qM-G^)@D4t#@xBVQTA~K3?}GVt1?sdxu7f? zB`3SGTB@PKnaavCiXxt-i06{H$7P%E-vMkUHQC!0rqQLnp@!&9+{1~I3I5RZ}?L?8Gh z@K6xzGqOpLQ%yF4!&qvwwStl#OJhwnG_@nT?|5b_-U8>_TBj%F*>pBY9Lw+QM01cd zsDoV=+N5gwA}}?GJddJSdK_GGnt}R_`2YjEX5)o|;{3v_ADtzc*)>WfX^TFU9lvuD zd)jp`z67x_wIGIBn)g>ik=tnV081xG<4s~_)JV~(88^%UzSyWqozDGIs-|c(WztBo zk&Qj1vpX3wZ)Bna=`MRc%d14rp#)E|>P+OxVkdb;UPQOuYt|leb47w=fWWGpeUba_WV6YPd2c zCWUDsMj9VJyqDTHpV*+9D@|%-wi=^|_4_Y;Kx8d`vqWn54 z)BK>!;ATlv=*P>~dRU-g?V>PdNo-iBmRg%Tf5*0y=@NO!Cpz9=3$nt#>LRSS#Wi+iu(oDezgtIdQf}XER9WN`srCfpzDd)Fwc{x zvjMLSVU)Uj$fA4sp5_eP-O$e;afO|ydPoEP>4m!7BQ8sWZC7T@yCE1~t(J;>xWDWo zId??A3T1g64=0h;W2KO=Jnm^I7HqGsgD#U(D#3X_+PrS(^y@}SOKmj4IRz$pSO zG&A=pjcfk|=@^7R1cBU#di~|I$(cDiWc%M++&#Gy;oGQBb1y=m1{CvpF}W;e)6u^2 zD5L(WY#E-ZMGeaRm!PGPJEfU`tM^>kuz>n*i6nlRw>Qr;cTzK%-_a=<+%yk(=?Fte zH;oOYim&7k-$}JnV``J`wU^+6@X+XVV-+rtnU0rooG7WBeII@#W!$LaS<+CL38Eti zuBjLA!1M~rNeRUP$*S;nZB(RoC&{wQS-v!L)u|BUQCT6utkDP}X*}~L{JO(bBL~#ap|bBwLNvw{VvvXT^S%eC3QgVbh)c4vey85h4t}6# z0q+VnON97D7|X6}o9ZY`qf@3^zd-4iME77-L4F-}scw0V5SOS*R+nkk8}5iKfNmjU zY&q9;hmyL$R3Ws4I#bY1P=prCIi=*tQ0tV03+tu>a7Sp)T(YT@a1E5O)EsI7XZDobhJcO+uMY;&hXYrA7QdGYS13>C&2}a># zk2$~~hR4$1fK2+fDzvmTFND;~(^kH**|a96X*Vo|=Th)aR+jaDQMB#)%@}Rj5#p(y z$JWNN-*B%{?7o_)nd&B6b)KK;HB1sxkq_#VpII@mzvZ;yZ>otycDRLYoiJ<2pcBh; zVg|+g{csDR%RDT8hc1cG)Z~>sxxn??iMcvAJ;Fp>;XgswQChnCh*H9L6`N1&oY0L0 z*L^cl&F0G?6=k=^QshBr;U7UX^s<^r2`q%K9dBp{#cB%cWQ)|ih1zMgmNLt5ExrB; zVt88K_9Z*Ouu9a(UuNtoLo`j0~{$@+P+td zCJ8rQ%Ewyy4*`bZ85PZvzoj!B0wz+-6@mg=Y=chmAH-{Ag3~k9FQ=LA66H5}hKu05b2pFAW2qc%UtuIr=w^xq^C)dO3zi z^bh;0spskIYNsqm=99#drsZ_%k*Z^~6gws~fJt$YN3Gg%CgG14ck>`x=USx^Rtr7) zP94qBtnhv*0$~yG_aJQJolU*Akq-nGG;#zU6Eeb7uH~JtEfDm(W#D_sTA%`(p3Res zcpB_W58eps2{N`4#`bBwPPm67I<~fqXed zs6`OtU~{sD5#!jS%OP8Uh{xSfWf!-3Z|`_>L4=fI+kdS&!vA<`GSIT3g0P}J>d$$y z6D5VD#hHrx*nj>`ZBy!?D$(+>t~)IhM{3>j-f=6kUCtgJ>dI24MCtPN00~;Hz-zED zHQhIRyu%{@ZtN}{8gFyTRLJeUO9l8!HnFKXw#8{eYe#29944!CZJuV9u zC;n|k?xkh7jId(b zviPQie5u=9ED9v=17_*Jkk&1`sfk3YYozpU!@pwSS(zuN825nxYc z!+nQ%6tqenB0L0nyae%L;{T8A2DTXQ^_7_KIlMvXj4r@q`+sjrPx{Be?04eRhefyO z+Ft*MmtX6DoE|kxrEdQ?b4=-y(zwnV6YFm)EA$V}g#AD6bDlCHRR0#qyz=tb`uf6a z&XJ)w%PphGPaEZYauRpz2_PK!MLGz{u^>eG-uv?4DDNQW;X|)~>0aCcH*^jB()2<5 zOAzS7SmDtp$K=gS=2x9#A$B*@?B3f8&4zic04$j1^U<|SLJqm|eRbE;X&Bsk(^W%xH{RYW0()QTJH6F^Z4OCM z3|pmKZB-1xLU4E%WCj;$*NhEUVIHXji62w??{8aGX=u3Rpd{3(*)dSvR)Od8kx+6gV7w zq-q8>N4MAxs>v=jGSuY92Kqe3^WXY3inr3FZYbj1#?1f7K67jA;?pI3`9fPyzq5W; zqE7A^sJ~^~8OZ|{eVDXYXiwC{f}jOfjXn*d9?G3+7p}JENexoF>YwUt7(OhiNWHVvWh$yoVL$^PwMDMh8Vg zY|~ek12U!mIBn%3aVKk%UD}K2H6M?Neeo-u7X`*e)1OY$4YqBidek;U*?x{JS5Xji zfY{fVvP74tQvBHW-2iQMN67$D-68r8fBoksg5+Xf%o2ROUsH-vsaoKyh53%#o@*#6 zI+XWmQArkyB;t1jlQJJ~z30ux2f&p93+R*~hFgN}_g>uLPk4j#4CqhSDo3RyWfK{W zy1Nb=if~1<45f>7$HxrSSvrW!e2ELm_aD%e7Ge3+lWB;2s?Z+2bSOe5%M5fsA9Sv~ z=fH&Bn!V2@X?`uq`-j#_2n?w=V5c8#*q#w(cP}ZWoXZ);H3R0XeBb2?(Ub#@=l5OdFm&-j2=IXU)SK665!Y3rZy_%JGMYmt+xnQl}q6Om?S_m55_H9x*%4|&C zNn7J&^2Htlv;FHIrM`?-jJJ($W88Wp(L^d073YoLl-5qW26WvTo5^_j0V+0@ZtuQo zGi3+7Et$;9xG%LC_eZ3URPLNktEbzJX!i^$6nQJrzA9AyLzG@pseFXD?D~Z2Lx4Z^ zY7Pz69`c#eO?tKlZzE)3F_;^B?W{M3w}*&bK=RoN*MjJMz$lUPSQR{8$pMIhXOD=O zLhJauXD-$|cci=evNNj3^e_mnsjTz;g}z6%u_6!7ncPM;@PwfsNSZ-1v);q7i~wau zGj<@GI86CUdl|U!3oT!kh-T6=M?B|<6qv{gJ)>;KsJ!=rmRik94R}5;ZjkMkIm+MM zEzXx6to@}ZorVsPMVPu*6ts8qp1bl<6+uE;Dy;8qiLH=1TEk$M;F=hfk%|ntxv9~r z(Rtc6z`dd7gCp;{zc$}5?hDdqh}3hBgp1_29fzS$Y`2VMcaJ>osWZ%UuF-hnI43<= z4U{!^y0QFT>lE$m%%a(>nKqR?{&R-Ql0)9ikIJikcss&iz2^B*Q=&+TFYJ`t3y z459Um`^Fc7`2A`nK!7 zH~!0W%^Q5BM}i@_-LEcxd9PgB;JI~qEE5a|H5#fGynFS$JMaU{sGUPZ&ktE0S&Q|j zk#tuF+q45$ujl)OO>1~GwUiqQ z6jPEG!Q`G;y_1yZ7av_vy0Hd0QH--w(f_pzm2m0)%15qs+EJv!!WnPXS*NY$@PBaS z%RJ=Z2gJY?U6AOqyd7`T=y8>cFwpi)~gJDRV)0t`^%RXt`5Kdte?%aCl;wO zF8`7>3La`uwpc-k=t)WT16(66el3@p48Jq5Lxt0QyOO_sU%tq4H!LdQtyyf@uFPuu zAE6|gbrpT#7q)!%GWPDO>&0c|y}R()-nkt&=Yva=Ja@D zsksw;T?V&a2F?b0mI_}9c&2x~ux1Irgf>sp#m|2hrqv|aYgSpOMRtCsdjEa`1bPnm z4m^^_>4vfZ=^bw^UkRTg9n^T3$}O3Zkq$qviO1vvsG*&6Qj{fK`LK70dBRTP+ks}c zeY;q4MaGDUftFL>71#1|MZg4@XIAxg>)wu7q5yw<*d|#wt@o2Pm7NjPCx?YJI~^Ih z)G6TPs-fqR6>l}tQyd~as`*a0@jPP*{aN{Csb}PlYra798we3Mw5@1*_n97#1e^IwbO0#pW|Bqn=ClOsmRVwZVY1j-7eIgv5 zo!^+b!{vHuB*Diey@rf&LIH7e#|MCu+^~els&C5lVuMENvsIg^4HKMrc?=?;#tQssjrQfnxhe~$k)rl&DgalRj z<0Ys{hpV%4HwEjJ&^yRQ$cB%;?e+Tn7GHgQT&O-g$7ju2O_pb#KfEk>_p zu?v=Mb3eN?9KW74<}e#>TtAr22dx9F+0-(6eo!ir8k^WUQ|&)w3MFjKdj`@QGyJyy z+-w-&aUK;c)TeTa5rC(9{RDlvSTVc5VMGd2$EyBwy+WS^M?JgFVj^_6!aQCa=YLfU7d)5X zQ}8iWi8#uBVK>LPj~lBl85QN87=@KnhcxR%r4&XblA9sx==JA~LYGIOyeo$53kQz0 z!$(Fpvfoe2M3TNCg$7{>qff)iSCnbLzCNPwNJOv8{|+_YhC!T%K7%g)2K;_$*nY8_ zJ*~5&m6pXj$^ii&J^-6~-wRvFA9U|?{pjg0{qOBsq||P=S|hj3POIVtiD%*ed?_jh zgoB{V!(CcuYMn235zt~I;ypKrVYl>hu+9+OpaeXGk8=-|jvC`i#lLuT6#lh}>)OZQ z<15|>lfz)6o^I}s3}*NjfBf$^Fn!Opz8}#}JUyW8JY#k6?^l<89YO;AzxlwmM`xPk ztVPDuch7xw7?EGlod%POOG4#jr4!)<0__jshrnh3S2GO-ba%83AKsEsxaoTG=9$l( z{J>|2cUl}iSG75->VJtfuE-sk*smrzn(k`n4PD^_mo(oz7SI~9?co~ls@d?p^sXlf zuH(x21 z3+%<$9v8UIXIRzU*wSB@@ISOz?0MYs@;_f&dk%Rsj$#~Y&?RE|pxplW3m0IoKGS(G`0Pv2MTfcINxPn*5;JM;YZ3u}*xziaW4oMuXYZ+*o*X!;c2*5b9EvZwWVyglkh!v&$%Y5jS% zi$cm9L;WGPqboo=9LC8E6))K5N=82i5vD_9bvWQJMdX3DuV#j{5dXE8Eu|8NclS6rHlb z()X`5>ld;A-WUCw1b;2^%&W8VT9o!x+pQ56xbakRKw=^Qw!lTY? zxg*U6=lwnflp9U3RA6eWjCU8C84)_~Kb`|8zYjYCC|!)lNHuk31W*yEJg8!V{Ox z2C-)q5m-n6T~B1nVYGZ#(5QD&cLveauHlLD4VQX=YPYZvm zNKf;mft|_QXN|XIVK~iXx%P}ns^4Y!z(72b_ql!$XB(&D4<5EE*ScK}_&?qpA->rl zcda4@&mYazm8FVVdJ-~s%)fD&zAMM^^YswJPblRbM|P&q@ZWov4JYJc%-a;6*9u1A zH>O2!?ugRzqS-POgfLX@exURjh;3QhnBOqBIrod1sgo`=HwU@kEvHLbn~I^xLyWgCEH#O zvQjG+90epXbV(UUf#Oq5g95B=8==PXA!yOY%+k&jmMT$hj~1)->fZ)YM7FU}C<+NiKy0M?Xc1wAgF$Z?|&I_f}lj z>x?oX!}h!34GG?|LRL#p#$(q6B}5EV8_ySfvPQ64jm)b-=9QcxXZe(hhpn?)OfO1x z7#Y-w@{?^v4RXyG-Z&=lwed4_{{#i=lx%Y3R=CV49|X(pNxEf)Py0^n;lr*4kgMuN zc;Rs9q&zt@oqn+XCumaC&UI+!ZZHZJMK+eM+0!Ry&pR6W)xE9iOC1Nxx$$laUUsJk zOcCzhKJs0s7}tf$a*nIMncm^Y5E-+sm=*#^@IH7@J2t1^y#n>#wI?%sl~_3~C#R_s z8I{FT51`J?P#=7;+f~3g#`#Ne`8Scd0rMDQp8cX@mc0s!yL@VRoQY5-9kH>&Q=@%D zO0%oA9N?3bfw6R_--HQ%oAHIp;E70*46t7C?pegFm#j}r;ZM*TYGJK|CLsm7viM7P zZQl*G>lWM@<(Kte=+<2_Z|Hk7lSuZ7G1V(F(OwjvNpS3J&@aV2SvUC!3O`ue$keM9 z$G;)g2u>$C&q}z;ukNDPwGN+tKA!^&)W0@5#V6Ujh!`Jo$+7K5$3JL%*EEzo zb~T>E=v1_W|CNXWpZNdC+Vj5}2`W|4VoF?-NhBF4(PYRW$a`v*F1EI-DAJHwWF_*- z4*447$zbWu*mDF(qf+}0NPB(+M@!d z4#Cx5xcqzudHJN&6PGo?O?hqanFMSVRKH0FBp@y5N3zYgEcx}M%hx`skX*gtW3iXG z%jROrIb$ANDc-i%A9@O`&gv}W7nW9)e?lXa@m@hay7SQVOLD!D(@gb__DwUKCv6xp zbr)WWHEt+on=RrX(Ne0ibP=mQ;<>IVx*_%3z2S7O@%d}kHAihEmA8}OOzdb#la1f)D-OXAs}j`h9{U(f5WqL-E0Gj5p->Wt;~%p@D4q$`)oT{e$? zLv4(iS16YzMPJVI9?@r8goe^*eQ3r!5LX~QD~{Y3(4X8HtI`@zTRICms6q2==;bvo zS9@=4YJUGxtoN1R7~&4fMg$ni?!s4A{2`_=rEzx zcj(icHplnz&M*ItME^V7w55M=(?0(Fh1ZZo2#ZEbwY8zl^XjWiCF!Ojt3N(LF@6a* zycI4_sjei%g&?MLOWvt#U`W$OL{=&LGH6dKv@d78pZuK)(@f6`M9RNRC~e>Ot^A^~ z{dRg1sXXEXru{FYxUnZKPb#E;Vc;)8XPVjWcu#H5;U}zV{t9)@ej0CV*D?*Fa69DL znqUxTVjrG10lM-lw3M*=2aq1e0O&wZ8v%5AK(zTaWt9Ht$++)8`BwKS)$Ui&`4xB~ z|4-|p1e?g53`UUN-QBdgp%PI6Q16Gu_4B_eq0jQ>1HE8<^*u#;`P2~I;@PIGi|TsG zU*)&1^ZW*))j=rW>rNd^lpo6aewBX+qDk|Am_)i^k2GE?UU)kV>G@J`#|s@X)`k0P9ugPj zoEAFUzS+*{1Su0tTL*OYB1$UKJ%+=T7PVaQt_v;|vdB`B2Uzs4z68Wn{JNpMa&7Ze znNwp>4HE;FA4BBFxDGeviIKgy0*>lZB@(TQ92>t2`2qP0Rdbt$wqIgw839&?2H3PegccCIOf9KSpTJyxbR+>L{+@AMov!up%{UXA7Dx zR0S?lU;t(J@Okj%=47t9awD5IF(*nli5iue!b*}utU7JP>n+@xfV*!6g8l*%}1XMi{KDReFz$AF{_M&ln^>ATH1Htc|60vph zxI(}iIlK2v?a#;dcxU;aAiL%d+BCMROE zC;TAD^oK-2f$QjFxIUgg+&5$%PdATk*T6gUMxtH<3nO)zej$tO4giLhovW6nyw$Ou zY`H?uzAg2B3+TE(Ntewvjnu21*7|5+jp6z^d^~kpGG=vTwm`AYtZP^usmX!hB(jmM zd~%Uxoof!!J!8x?yBq8|aB~g#_+J~6xy#*AwS5R*-}YGhle#cgJrXGv0!0G`T@mQ~ zz~tmcNL$-&xCGu=FPTLY-i!qUW$2s7IBv|KiGwwoVbH=DE^Wfj!6aM{*TP<%JyfJF zvK9i46Z6&)AHjvx`{|v4SCvCzmPsP!VE6J9mlEtWm*IyTII;eIi`WbNm+@i%sfCYP zGTzZCb8=aJ)(=lf==}*giwbxf@_SrCtF;8#xz!*PRjQ&jx}JH=M(g@uauIMssF z8hV5bfC^zJcP8nnGJq1(U{^M7R9l=htT41e%9BPoy&Hfj4~VJHe=99HU^G&dXs>v# z<0`nJ%b}lRfb~$CDxKfqRb*8I%qn9H2yTH?E=CN(4SK=)zWN7srTBjfN#fB zz_;VHREp@|rBa&ODb-HLCb-DvEao<4@u;bECcJTC7uS&cc(Hq2G1nahsv?|^A18JM z;T&Q%2C2Fp9;4M7pC~T;J=fu6(l5bEgYFv#*Z*SgJ)@fHy0zih3xWmdO{E6|(gO(G z>J3PcAfZWUB2q$V0YXPq1eB@}Kp}vX5CR0G*MNY4fS?dUFVdxVkm|ejF8aLBIp;a= zIA?rgd}o~fBdm>av)0;c&pp?i*SszS^R%CIN#tl@$j<$!ooVcfHm`ftkHBj~J1yK< z_2V*rAlYT(me`S)>W^UR4p5o16X$H4G}01uFIG}{;xf^HLg9qb-QwU_S6jVV%|*Rj zv04(~%dTT>yqiS%Z@QM2$}FdbTW4VKxJwfPksA8Lha&_fCofB0Hg~CR$Vqj)<`h9c z-5INUk*NXUtA0sgYHl;wT64$tO?2SlwT^lX!!BcwuE-JoZy#>A zeSTL6-TC)A%&Yp6r%Ca$(k~+S zr>_j#-(Kr%jeq##st42|`U#cvuJ%OEMDso?%XeUnJDpV|Sd?)B*z8{Vny;5yM07k| zL#}z?=gsaSTWTsCUY#K4zaJkMAf(5$fP>UuRPwn>Sx3C8|auNbWsz6G+v)d znYA`_8utMw%En)@6D_>|Wq7#1Pcz3>8qC)>*7Ne*YRb@uFP5QsS`wpD19{n|IBU3M za97vha;Of~WICN7ShuJjqB`Uby)OPl_a5$EDPO70ix1y>eSq7`>Ymh>`}m`EUP>9{ zXWN-zr0oa{w^=wlg%AmH#655QQgo3=1K*ZMPSZG*ECZ0AoGsSS9i7#JMf3AW4EP(b zKV`iDpu8I6g!B$vk!R{ATs?ql1`)orCs%g6<|IuxS?W*Rm zuEo!bHR`#Z#oiab5B|6wm63BhxiIdIy?jNdQw6&#K`Y1;nT6$BU#Tlw-By1QdXKqO zmC^g@D3d~TLIT@>0V&nH5I|3^8Jivc%8M-f$d&u5Kt?dsq`tOjuT_J146*<`2BD0z z+Tk;%zaM)7{E>q{Q@b+1|1*VU_b*iyCh!JoW0icfw-mj_qgSw+e>3W zEr^OEBh;W+lp^)8mXP6OL17qHXQPwdtNjmDg_oopUWXqC-a2MGdQ<|-enZkmspH0mkKq(S)6YR+&8JrUnle)?J%7V_wgC?|?7Pkst$c)r_DVJNEK~NO`|cL}mpxq&=%U#zI~lhfSaHPy zmWd-<6t~&8Xo+AFJQ{2D0z(t++A_a+ zipCnW?RH+{@~K>R?aVuPz50~Tk-2ciU7wuW9msua+zGEAH{|EXK~~pa9L1Zazoh*i zf2lN#Lkj3ur7}cn&4kVIv)e0`-yQ)+?B?xG%6b_Une@)JbC8>(EnwnU?zkpZk$3>2 zY25Go%+}2ChGox&#S>8s_w=KYD406!u{%~VOwNfo6_dZ)Y55_xz>ZEtH*64$sH#U$ zX`6BIG;dwzI33fQgo#c{+1c_PmYYu(%@U?khMPS#jx@XFr#?1R4b4OiW~ndIvOB1T zt;99_> zL}ERqXUbb50;Fpr(V6lstFf0jx>x2#b$WLtVto1>PDf_~!B>xTiRx_;RBoQr=b4Yu z(k=d7TA7#MZslGX-723dx7Y-eEBPs~+6ZrZ_1R4C$|?Tb0Ekbp;0slNt8G*G`jtTP zmspvtlD=UhAH$woHxipWMaMBCCzZtX4{Y9LRT6o*e8TW9Gopfgj9?p9dOLYZ(OIQL0a>KU2Ljs@uGpHrClL0@KQP zv!CKUGBy!f5-<@e2j~VqFHX)`Tl1!pM9bigRe+mgw{b{bOfXJD%I7i5ZmIMXY?i2b+T(qu~qNtHN zIQad?eq{N(%{d9z8Kj}@n87)`6iLsyBh>71wUP#afh}M`5ZP}JKK7Cs?{`2%`Sd#) z2yFM;=Z$?Pw9@V+P9$1q&D7`7?Yc0<2#1@%R`%5!B++i??*e=a(j8~N-rwIUm^yL` zzo(~Xypfss8|dC2kHH_G$upK(6}Ji&$kl+-*-vb2kC)+UW4Y0#j1+LF36LrI>-Yy< z+J94~_Bq$jly>sTZT7Dn6`RZ@#5B#{{<;QWs=(7~qau<3;`AFRGtE+m$u)JE>D4#T zgdYuY@X+6wR{yHM{4>CTSs1S_za`tRt0*mLHJLAAHYmUWWJ=>+6*&EIU5IA3M|PX( z=+O3wxc;)8dmCrAGWs-WtOH0|vP`FKO-(f~lD(=PB67#Hv#=t3qV)tU?9PKzV}i`p zzww*_9l*|YYzJ~Au1pMePE(7E>&c?v&Ww)Gi|komn(O?z%05L5&CY{9`Xn;@%7G$~ zxoL!gQGVMU`t;MGL)1W2aY*`z_CoWZAy`g8$tv7-09SJKOBuwlj znN#{%O0kaVQOQA{FxXjiE?3u151G3lCd8j=kYe6wC$~?v?vb)Yf^s|%LqoRgzNzui_Z;ktJeJm~Z z`yefT)inKuhTBjd{icGg-=ov**<~f;p9+ARz@!2?SMjHdwu+8?`E2jR{Vx8v)a(*V zN1MKGe_OqC-%Ta6OuwD*r~+^68d?Q*whnEeUaz!`^G@^Jx>q^S&1ho4Uf!mKFO1sk zO7F-KzD{As*^+0lmqz&e;@achw|Xf0R7aSN$Z9rI!s?CGo5<%0aC_TW3nQ{8EB6)9 zfi^r|kgXAKn$_k6(#&~URvr#{ZfiLc9Yv|hEIJNDHtVl*;Q1#y;J4e+=`SWSCELmN zc|kN2 zwHLjDe};vB12N)_EuZOhxI!x2dB6qjOH>Lny}$kS)d#T(H=jVNpMSN~wAFB~5=2O7 z3e8eZa*>Kt<(%7v$fo48ssI@gE*_BLowxR#rx~@xMbCC7z={Ng+ps`tSDj)^5Ntp= zhFJede*bTf^Irrx4?HWsWyuofX6}s~f()CV+fIcEPgk?nXTI^_7&|5qIXQOTGAcAB zEe<8MMQnPM#r|R--nd(8m|h>xP#|Q#bKz=0q5}jX1ZS1*aEJN~GY#}Fl4iR`1iJ91 zvmrhofI8eGI;fe%&?sNV;$%*xk^2Jf+e0((pf@?*V{0>103 z$$v@mW@(yDBPhwU0byQXhe-dAx&K$M&Rgs zz)}GRGG3>7xy|qp=i4|qnO3ntPdP_ssn#s%t!AAa@9f%}2jg-CvaY+%*lwmm7fM_7 z(+X$W(oHdgOTjrkoY{i=$Aqktn}29igdggy*c8vCaN=H_}X+!97^4$<9=7K z)qwMmM=Gpuq39|PM@&ekfwca-{+2r!e|~*}ctS9jf&Ed;AEQ2CWv!TQc`Avmk*g76 zGpI&c=NERj-Cm|5ow_tk0BI&%gd9L3ag^8nWLKFi!*(paYuNlum{FM2Bne`EESF)c zCFsiuxp)_=BRHpF1FKu`{?jF66@!+;y6DU^$Y!mk^B!YT8>#~>b$?JIUl03F;rOY?`ly6;3k@3*i+gtP%!ZMC7sB#3dv zr8=-pX4xE-qa|5jn=oE6i(eidxe85K~ZMCQ^|n5;gk@Ui3uc!EB( z50K$((;IoFKQ%+=3GHiag1APTEu&bz3YZor!-fLnW!92mIOe&F$1@s)vHVS!SfG81 zdhWUK_VzGmtM_k0FX`qiZ)H243kgjfoF+J`8m|-8XX}*^QMk&Cro8ZHo`rHs#~ay( zZF}Gcz+HEY^~FP|4vfjFDPdrVxh$RF5R(u?&*R38?e9|8(%BBK%mgHmjY2a2#I0gNm#5w`?(sZCxUPaYJ4OF#*d?a}vizw-!3J8Oipy<0#u ztj9rHchPZl06f$xYwyr4Rk(A1_t}^7VI2v|gOKwZS<&+eE;)=iyHta7Byg-W;{vm* zRJ85@fa-O{;YS$oBkl_f-_R-fJ&eg^-mguVYCg;ha5+K1&4mTIvwseTw(d_at746n+DjuW{^X9OQW2U5rTBD}B*=_cfV>DkhvU!qB7bk6_ zPn6Rk!L!}1Pt;EfH7S9C`f;)r4zMCAeUoIeE!GCEJ41~GL~@|b5?XqE-RG&)U`ZLR zX_ka5B3}9>-OMjr3#E{wSE900dopW?f?FM$%U7Nryn~oF9psycO0I4UqCF+hi1J<= z0NQ4xxdgV?hj428 z9*)JkE}6jMa(pqtKB3~l_hdby2aU3J0BE>$ViDRPV@5aQD+Wi%@`+VyQ>z7W;YKI; zB55T={AkRoI>s^b_sB1)`2bmT!sW5hcJ!$N{Fo&S6PoI7q{bqfLm7uo6Xn)7$p<)2 zauuD^opvkT@4^DRDS_?trrLq`Kgei#S9HH2AnDu#=j9_q0pO#pno4)$n^?XpUo4<% zsMzJ$z$IAbRposZfp4JIZy?T(z{@q5*0d$l&YD*NKZ%%B)1w!dlVZaeoOh`4okYr5bZ=pMG*6 z_KIfp{2@Bs3(M1{?SvHFTzeF4NL@NDJu-eW-fO0Bb#{8xgs)(I@OW*&0*|>+i|I7l zoDF6y)xN35Pvr7|U^OyoL3Gv9_H}^zH+nw3-KyJ z1rQnEF$hhw?MCm~nC3$OuCOCF>LLIRVy4PlBc|Um35P@<{xAycKSRw{&A(t9{ySl_ z|3aVr0Q0XPSblfSZ=mA)x#mxO`VMQ~y$u2ZwQH63ecv@+-_=YyeF^&$AA14*8!!T9 zfgXt?>7P~td}Im&E?*sE%e%GbEO(ni zEVoy3ACJoRU{TZ63L?*L<2uF(6*alMYQdlnP{*4l$-QuYQpELz^YLBLtzj(eA)4oK zDY#M>n$!+fk#0Y-Y-KXYCDkR+u*FlOyuXK}p;go29NE6U)ZKjd^O0|$mu@8q%iD~p z!B%!~vhWgsYYi*ptKer>cNEO}d?zxauiYlKrxd%N=xhDmc|Oo2(5Eglj5u(999WY( zC(gE`Ep-}~<>d-Elhe;y7Kq$`=dnJIJjZ|$D`xCE$RgBWS=vaeZDBRVx;+mzW= zazbE!w$Y?FhqJV_s)S9E-4U?_Leojyb>H&isH$ByLK&GF(p<9;e%|~&!_cAzu_#Tc zVafM`j=-I+Q#KcW_eS0oHEDFSh<^S;t}sBsA?%Ep4NW@yRgrP%w(~{@CA!{=MK@DB zS2dNOpKucj$5T-a+CCP1RGLy_&oN*d?xyOj0cTK*c+mxFGE}hZ?G_P2ylE5_L8>ej zR+*9r>2X5jh5fU}P*siT=9hx7N;v)^r!{1x8_Tyd-8g#2kFM zaC_E#i@WMh&{A7wf=FNbfU~RYI#Mj_EE-;=yMMa(eK31f_fnV{`k=~~udt+aLlWXm zHgf~LlC+*ta7Y9_cV5Ji^h)l^-9YgVMuu&@feNMRR`p;i+D-RMr+(oSODpU=PM5M$ z19IC`x52pvPYjWvo?F#Evw#}Re=4VRG3XyCOMVad6;=!^u3y6E2uS2Fk@bT=Hg4Z< zCAZAq613ie5%C$cvgXoOIxj@7DLL9khd?il$MfOzE2SMDdJcwtNxH9^zaL<~*QsQy z6e4c`4Oq#m&0Y}a*i4Vvcr%Q{&c=HQskI2ah|GsKh;#IdAj_(zdvq3V>(tF+8h;AVR`GShM_ucV;Z*gJvMA$`XTy;8-uFoju*j}`RB-#BNf2-I|d z-Q{YRp{v^omI5ie(cmRqtZi`cN`cr2)IR*I1+0axFxlE#9R&|KX?rH(6`${(bxp{q zTd7KbQU6x+d0~j@gq&{FimEsq;rmRWm#!b$l{Z$#;U`u$-5OKn()LwAo4}J3CXA zx&}LA7@ueaks!88(+K5dG=zt{v`%H>g-tv0q?9=9A`0)+; zoyv8o*wqW$jl%9q=(0Q*T{p=bwVt@1T4qu>zEo)mz@kSCM1VKE1hYo3sz^B4-4kHn z_EFqyFG=st%*<``(c}B7REGL``56xRj^dWnDwTa%qHUGsT^EPHVORA#cDSw$VFOwX z)cT!7w+J?plvJ&oytxXC@M4l!*qwwr+L%M22srQ7FYD_s{_D%dR4#O4jEU0LAa z;5^q5mXPVSfC!KOn1k>cA2zZtpwaDWiVo(i`+OX!#h<^B%9H|7A-D6sImp~>8^HtM z8^kV~JHHdJXm%@Z${^1hXQ~5K1*OeeBz_Xwp2fzmiEOuo=RI4Whf*`?lZhr?4`N_Q z8)NDH+-T_bB{BU#gP1tpg~=mP8WnD8CN;*^acG>OXHg&-KOh2cVzl4ki!44g2cl`>aXS6_?=>pf)y%vZ z+2(0$rh~bo3Qag6r(2gr4OVZ_A9x*U>{-m#SKuu2EY7+yjgaQmobqB8NK*jb)nCW| zou-grm|x|$@-aXghR>dEf$j|wkie^TqX&yDoU90C&eTRK2|x0`=Hb;p#nW`EeMQCb z^)F4jt1Lm}D$MTlRvte6?PEWipypqb9cR6SEbVF@*@I`s*xric<$Fh@QQ+@qA1z4dqBb4 zu;^>)J@-A)l7iS-dAjD}wt{FFHEHFB6l7X<|2?^X61x3!2>*V+SAWT$xR`8NUzpr6 z_G8F05SqM04Qpi));dXMP0LgPYQmYH_B{Nn!>nSCJLd@rLbOJSL9lB9pT!0e4m(p7 zjN}^@q8X#MaJdH#b`@0@l@^tyLi!5P9e{YMZ)C_AH~LkfRTNU{PP|61 zn^7NAX1F98eCNrfWR0`M9W3WNycQO1I#cV(y#l4hg+9M^Uy+RQ{xTK6vO_jINz~xU zd+e-tIkn6T3o&x#7+wF2<4l^lGPuBlv2aIpt>EStP>NvfTozawk40!<`=cfQVqM#7 zXnB|5d5q2M!H45fj=vZAKAl@&jOb@^nO*$qw1WP?5QV=q{#8t5X76&fgi>>XgLAap z=daT5$ov(%DV zHB*#yv~W=e`6#HE4p0vmcDcd&0SE50d`bC2Lc_`1?b58ynDq5AC3~j`nNDh6QAu85 zGTQQbG^OacY64jIb?Qlsh6*d8hOd+_d@XWZm;SDm0@%Y%3~Q~@N-fFK#~JTJco7)n zbe#zcW3xmRwb(NrJkKcES=780JUWykj73JgC7+HY($#ehL-%i5MMR4D9{Sk?-A@cEIT=nH;51tJYFU50sIznWqrrGPY6^Ja z$;(XG(H_n_9wz(6ELVuIHB2UWhg8aw!A>E`g5290zYnA`Elzi7O8<~jIH*}SYO*T|xs!7(HK1a#3 z^Dg(G+H?Cv%QDp*K0IkglzsW#E6UXVcSLp<~8^=gN zNcCxvY`EwUU0`{`>A5JHGm3wL|L6y|(=pB_NZFDX9z)_O(#VV<{FGKQ9VZ3Z z(H0=j+zGLSM}xJs1E4}uxO-)MG0%9Z)U_mTF#b(TX|1TUs}&AGD=FU+{Uq&)Y2&VW zkeX3xS&%&;eRL(znx-Pk3YahAFIK9c=HD@F48O?CJUvoyH6(7%%|AM8aKac6*^Lpm zMiOj$ERjyc<221?1p4=wa*9`!;LLY(aOfBKQQ-$d;jN%}N0$xj(&FZ_;3A40a)Ask zbSkF?+=OMv;gH5UCN?F$|CyDDyz-COr>9QE7<`o0*2Orbo8&b{wNz6szbA9C^o8|y z4s)U-rlW{ttg3FNihMr$PLn{9<^kN>AJ*!ZeXpeDsqV>7E8O;!kG+Yb!(c1<;`Kaq zd0}RI?JS(>A~@exolq~`^|1bJSLzWs4YwI-&jhXQIU zevDAF??|oC(tB-T9@u@xd}A3ZQ~nP*-rYkSTZZRtsS1eO?%S0h*0} zHV=M0GBWFk_v8D~n@2yJ4<%0juEnAMz1Z}PARGPm70b(wkL}?dW6Ui*gJGG)r6IZQ zL9|p&^?I8UARTi5{f~(Q0j#FOJ65m_)}YqcD$KXIUwV48HD&_NXvRw=!DpG`fAp?` zSecQr=s-*_SMpiAZ@*sBrX0zZIvzay8?%SlX(OMpKkwKt%>&l2>QcnQDzt0|eR^Jg z1O2o=M~%MJ;a0g%6p0vx8i`rBF$Z^zic%(d^uweEEK-IdsTn6Ce{}8;o4xmas6e|t z6Eff9fiw()cqgobz{P&nix_T<((u=GLI*ol*u+CW7}A|tS5m$=-4O)~8x)wbH%dCL zi^6=~=|%Cylw(vIBcjgM%`Gc{yF8-FltF?e0md|)iJPq8G&@#*LD~ff24W&nIq8K^ zMll;jHYYonv_rn{SgnIcvy8hutteLM#8=mK#}K1FEHB7vKE&FmKv*IB;LPNpB2ybT zMGvl_X*_z$@=AyO1D1A`j;{J$e4M{q3^n65K+$tBKm}BkV_*n5(!gSn(;)}kQSY)RXm3?wt#Rr}tQ&c6)%36{wKBxnYk3eec0tCk z;4=AzO>TyVoiB2%LSZLp(81P8eO9KTa}C)57I|;_)@MV%@G?ZGuNUBk>jL&4(O?W< zy@2X%1VG@-7xDU!rS;EMzbzegC*Cb5B#lTp8O!XPD;yvVEN92!QGlj z(n?!BP)?6Jq$%-xcc*cZ7SXy!ldCgiB^>n&r>j zEPK>JS0FOZ*&4?rcNfad?+Z(>0OXyUwCxqyv6g8Df?Za2uiCviDXue<`0LaYB`CSu zCHpRzi6?|wyqSg3TQ#VT58qm-^itAk|B@lWMb!bZGqr9iPx|k58891&61*tQqYRAN z(T30xmU`R7N-dpBc1c3_bEXVV){dB+O{d-oScuM%9;E6CRJ3s_biR1-5V$Kpcve6D zrRc)9sc|*yC5DpPGjO+uLHudoo*F?_FFV`T;Bj28z7e@zDltq^x;uFOO2mKW9Y}Ze z*PP?yy`OKXo8eF8TsYq5U2R^jWMQf^*AJ*fHE*=Jo*%H(F&P{$qUyar0sU*yfI#1S zD)qg$n$oit*IxrI&`H#UtSOHN_pWNRqNu2nX7e^tWiSS&Xs# zipM}xflSHf)y}=VM%(4D{ePR~fAibRc^~o~jZI&6Ia-I|VmYnXSDO_<50w+>mR*XG z-mj5C0~*ci60P!BfpyyenyvN(@pyx+$k5H^-%dfyQXi;gsiyJja1ezXRiI`vV-v{q zqSbVtVnsG*J+dL3uy0WY7gdOMyb$S2nSfG!_n<41FAtsWTXXB3Q1sOc95z$L!bkUW zy(*YzBaSV_!jI%|OBYe&mwgAG!0z=AndT$b-pU)}gwmxC*d15CQdhqP{VgDg&InJdV?L%Sn; ztpdPtLf=3#x%#0V59|a|7pF-RTB+$bumaN^^`vVQ3pmT_;Nv;u%-U-4hxeejQC}!g zmU8O%wvuhv+fCX>mO47uACz1UX$+TK-ts+a1-h4I^3blMuM*q{$&7r0i_G%I2+br` z-kVA_RgJS8@xO0JeypeG6k6ur+a27lmud}{R`7vH@9_iC+W$TNcLx4<2L5*j{&xoc z|2P9WCQ=7qhu+DBWy+KiCg?7c=vrV{;z<@UCO~icd|c?JHz2DDaqSKywyyWKtblng zH-5bUyQ`9v(`@!aci%8#>DFe!<*^%zs+&Ok;L0 z$ZzFue@xDS;Nr2OS9Vn%?BU1r%rZ>|mn+tlT^3AQ_YW6!`!&6;uAqD}2M|$PjZ{KUo zj;_svPU$7N%|=N?du^CzkwWg_jKVaG6@@oticI!YJ9$%)(pl&?wQT+bC5uwLKA_UU z>Z@F3z(~R>`a~MN*gJLNS*{K3jKpl3OuNW}`_;GPP5p>6^RB(kBv0k`6FvvNw1ye?s+N0%b38QuG?tB`l2v_5QIKi=#`pJq z@;ZVb!!VpAGviZpiKxYycAUc(9TPZQUegoDqwI6yIN-Bd+ndR0UA$kB{XmB9j4K!s zL>hD$qf|@t(77Q|Ih7A9uQ5NUkb9tVV*=Q@ANF-~-VF|F5#K+4?&VqQ*@D<{T_xa_ zsC8S5z0dF|;Q9PW_|bpx{rn%_H>;5ps4otI5GGm%kar9^t94A9sbyZoibd3M4R3EZ z<)CyxF!uSw?|1O(sqXkUrwBF+1>6u+5Mt0dn5?v)j^DgcF|B+_^U>mCg-Hk8og+Bn zsY6Gr>p5LJ2=zDn5OMS26&(+DwZoT1ab@M?4+()>vGZSS@#ctV+X7?gEMz>1``yD= zabei;Yjim;IIfd5uO}+N{fD$3By!y_ll9G{t}U%5xD8$`ex{EguHDg8jJfuW#;j{;?c*#*gjQCygSOd(M7aL6I8L0EKjQYgE68|K zn=^4H?!^Ad1gY`iM4?kl4CT`1NE{duiQ@wVC6GP_vkKpb`aj@c?M@T0F*LCBsQ1#7 zb<9m(S85k7t(w+@mN1t=>mI1@qapz&As%Ukxnav#@|5VvlTQoL9kItu^J#+I6yY@L z?q*9iFWCy;AMvX1PaL{muZmAXGm`w{IYesWJWuq4E9E=4&)ZWOV&WY=$~K`X;FsGa*!z ziGJ_)5SZ|Pz^jC(F*T~ z4ld3<(lf@-kI9V2P!12FPpgf}?yclq&fQ zAC9-7bF$evVG>&|JPl`TT$MUMpfJNMgUV`*k3K`H) z@?E-_^`cel4%Hopl|{qikqWz;vG(43{h{!#CwS)kP)mJC(L_62)Ri@(55;knJm+gM zP*hIz`FY!tUZ!ES5u1`?gMtt42mAH)-n2rw<|EhL-Tkw8@|s;yZP~`)`UNeu^L<+H z4}Uq}4ElxWoKciDf}r*cuk)Qo{lagr0KRivXEwln-VExrwe*d*(-F5GI9et0c<}D? zWYrnBsIWn=yg0lC^VjS2s9{lxM(!Vs7eKAMa^PX#PTQMeWzJb_F|DwvsUCxRa zdBmem{8U%pCA&Z~yK%FA7&(dEAB^6Y#_UQeulc-(7Pqa$@CJ-UX9E&v_s*4v0+Oyy z+R^fu_C*y%Y{<+ZVL;(V&-QwlaAy{@7S9MUV5I~DrF=K%@2gB}6=lY5b{k5Hbz|!0 z)22tIr(5RN4djJ$D}(44N<=x&(SpduqAwPPihOu4z>++NbQ}1Yq=p4Dt}R7&2f4-` zODoiRf;W4I(fFXcO`cz;dB->B8V&#mK)rZlY1~AXBb>|l#ZvM3x?jfV5o~va;f<=H zA#y#^by}qv%a_c=lojQ34uAv(e-3SrESI;wT%#IBo2Rp5?nsaL2GVw3=+7lc3&vxvQpJQ;0P^X%m(Xp86_ny(dg$icB~PZ1Xy>!(fSNHN)>lfwQniFHt@5b&xB)nY zx673Z=i^4FyHiEvih7yxW4TEx6XK?9QR0~1 zMyKr}M>a{CZK3HlF>R)Bd)@XdYE|d{MUtRgnUei%&4GG05fQr;kA`L!D|_3*L`Ks9 zdEMzIer*U>Z6tNk0zt3LE8cp(BE`AAL2*?pikdY%ZYOB@>QXpk-Ums@JzU!0VCbG1 z`~mZx>jXj!-nuvFed#C#4CyVMNsQ1mxKv1}fVk4IWLFw@4!Xv{dE;(%tz+zJJo`Gt zR#6J!6cgX1OhIFF2DMmE%(i<=eFF)(nV_tZ>Fbm9%B0oqz;$Tav^vDoL7K~Jz%cgx zwRTCF>|-`db)q7fZg&O&m3DOklPP<1XuPjub$aCFRF~#@z3ul&JdFI+L`M()!kzS} z_eR}=S~KbRi@nE!vlA8+i7C&>Gyi16f2(*1z*t{F6EqT1dhcwS%rL(+mI~4Zcaj7J zr!b)k9+wnf)e)K>R2XQIAhQZ6h<+To7h+)B9Z0D>#eK~FI7xS`p0D481aTDvIG}wJ zP|-3LPjrcJu`~5HI@OgqBNf8Un4u@!<%P-2DX~X#a)0l?Ku<3l=ZHt{9c^;6eo1zL z|6U`GmeawM77+(6;K)opT||PS=Pa8*=_A*Npwm}?f&Ry4GGIsfE}%LAH7nRSAmhd9 zQP!w9l>5boZT#&Z!&T$4#6^QOsZ%T8Ck${=q5WO%i7LGl6Y~josP&@b+1W-reFa+8 zvrCrQ+oY<(EkIDgZ+J?Rs;ZnCK;jc z)e(UxsEw=>#-QY`mFauuMlD3Rn`)}Kd^V>}SCRE8@1`&aELvt@3SrcNi+9kh0tDo= z@N}E^UYT39rhdL-g7n@f64f($S?Z|rDXk)HOtcMr2}#(lSYcACYUWPuAv)!sAyc zH}~QD(T13@cyL)|DqN?mX7>8Fcu0G5(j$GyhC@@+4%6fX!kq=UcDHU~Tu$M!Zhtf5 zk<#d*`2YjemA-6|{!R^plW%Nqqm<}p%Bhof1%hK;uTn3Ydkp&TJNi1FTn;Cd%872D zhL=+A?UGXOG!;@ciG8;m0(%K&mZ3V3Zjzt@19>LPrZ7?$yo|FL4^t6X-<6)c#`}GV z{70k~T)3;UbNs7@`CQLEGMk3R`n-hVhLT6B&BfwcpU?T%>%%v>sqMehm4Xh#rjuiB zsSQq->oj4y4iG|+uGi+91UdQVrk=uDyA%`5!(OZQm9Q9(W|ps9I}5gzvKa#oGxM{e zS29X-w17P;&Dn|~nXX?i5w3`RGUk>uv@y8ZKE+nQg6A>j(b-H4rSo|24em=+EXY>H zUTJ<&S~sg<6wkgA>zvhM2)TMP4kBCGM@)_YF^*a9csKRtpSLkJ$)-CWIcEP)jr*mNrEBVm6GR&DJ8*Vsccy z3(Avy(W;YeMe-Z8*O`bc80a35d!PUVBB1!=4FDOk>$dF@rH$Hwk23g3db446`{2f(P^ z&UoXA6xx@V*XSs8Pth%F6kE(TSw*LORF5Vff+JlwX-leMwK24&V)Y5A27*@ zs|3v*qmUpD>w3`V_`*c#hO zZvxt1r#!$YGaQ#mJnp^|F0NT5%i8#OBOWLz_5`v}Os;iyW^m z%^k3_)#{9mv=`buSuQ9n+bo_qKK!y_%AUDoE%tbmL-nP3AirC|;T#BM`-2T*PIBQWRDF?$YzA5b;J=bkCrWNM1 zg{X}Kodo`-=}ybR!1<6f@1gk1%O|=kT9}L$x?Xsd(^E{48d>4w#wglW7mu7uX%ln_ z9l`KgCd%B?@hOnpH`FJZc8%g~+q2-vhBUq+gce!Km(S*lJ)Y6HcDE^m^p zN8k#xPD{iiO#JTOkNp=Gan$*{pBK?_4BCM!C7ynOF(XGWFwPUcfe^~-SGpsy&upD{E&R`) z@!s;*j*(tuo>b^jBlBy6}K@IZ;z;D1X5g=?PWYJv=Sl{|0X{BhV#5 z9xo;M(OJc}3w6Uo(}wRMZuNwE9=QFN-S{8yleHyH744~GSxP`MI#sM(n1{|!3mcPF&AVmr?%t2AQEizh;T;yc&Dy8*JQsJU#tV6ZNnYU*gkQ&IjKyq z>LDaaw|l>wxYe_+={K9{mk=vPo^;%e{7}vR9r^rs6I4vmG25O=nzB9_EvQZ2YDT&h zuZcz&eHh;2fAs)HUOWA>c>v`<|88lgQgnlyei4m9VbvEplQA&e%xlw`h>+1lyw=kb zvY$RmZ^v`iOVqd83!705V)E?yvUH4!&duYJE5xKFjMLEy7bn46MlyiQSG-Cx$FT3@ zQ4MRH@X|iG*W*c*cBu@NCGW26p!?bL$S4<>bVjE?4So(;pUbOxVC^C3KjPO_5b|*B z(U>f_uW(^mFN)GwxTY;l`KMjvQx>NmOYFdt@|$)!Ei#rBbAAjZVON{XuXX8NWFZd< z2@DGJk7%e2QKV`7?^}rpFCFLKWS>6s&T(y>pFlAR?pro`;qSbyLGPx)dBXtrgO;2>Tu_RQ^6FVb#?}EDu z8X3!B*|LsWSm+liFQ9$dG^q$;=+5qDXjLD5_)92Fz*w9I7QqN57DIfcetRSdxKwl- z4{DSdOr5H&=MLJfL6=>^y(wm&K1nt`D*t8o_{Wn(?>b+7Eaq6z)pl!v#D$B9LPRj^ z3Yl;RD0oQCc%!aO6%pt(cxj3TMEnqBP4i9IRb8w2XyKLa@B)9we39%BkUO$7TE3|k z&{|1Lj8j6*(o)sq*?YO20zKbDO(lmKR&FoI;yN_S25tnXl*hNrltfNd0M51KF*Pzt z!@4Yx%&Dp;7@Lc;aEEi6u!-u8miHk^{Pj6kjc+r4;$n7}60^4%L;~ivuaFlJjYQ}K z+>`>)rk|;rQtdmM0PjBn1dKGcpoYD-6Y5d?S)mtM1lA_MimDCVeGuzO zGG{wDe2{=}Dr=Z^XmR(~Un@+2E0Q;%(_Qf^k50R|$pR52=8GXiWnL-UqwbO+i5s)= zG!>E7{14f8vmct6g(-vwh~>Ws=r|?$c54W+|>H z1iFmSxt{qsZdN^NTJ*5r7x(C~p!?o)HQIs(ETsc!-J&DcN(G5JE(>v6FE0G{z5hXj z63(f4tTur| zjVk5es}!~tksC1(H`yCZ<2UUyE-fY7-Z01sQFos7HIZ0gQcS$-nhJGnVI>-1$oD&K zXk8lbi;&gAbQXflarV+(r3VVGR^Xi{Exv)wElRB}Dl)pxFPO{K128QUi>6hzO54Wr z;zGQ+XqEWr-uk;WviO{8+t7K4!-7qFV8wGHMqfNU*B}M)iT?Sd5B1T6H?T~^(U+AX zHFI_KwQ+06BG%O?YNl}U@B7a=Tm$XjkYe+*Z98hfQVR~Yjn0HVO=yGo)JdPj+^X0k zHG_I6)I@ng~sm*eYhM6`7uy zu~aOj62+jJg(O1lYH6#MLrM_|QqmSNS7-t(U4 zoaggpGMduT_tVPa({$mj6pohmSlbx^`>t)*lS|+iB9U9~!Tx2H={>7(0^LFmDZ*A{ z{~V+|H3aI=vi9wNZ>{P>F)DO#QHtcWRxH84Rz{~ir z#cI|e%k<-2lEXotT~$j=4D1t8Jf%y&70xMV|AUTtazOpD0LKD4bkOS#a=hiq+n#e| z-pbwEk50vJ4?8?c7H-zeH^ZD$uH5B4-sdoZ zrT`rb4lKu+)4JH4sl@c*5(@m;_B6;AJ|GpRAH%$fa1;yjmxc(NP~`YJ2ptGP%`gm` z(+;A<%Q4wL0ui>?n13HQa&~)+NZ9Qf1MxV3b<^pS@_EABQ)~@T}qZOuBPY*vnUQWmP$J!L7%^B@3)k){KauyhQsKPd& zihq7BtJlGn1x{F)6F2wCT5HNA`V#Sw+zdE!Yzu<($K$&>0Y?mqJjEd~HpM^sGng<~ z9QBA*Gyk1Q2A4xH&H)s%!<~HC7M<4PwO~-kvUn&?@>pTBvFf@h_O;=@J&B;?`P35p zFQpRD$2-qY?>Uty1`eWSH z&l*vDLSMN^lQ^CJvpS}3nul?C&87W4AsK&^?>P-rV0BsXrK}~1H8?ItDrYJRjNzIX zKtnz3EFOvk`8$x8f(30$nv5s-Zu#@2lFZm zRQyccs+5#)%{Jb&hRJV1mUYn)uA+)A5g8$nV+}rVwmvJSPVPu7T@)?(F^gJnl3&FU zN5!Jf#No@&QF?338{=yZ^oK7aLC$J|%%3v1Cik?6n#AIi=~;e9YscFF{i`fQt#=Ac zFGozSmc8P8W;dssWf|-}e`j%OuIL|WsTtDTt**$?LzGP>^3Ln};MGY=kLzo-{Y`u# z9cjMiP?W0>$o;-JnFRi*hVgzy9g8_vb*wpK(Kwc4nkvlpe87Ef{a~b^WyP3T%C z{_7m~gIr8F<*9MI)I!sbjM8Pzqx<7FM?^6Jj3 z%~)+nOf(yV=HGLJPPDkwtog$DqE(o6Q)n~jhbF~Y9Ky&`D?Z=C8M5-y+jm!n-Z8*F zX#m*MZ-#=7#;71?+K}W+h%|%xgwxnB*Y{~PYPx?|C^b2;otUrwbe7VxVFmUjOnJom zte2sM@1<$r!spj6+wwZ0O@t?J=AvCWNl{k}0XE$iL^n!lZK1u;`SCcER7jUm?W$C& zU7TRRAV>Sr6*&TfF4A^&*w`-u@;yUKWy^QOLIofRO#15*AX)PooEzmXT;{sxKTSqW zr;s}qGGniK@J;sDf9kB%e$Z8D{<|DB`0aV$MD`=|9>so2R)Nf2J9>kn>}otb*etE( zMH&@bZ0BD5sn*o{N1VbnM*@AC;7KJ;zcwnSB%tfN1l@*A{|2>#)ctWeK z8pd&~l5-oUeN`WKWYi2@VGJsz>b~e}1R?uv!U$Os@>kON;3KH_`b?*|@hG54%D%em zNZ}rXDG%I&UYACaipzAi%4gOVD~vv>!FSDs*TWwbCWp&SCp0>h tOKm-@R0|L0?Wfh`4ei4fCNY45*>e2%Flc^)>3^2`KR4z7S0H_y`4@wlbF=^e literal 0 HcmV?d00001 diff --git a/docs/docs/assets/img/overall-view-black-background.png b/docs/docs/assets/img/overall-view-black-background.png new file mode 100755 index 0000000000000000000000000000000000000000..c125b1cf556482c57485a7444d8051f349cff78f GIT binary patch literal 163683 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%0D+0mjP)a4m%3id?>|$Qb$L}NXZ{9h z8BZ$-s^!5_SN+tqN_=0PjEs0; z=6wP3^&P8O574I4h{JI9&jIZm3=PeQm-p(j?r349?Hlv{M<|mqp}~_jF#CWhP;$ZL z_vft)uuYHw`YDSG@j?=R1uW$YL!*%+vu4vRgInS^EXb}gFPC7tXnlOK{_ywrKsy1q zXbHoUVWvNRa_)6?39r@G)(;_Nzy8&e;FIN;!-i>lmvDUIcT5p_&CBz5eSuYqw` z9uV}fFd!8N_hRZfiUMk`k1GjdSk+O}(6mBw^!$e1U0-TzyBq{Ua{$4M3rB|F%$`}& z^qvigik{dMfG$1Nf_-6nky1~(){BkBtn$^eCxjw`i)O#0o(q*{ssP-NXE(t zy~X(tb{=$(s%+U!GQ%`P-6?M=5YAn8i zg|HG77f~RFp|x%Il5BJZZ&k#Dqp6Ocyfqz(6-~oSHyXj*fZi$m4SImP;v|U78j&Hk+$vGk39&Hk^J zupw9yG$1>>WeskFygZn|SWRj(t2Bcm5k3y%hGJ4?7c5OET|U37L-d-=zze0mJx5`D z_rv$>Djy6irb7T^^*^=(R3!etLr`%?q6Eb!^`h5(Q1tY4VMbY8GiIclvql=@$lOqc zb-3o(gv$sO$ze_*O>@4uxEip_etWC)Q`0P0dkOY#?S8AYtJkP?+2Q~E$7cV^;tG{u zGEf<{9+|1l$nkg2SihDoDfvaBK1nIO22ON59b*mI>Q_VEHDwz?g7WD(a-%T3$6q(Q&GAe!AI|g+jK<8mLvV_u> z;FkT8tD5!O-1bfSe=kzRD?}Yw)ITJlZ(|H|)`Y__LlG#H(oU%Vm9KiCK3KWx-#0rBJ9^$Ku;oQBHyf?ZMCJ|HKk*&wsvGTl(YcOW(slKjVu3`%t`#M z_9GhxIdUCi8m*REO;pn765ymI#J`G~!%jn~Hqnn92HAhLWdYlRTQN*ktd+XBDs8b-AMqa1;j^&`=-ofBC5QKP@akd3elUnc<2R zTkGW^BGNt{9LQ3588G!2D~%WFDHy({M1&%Sp@$X(0K5@Fj{=ye#+6B%x@tyGebcuh+|Rab<&S0N9QHVRZbISN|kv zbr75Z8jdUpLBjHVlZk6)mQq$&Jy|Mzk$oj(Bv}4F+4&YEfDWGp16V}(Sz;0DVH=-5 zB46m8Znn3fga%}O1PtD5A!IFqfegjDQ4AM4f=t;cFLjyEv-g5ZK3_!ni3q#U9jMVI z|JQE$6!^EEus^oBYxH&a`K_V_Yv{~u(1@Mdy#s=M;=--m<-b&%QVeQ z`<4O~^YW+?W5A*3D*zY$$2EXzz6PrAfp=0Z=kaFxhI-z!5og@kn6@AU0NNEs)Q{0U zTR3M?KKAV?jy*GT%9Zo9VrZFQowv3EF)*!{6u@jnrC@7D!om|r6$uhW(=eiBT>u8+ zW`eiD6I=kKy$#Ymiq`gs9_3W>Rnr9%hhl%0A#&{s5fN1qmlBX+r?faininlW_z(Xc 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 literal 0 HcmV?d00001 diff --git a/docs/docs/assets/img/overall-view-black-background.svg b/docs/docs/assets/img/overall-view-black-background.svg new file mode 100755 index 0000000000..7b7934e932 --- /dev/null +++ b/docs/docs/assets/img/overall-view-black-background.svg @@ -0,0 +1,1619 @@ + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + CFG + InstructionsDef-Use ChainsVariables + Call Graph + + + + + + + + + Class hierarchy + Supergraph + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Manual Queries + + Automated Analysis + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/docs/assets/img/overall-view-white-background.png b/docs/docs/assets/img/overall-view-white-background.png new file mode 100755 index 0000000000000000000000000000000000000000..3b9e7e1ef3f41e3b4bd80318b7d45f7521971d50 GIT binary patch literal 208348 zcmeFZcT|)~*FB0lCKMS1AQ_RIbCRH!pVa$S{HUg57oRQGvpfZRgQKDoJ z$pVry+*6>=y!U=<-Sw??e}CP#XLRVMyZU*m>YTIB-n-_pqWld4d@_7IJUjx-&1=ed zc*k?`@D92kI|8p*cm>Jh;hlKpsjBI$Z0JVg;An4VX=6&`?BQTaW9n{chKJ|gll)T0 z^`h{pYrCZZS_hB+IKc6+#mVew}%axp=VU zkMu2`XW6SHi_;`x)xk!CM?58N`bdf2-=<2dKC0~D^L*gYldgQDnNDB5ccQr?n>!k$ zYXmz*JzgF&%l)gn<5J76!$|=Ya%3JFp zud}?pI$*o1#hZD)RC#JczrF5EoPw^P$32OjgDRQvD#OoiFKLXMike@!PpJ`Ip+1H2 zpH7K(C7VoHq|nfoC zwR>pgb&>lqxfjeZyDA5L{M9+wC=A42_V^MGo&BmL%fNihU%M4+6(E`P}W+zoZ%C9H%Vn@yMNZI%YwiTO{A^r(ImlbvVK9_f~Gl;M*O^)rTLpgL@Uy zY`e;rX;!ALa$2dDJ+*#te)mVl;8VkE(Y7vj;oKxnG9u=#LTu{Evf*{d}YBc&&W1SmC%Q^a06-z%22)W;P}eX$t$uocq76k9D- zN<+NDWZPVUz2k&avJY?n_*r;1OKu5&VNLYaA`X+jot90yTH-o(aNKF?*yX~_;ps?O z7WLasn9>qA64@8H7v9_$e?5rKJ3r*}UAD%PV;5x=WZWnrUDto!C z9{)4u@uwpfXE_+X0^iIK<9Gx6rVN8D;(x?S#7aubOi=658eYBhIJEw2X<*5xZ>bCLmS^$ zwOG7IE^S(5TwTP;y1P81t^d?OT!;h7S#}}^FA;aU=J~&GpSpi!^O9?No6;+#^F1vs z_nunkK4k3;JBI5^ZPfbT}o%gMlpWy!_9hGQ&QF?co z%ig*)YvPfc-AB^e3u_mS3vTFCSv%Z_F3V+o-oiS|yFg4TRd#bRI^tc%Z4&Ju;c&|$ z8YY+c2Sd2OeoR@AsHP8_MjWM`{hA#hXWMh{)T=Y)_nJK!7)&|*2fZR64(5;felfme zZDrfyGW~cvR=+{iE1?P(QZVoMq0K3xmuIHRMN@3zRDYksN2|~kfx8EEzjRU+9lPYH z)!kkY$mf5-;>u%Df#p@2izMu+Bg_dV7J=)up4@F=niGy{H%w9}(tNBnqRGeF#k4=o zX14acZA^c3V6m@B~<@>38ktn2+6D_@;=li7;X`;q0|LVEek@`OV%>>}OlW5gNp?|V`|0k#MPA~9 z6h#Z7|ArS@6pPfDE}-CQ!B(w*Ne`%n6fu~AFbu+gpK9i|Gs zLCDrJ+G65r|E@ykO3ULU!8|tY$5eTD9jJ!8be1RbsjMH1Z)txVU=sVT$zUZux$rTh zLf}W&X2#oEKK!v8nBd^4e%;_!{V##B){Qk6M9vLs-pXSNPv!Y?w5IJZl}M_mLp>uh zrR8tMTAiC`Rqr0}$++hvX?0KNmd}0sX4crxXa2aVKjHr3&H2aP{;9!ErnJi0v`1cJ zupLvC_2N^19#Fi0wVD1!5og9FH!ZtaylW?3T-&{#icd_pe5yxPDT$f?~_xMh-R6Wak6eVUhE9zslMjR@U>aEX?}<& zDuasAA753T`=M00xzs}csacvh;(2eST%I4@^9x@Btbpv7&gI_{o-FyURvoyN;)mSVT)|MVBdX@<>NKC&GuKg{=%fR-)m1Zhx6Ufy{= zg_cLMv;sCM%o>=U19A&7>ME&(#})bp+9^3G9d%UGylFBYc1iva{-gH&UBZB;g?y?_ zXRqVOBs>{oYWMAD|8&l4#XOr zq%a+&d52w+`nwn$o{Xs9Q#`emwT=ro-~QLd=c@ieRTnl4pP8QVV^2M0cCB7S=Aq|7 z_Os%Dd_UYi+%2_}yjh&>&TcJo`ck(ASy+GFN{kAowyV-R>XxHJx%-wqVeR5IgKsDP zcwCx6Aba=A0*_O-u@k7{yYBc^Fi z-V!~jYdv2r?SR4X9n6;Wig<2yUx=K`Ew@fIve=31@r>vP7dGCZ^c!w(AHF`Wx| z*+KG{Ky2ashvLGU?++?|P+qOl)?>k19~Gt~Wn2xdN>NW(yn26)bJi!%fuiq;)F4Ur zyTcQVs-?HS@nobBZXvG7(=&f| z)69G1!P)#jw7jyTh%o^h*ky;$QVIkY5*&GN6Sf(jUD*;F&8K{qf2mXNZD8==ciLCl zjt|bnGFsm7jxheL+9;YR8gMx(OQsZ4`g)v&t?1LmyEJV!jDqsYnR6I2pRwID>G%5E z3|U=&REUV0kfuLq!Y@1a@@vgz3a{jfgqGu3wjlRfL&7U3O#fc6d0XN^ccVp7(ZAi1 zG(7g!UuVUPb03M(WZ$%1tUmq@zp&FKl9g+K=W%!DYIs{)(#dONqo>MZce2;cF~?mC zbB}BhIJV3y&~#eH*QV;_+`-4f0|Yj=nGRpl{CbG&ObZ{o5a(c@@yFXnQ zb5eH;>3u2L#e3OOCj+147>%zh_l&EZXY4vaYxyMefxS=pTs~i0>}dDx+mh~Gsyn0e zM_f0=wg~#DCPZAbUee*=9k^#HDXEB&l>7zO0is9yJQurJbD5&npp7_Co#H#q*JrOp zwEnDiXlFV1>S{WXwhLG3(uqhuI%baGJJxUCKCR8T8(5HYD(4Q(+JUbV-;yS1>32Ap z3m?r7l~B*kip>rjxSIG*m6z*CTBK>x+1uPvIz;9Ar?37gcI=k4QHprDH2tt})$H)d zwB8eQkGM^zB^Hm61LDu!!zLeGuec&wX*n6a`uJiN9rNGgev*&P`|6CsnHxlEiQX5c^WS*{7HFsEb&9Q@sPV8hjhNZhbEPJZp>IFr`Ln3r45r>9^mVf)W_aJ9dQ(U*C*TwzUiWEADiChvu}1!>RQ_R;SMx;WA*K=KVOWuCod_nvtFQbC_QvibP{_( z7#o+O$b>g%z9c6cze_x!@8o4YGqQBxgQ7+~`Owgx=b4pP%IeA`V=A}3QaT0+g=HO| zg@|x^K~v>!3C)wHoUDkky)B2KiM^32hr6u7hQ`*W&NN1*=9YG1v~%Uv zv^16`Vze6ka$Ir_lBO1xH$5FqRXpWYjXkZ6g-vKB#PLPlMPLA1Q)fdOcUv1fClPls z+P!f_;AiwQCoRq1Tb!-MXf@>&X(a6(O=1iR~Y2y1^thoa8iXHlvCN%$==1$*z~%qsh#u1e-B||{QL6`E{-;Pw_{?=X=-C? z3vYFTZ{_}vue^bgQ~do2GzI3Cwhnurg311mcRE{|{WDqrac$@~d$;rN3xSXS9``@q z{p;TM-VE=OlM}gSZ|s6D4|7e77Ja{niM_F?Xn{rtEy&#-{8- z0>%RDW@fNRM%+d`M#9Gb8VY0QI}0!8t5p94;L^IW27s%}qHS?9BImfhJt!iXujgmWPAu zpH~!Z44uv317ftYmUb@g|NKJL($-YP*$~Ypw*bGefUqDxA2&ar5FfX|KW|bub##KA zh{ojR;^5`k`wdza5x5zcSVOc=VSv5Y;a)@}9Ze0L?HyI^?QO(p(IwHKPyYV39E6jJ zp|jyNLuXSMl#7Q)giA<-i(i$S7ycIE6=LOrSN`>WdlO4DkN>xKqu`+t-Cy#XmQL{f z9(ynCN0f@`?fqx_Pi-vsLWzcEFDOI|jrVWC$gZTNv7zn?m;Zxm|y~ zZuviL1tET6V`DxZSU-LNc0N8HK6WEvetvc%Q!{>UK3-Trf&H!UdvqszGiNtLN7F0j zFpn@-us!$YiiT-#p_u>kTHGv5(JH`%v2zKr|DG^D?w=FJ`SXT3QO@{v$D*A7k2w+D zd&9qy418|?HAr5N3psx$!=G~o*ZY6+@pCQyPksdr&40h-e>K1Vov!~**Z*n;{#S4Q z@9z5Vbp5Yp;D7bz|L(5;&!!9ipLWXB4p5LAG)uMtPcopAf2@j7k8~r(8tp3*xc=4z+M(+C2v15l%9A*|;wfX}Oj|LBO?TV^<&-Z>W-Imqe z-FdJ23)h6dy^y?qEW#p?EB~w+x7B&4NZk6ZnmV)1*LR;jC9NU3v?es;pB-8wV9eWj zlAhIP>;+v116?vxjPu&|aO-rJS7l?VVe$0nS4WR!UvYz$@#$sn%1b5AbMu8vEG#T% z;7a^^X*oF+9zT98Ehneu>RObcnodhjPTu|HOPW%mREBOTPvE`jGw08zna6ra#m2_s z6A*;nzkfLJAbfW3G0UpXb^v~In8t>LEYB1VPW8JDs_E)RcZ=ub z%=o8HsmsbfNk~ZGGk$;4WwQ1D53&X|P0cj>>TPvwdzFtDG0fopqc8keF1noJTZ2_T z3kwTay7i3>tBs{eM<*whjtsRR@%8m}X<1oSclVMoHmwA+=Eyt;CJkxnfK!wrbQr78 z35QSob8j-l*Wtl-2nKW*X>DukNL8S|E+u8vo~EQ-WD{XyW78nUc}+hR z+S-vkyu7Biw%MM`Q}wN_3lH9xIF1IuSG%k)n5yOK>$yyKswGQ@+D&-|)HO6TV6(Hc zt-5k-iwxA&!&+NgO)M>$FI~E%mZ?4y*7@pN1T7La!-o&gl9Q9CIx^jwe;8f8dWWvQqeE?Zs)M&QXKBfKbaXUbK1L)%tAHWy z(f%Cc;SI%_)}!5nCpiA*uS4(Wv+WLVbeqT34-Ogxl5lbq8^C;SZEugx&E=-0r76!? zT3SkN!s3U9ho@%i6kC0Gb|FI}H&(mQ;?)rXGK--%e{C#x8RSm)$VUskS{?K=nCUGJ zudJ-3prq`GzjDZBZa^iTuku8_#cj3-Y{!dUY9`uh3B#TtRc?1dJe z6O_km-@mt7`QB~y<@GBH3X1lU+r}=leOdvpI8V0H8~1(1GsJxU{8?&qeQ`X< z!)xWcWz!|U9q&XxL&I0WRAL#+(_M`%Eef5Tof!tzRp=tplrDDU8W_w~?^a>4*ap4K z;sH;Yot>SHjp)KO8Ez78>($IJLGExsD{vSr2!23;402rG?WeI`PI+Y3z|x z6lv0-ER*f&D%xev>DoA}WT_ysbdSZc{Jgx+t;sTIiN#6L?nRF`VvlVl;fa3~pT~j; zyH1fT)_G&elHaaxEv3$LDm}H(w2?Slt3b;5{S)SbL5PJESSJkjW3+H0j2lr%E$n!W zQpEWg9i^zdqD(kP2Q0Cvv2kJ@m6t4|eDtSRk`JHkc&*ODqJ(#sLVPUOGsaCMhwJm` zFaG$Uc;(7pm|Hee?dg%1+;X}bObqtD{PKkvz8GrcX!&oBA-oH%1f@YxfKfmbe3=Y50p6!-?@7A>XRL6?;n5d%s=&eL6(}9c2mrA zN%7{*M^#%3GLY9SzPvVsJHsEoCn3&jG`B%{Ud*O%fC8VhKgzG0p+?yl$ zrDbHw({FR=l_kL{+{8lUYuOF1t+`{cfq{WjUAYDs+puQ^gU6d95@o-6~L4R(=s0nw*qGxBR`kZDM!isRY?u z+~E_%Ox)Z`n=3PIPbGHJ3Jdwz4XSkKsy1_o*)(rOx4ky3QC_5islt968j6Q3oB8^+ z;@h`xMf`Ru^7HeZXZwVgXZw36{KlF{D;FAAQO0$2%>NiGmTB8tG`TiE0_CAY!@!3g zgHK4<70GYiG%(#=U>-CAiN>-sORJ~CqX111%E%B;N|3IPoIFp1i4?k>fZ@u~tbf)U zOnGVPTZn?yWNWgB&qg6WKECs40}+Z=$fnpN$eV*3a5roBe&_O4ig(s15ac|6ygeE; zqE%p)E*B*b$!i`A@tp`$plAPl1pA8d=+TFcn^dsdTRUdxva_&(z@T$kx$-Q~}BU zFETz08jOg?LQ<^PtT0BS#vd{OZ@Pn-wRPt0k@uG0K0M!8YEz)c7}o`d;&3kM(}Sf> zsdUUjLYf~xe%zSK%xy1sv+u~%K&c0kn&4Py(#lM)_59b`ss8d}*lL_Ijk~)B$s#UO zA6i;kj%BlJzK(CNlx|H|NqsUx?Ynjb;<*D-G77#dolNy?*&dtT7 z$p*VlC5Vi_mA6GSu{laG>fZAvW-b0kU7|cAsFXcLwo!EHaCc^#TU(1Q@8QA&p=gN} zY>SGCHGkyPBe)twE+~&J^jx+wj#74lx_l86@c1#aOB|Glo&xiyboI^6a!%tPL$kAO zBUV>F-=Jm|7Skz(29n?)!(m!0JQmx_T&8*ISV+gpoF}muoy(W~ zd3bo*6K@b+j8+fB4wqzQv4s@1r7DouIgK?b^_RIcQZR~$XhUJc(p4_C%8Wj!msXdx z1`w0Jf>rylvlzGgf^>*g!PmD6_V$a|Si3D5d3k{zSat2G=OW+q?1oTFDR5cllYx(~$~*Y1_|;*=|Qk9clOrt({SqG8uA zyou_?Y=8Ok&gLBJ#fy^IC0O0Yh6ZVDH}y zIshPpHYM3fU$d_@AD;Hzm}JDXet3Sdk&=;_xn3y8pj!Oj7)H{a0Peg*j*X429V#}I zmTQ%s%dJV6kV2~tmP&|6Y!c)=HOU7jm0-_5ae`@Hf-Q5C$KZNa(p~`ojDlItGpaS@ zCMS=w4i1fou>50MTHP@4@nde5ClH;NPY|<}%x>nYWro$))e`;v{wcM=Y*7p>$Hqb< zCp`v@4REd@CeMJNHbV0_T*Xv&>6g__YI8@SlPqEeGzgF0x_(BDO z-hCHbU0oyjEU|a)+&M=@mGkYJ2|XiYBY>g_m3(b&NL!7Ra}%Evv2?KE3wo`H9~M10 zMX5czl8Alr@}-6A0JfvQYIF9ImbSM3>h_NCWg-d+Hq2BH&boBm)YO#JN2k!@cC&|k ztf*y{B$QAY+*}V%rWKc2iP4ZWIdtSCLLcA8$5W4=Wk`b(lc85$gy|^g>(hlKBMaec z)s_;}eLDcqPD!6|K|uk|ciR(_e8`G^kkIc`(RTOqi#O;n;^N|hOVPjX4R4lszm>R@ z2H0w9pt3w~+pM0QlfLawE!d|G6pRuQgI~h(A-kAue(x{WXvIk?pi01ualC!|c}Pge zyyv_?JY1aO?#{OTMpau|8`$jzWqwxtrY6~uFS_Tmvs}1HSq{z-JE{lL2NLEaDwE+Qy6S?OpFZ9+{}#Z zRZdn`mSZkzKp^$MqO*i_%AnQV`XhK`cm7m*pQK<@x3X6_x){;Im%zR<6llX z%nwh7j+`SRn#?X9P?nXIEj_@Efs*?I(1Mw_sPERAvQI#bjKt1bVt0V0hlj@ofC8LZ zGua5gli_RZtL&knTYyWOzVmO-`R%rq+#aFBu&QS%Zf`8h_4F268dDto;}6|MDkn!r z$lCf-%U|)0Y90`%;H)|tC=(%PM~eHD(OCk>5xM>KTDtgnOI(+AZl#&3UPzP1Wk@a0 zopIPmzz?O1VJCwDK}F6ut$f##ixur&Tg$7XV(nlQ7ViF{tctxVDcKzCva{tO7r}EK z%Lnxvy64&?Fyb1FNm_8PwWdJI5RZ=-)e@R^^pkd%_L*G=`8;h>>I;lK)$ z3{82N`+Nd`QRlTEhS{2VR{^-Tb!O|(V|t5hdCQwk=dEwwzO7y2@aoLjv!O9D*{}&& zXlbvWWt3NiM6K^Wd>CW;FhAgs0mLx$Whm)7{}{SrYHp68DQfypo@}onl*ocEuQ^ss zM?rkuB>grt3{U(!{(gLx-+n+8Zk(+5k$$P;brKHUPoEQS;9O_4#a6x;n-y>L?Lmo} znvt?j7id4QNidAq`nQmc?*%Q0Xn6ga%V(jU5vo$Sk&zLCfEet?%1kCCuyC?eA)&F! zNsne>N(zc^v$GtSNFLJwn95K9-tXRBO-@Of+MKKI3Q-VGA8U>xA1^5@3nOFX=Kd@M z5IYN?R3f$C)@RLd_)Y}=?;as&1@PNm?dJ#ZRStLp9@25CBGI6L04yB=8DE;}vcT`{ z=XJPx6g{!KJTDN3Ep!}}&W0k|w=*y>5GiP{A8~<$lao_yXKkdX%BOt5dr_`(eJqM$ z1{yH@W5+&0VAF?EHlU7z9-|;Se-DyPE)>s+zm8HpqwDVNjZ!@lC1@Y3@4GfgWBhhUTHsQq;3Di(ujX=#_%MxI9LZ9;mt8h&@*?5I9Ma#v|-=~gHzdo9AMBSQMe&K0Y1EG9+4>nlC)$pqy%#Iwqs00pRHi{E5=i()zl( z8)9B7s^zXTsyHjPkQHdcEn#RZow@n=w7w2xJbF4h3uqSuo<0r7;c!rp6QGB$@9x%u z{Efbe4uHLU^2ciT`L6^mQ2p&^2CEIAQA9yB4;S2qR>Z^8a|&YOeaRS00yO+LvF$)p z2@45Ll+RVQ0ft&y>9r@fmVk)}q7YX0*;r!6>DM|sI`TP<$wKo|+{doKX;77A*I$;d zQ*1Z&>D8gQ7r!$A(-ef8A^!{V4#)oti=|I zT{hS$r6nayJUj~zIw!QDHagFLy*@fVo(Y|Lm(NpR?yMjWef|1%2}*z!ltn2WdHG-< z%z{?0dHi+d*ERcBUu4-Q0Vkwt%uLhuD?LlqdK$0*Tqwf(>{=m)0cEHHEU}*~1MbaU zWX-iaDJkiEZ+O*mr%o|{iF$>4QBjeg_W%$?L?k4P=}Tb=fV(#v@A&@u61>%eGGXXR zNiifMV0Q?qyc@6Js;^2(Jpc+u#moyhPzKqT@sMiP*VYin<<^Xg6Zh#6vTIE`ZZGv~ zAb;}9qk9#HiUdKCsn3aZVjkOo-Jp)QL3!%HS!*Ef30MlmZveVWEgtmM!J^jFXX*0| zLA97!`Nyz<=wKgY!1k&5O(Egg;q9k!sxTnl-n_YtQHB<1uHH)siW!|HT&C$tgoIyJ ztly4DcXxNcT9@y)52rE8up>&E_hMOA38KmF$gfBYMJVE&(16o>b7!#}UEQ0z^{KQCOq ze{l~C|MNJCy$$DP9`plxt(-kF`9MU)UKwLp*M z`~UugpYcCbyFdADfHR2O&8^z7N*9okk?F{=O@u_j#K@>Y(#9D&1@uNDkZ35XAeR_l z`9+%I?L63D_7XcpM?j6-+S-Et)39PE-kgzOWhR%3&?_UznpI0jgSUr6uc7_9WL+Vv zkm{8KgDe}GS)a$IrZS)uHuU$$)E@qIb!$=kUDHsRE})G#?}f*Jg@U@MmcA$LR;-ql zt$I(C)g2!w)!h11D0qHn_ji}B3gpiLpH)3nyRk`- zp(jSv<}5$(9&+$NLW+G&DBK zVB6yjeeU10SAtQBZMO~$A6=fP@oo6{@i}z2K&Q6no5~PV{rC_m*T?-ge@3C{#_&VxLhCLLOlwnd z>co{6c{h{TE0PAEiF^nwof>+YB2``-wEuQKkZnWi2@)1^xVO)Zd*sa$XhrdoY+yQy>UdiLJD zD!BhfX}IOj04nJ*VpP621qAq6O!T{4Ijn}isA87@&HkYIJ@%~e{$>E|g-w9eiD|tX zJ4+-|Y+n2ko8<402S|94>z{Y4{Cam}q!mT6%lcFboz0H5~+|MfArTVc(a^8zf2iW z%ZSj3vf2HQKHS^b?&;red9w`~96a+^{{H>@laY|T<3u8>^yvn^q=5-v2T!8i zarwCU$7m%eR*)wyz-nzQ4-o=m+)5e6DK?YPBeJUK*;A`*d+O9FGBM9WAn{Vd*mW{M zw#iUV#wYwutqf5~?Uf+^$3V=?bQj!c3}bJL74v%16`rdfuVlPE`=x?+NBD*ean1|6 z%C)b7BHnASA-tMaTwGi_9-Y4UZFu+1G&vgTjgxS;FWFcjO{u701vdH_qzDsJQ`iqC5HC#7 zflYViYM7WLbuY$5NAr2FIe?xB=Z*@M3e1{kEkcEUPf(x8-adIVsHvsaNoNU76q&ep z31F?!aW~voZJjRl9^4!vBNvPGB(H8UyZHTJPEOGUwFwAjNb|`XM8w3A_qxS5zw-hp z{qX%IsK*P-%b`(GQNUmvFZk#7?(g58A1}1hdpxUcPm%<<>~b4(+E+6irloa3>_!*0NEf}e1P5;IO(wDWP1DGDb~OG&wb!zpHR}!gQb@_ zcjk;ytGcdfGvrHX(^*{XKr08mEn~DXTn0-KDWEc+IKLc7LIplvOB1jDI&?GxnghoK>&&!YUVz)T zm7sGp0UQ?fo0pD1xDP&Njce69fsRgtT^Laqnw8&KWZP@~pcs~iAB4>9&B5I;Iw(F? zLqk=Omv#yG?S~!uyPQQglFX`06{GGHk>#>$7+Z^gNpu>(e*ZQ!9IZbh!1b^eeD1F>&=EDiK82xnK?P zM&OEI!h*PZVcJpUE}9#9d$*(5-T*uV4WB9&_uWG-U7dXWx;!^PoMN5FTaTO#iX8XnbK`tS2nJge@{*( z;adS4Yd$Ci>0E{qPf;l~>a$4+B)N4gL29(C#?z^@VBiIcV{_ zK-O!AmbcuB67_G0F1zuRssjlBLg*KHHi3~;eJZ-}NW}F!6A1|k#A=`*Em$Kapy}?Z z_N%Vi-SN8kNc}rd+Nv$FVyMvoUZq0yPYG658MxkObRcpl03W~8n{5Dc1#ANbK|w)~ zlYgYtJq`{Qc;^Rg3WzVl_8+7EBqIZnj1!}5Wt9PZRHgKa?P6kmRNzqsp{SB-1%)D z8k);3qKi%ZEta5^imweFqLlEBh8sYS5<*3fJ?SM-S0R)ikb$s>NWP|99_Xt;uCZLW zaOEf=Rr~uOa>O@A%}j%44fI|nESyz$elp-?sh0$J4uh9KMh4Y`4g;(+Vw4~T7yZn= z#6r99Pn=i=erE!5!UhZx`s`Ueu>Q!$f{X~Dza^kxR!)}gina!>AM&v(DAvf}13C~8 zK%ggKVEQ?Eqhli?(m_g_22B;|y6~3Kp&=T$dr(a?!N~->7&(9J+zEDcJ+gtLLP>|S zdMg1$P!LyTAllU+wnm4CmBxHhl9HH2MRl?55NX{#Jz>JY;-|c;saXcKe+stKr(~IM z;Ny5vpA4j^GSUD=1^M_?NjUXYL7GAc0VEPsVL>lb^Y*SlEDo@`z!s5?M+4yw{d7QZ za3|1gFP=T4fi?~HhBQ=Tq@XwU=WGLGgeolEB!F*q$UrU&-!L;Eg*+sr?ksg0M;s*q zrAYg?3aFdNHUgqJjEB4c04Cyz*|pPvX`A|zXPn+(HO7)#TUTe@m7|A{3~<2!I+H*o z%!DLlHpp4-HrEN&5jjraMT12#hp&GhM`U&1KB#xQv`^Bw9mvS)HvkO|V4oP$zF~uZ zhHn7Zjxdam{9pz?%c0;kxqt!VR(rIt(-Wuv8+ZY|%6>9@9r8mEu;#`Tp!1hqt?B~8 z$P)I(=t=~bRm@sq^~@$%012FBko^QSN+YHD+(4z2L;4qxZI-_Va>Cxw8;vCgE5^r| zOM|VIW22*!iBH8_UofjQ6;&^OycEf8{Gj`pE|{A(mZupp(l>81@m3H6ovfknQG2Xx zI|7GAYob&So9nT5=63hh&b{D1cP{BEccS@Ro^BM@10V# zx7utXA>RvR+)hZgAOkXmh(iHkT5j5fMvIw-CcdwfQ@4Z$>{|u4sKJGT2RNs>2yR## zIj?Y?&=4%l&p!_e3i>)S(nxTK7-S3xfvx}|Rz6S@mS=jyIr&f!`g-$V<5Uwj0zwNq~kdy7Ntw zSq$}nGzRTR!@=QoMMcF@18Z&?fKo7=rNHif5f+vLqJaf4ugPOoAi7BfQ=rZeC|zq1 z4NyJ+vGf(xVNixxK`sX+Y?yt}96D^Y63i+o7GOeHSXxqr%RoFgzzl21V4l<2#owUU zzaaUbubtwO+iW|mG|QwFlS9)>ZYXJ`EE+jkkTWbH2DPiaODifXabBx-%bnU6F8r!!D&S0K6F4WM_0?dC6+%uAuJ+-8B!Wbpg>`$$B4M7rYqBfMs)>DOrD!F z#UPbtAvdMO){nc$Jv1x!$Kk`1)VpgB^}Xk>i!HaSAlDf{IphaRY=O-Q^q&M!=ol=@ zqbNBbpBdaC^5=lo)EvQkW7O6mAM8;<)Drr+)jMUe?q821h6d<508S2@QdxdjjwtUXcqiMQZfDe`i zrXvjSdp9>qc|&&zrLiDGkRTsZk{BDCENrz z(d!Xh17Opa1aaWkb(c^)4cejRI0*wVYjF@Qm4m?&GFuWUui1y%+UuLEbDU~w-{W?- zQXzbqFo=eQQq-9Bk^#f05UUE1L$%b=v|t8Ivt%O9Y0$zIdapaKZ*GQ9sVtC6w<2T@ zHif8dEf&z(APB%s754Dq!_HiTT-cKlF`M7J3xKua)oR4{K9FvOZW0-z!Zt`9>ap~Z zA}$$8nZ1#K?!P3pyt{v5xy5fs1u5pBJ~m`2mq7P{Rv8o-sB)}so2qEF@ManISyPAc z(Xlb4v;lRN0iYA<_ZLeLs|5f8wLwTJ_FVanahRD~q2`s5K|u~8MLW0>aelkLuwA9A zw-y4?t%K7q(H@|J*~jQ~@fNGP%O8qF2*U%-mMYO*om4dsM|hMH0ogMjdE%oYMX z;w?XB;l~fIjN!;$;-#(iMKHhBH)cN3NtPxG0lsBV8s>BVF~oD%8Vk}$M-+a}b~j`w z>BC(Cb<~1((R9o_JjyV^f}*a_%7OQ&1F2_`eCet{F`>|e4fz3Vst{86$B#3+HG$D4 zoLn~qDJy7Du({n*<%k0IX<=~@tPB8gA*0naY+dw+ZH5e2*d-`|VWhet2s?)29|%J# zNhW|jGruEjTJ-D;3@NH{Uv1@!ZMsn{4_y~l+Zu@BF~xt2$a-*R?h_ymM^Ssp#B>wN zGtc_{G-q5x4=VqU9vy?OGI-{S;p_Z-?A^O}Pn$vL51u0-Nd-uNsCV!jaBn=gch3Tl zrRUo4!;V5r6{yIK#|mJ?$40$){^fF@Zrt7)x6<|Fhl8S+R;UTmhu;O}@qVpY=Loc1`%paNtrr6#H-VNrHkx(UH%6SHJp?eu=w=m7+Wml z4OK6I==?w$U|ONmX%0S3MP>KLV3Qi<9;_R(GyCZt(m+H7wRjfLSjMbLM&JWSyBbpH z@yy{elqtaliUb1C8J0nyXW8$H!CKw{d*F$I_#Ch&;3DMyI0O1D_z2@spAMl3%9;sK zy?*PfI3xPIHbk%D<1f$8&x1;SPd-jO8-Q#?VO?FFOquopRz}9UB(fZEj@66Ba09K!D~tK4nnh{#mFBazW_`vLZJXfnx+2 zbXz+b%!@ak>{Jk0U;rz&qGl2{2V{o=)pC$bkYdbPwRRQCO$YQlx9(jCJp9KWH?d$= z$bt$xe|?Xaa8UT!&@h0r94$FmJL91eh7>(M%h=G>r9nkSWixVDXJwqL7c^bu6~5S1 zWKw^|)WqawS=lHw-{nu709a>1gV+GE1B-C48xM~t#{`#StyWhS1Au|w{3FH17tZV& zIlA2IU|e{=0gf-=p;Mnd`!g}}WqEly*nl#SQV3yWmRS$L-D+{H8QJ`0s|39~J<)|g z$qdQNv`(LqvVQ>oEj28|cD%Q9E}zE^zlN36UG*?DG(>&Bm)_{%a%r&FB6rAAr;fdz z#x6hr$6jl&l$z<(59xrMXty2#1vI&D3LLz+CRf`3A#E|{_d%)bCepB_Gp*QbPf;o{=rJ9qEmz;g`=Hp8kT!_E04nDwT?GY$Ia z3n+j5VADpeEPPA@0y#*r18ALZ)6+U*LP&~7#&W<%(?E=L0WAo=qx26SZelc8W7+k} zRFRJjM?VT}h6~srwm}6)PEnxH%(g=4p#4KuGAJcoS>M!S7mD6hsL``#0|pxWBhYk^)KX2hJ3SzU^!iS5{Dxj`88G6KmcsOj*0J6Y-MMOOXkhX-7 z{ilLIu7mR%PNYafXP<_A+JIfUY^uN7N;bAZS`^#eT14C{3UUC0OwbY8S1yMkODcSi z49YVBn zlnmMwfui?)05Sk5dEFRiUFY>ySMSuzTS3eKoh3BEkRM(`*Ax#1P2`2dB8d9Kk8r;X zK*>fwLyfEg`LLA>TqCkhIm~5aPjFxv*2Jm5H4tnFs%|h^MoPUjLk8y~$ zup<#ef=z;AhQY#pgIkIo16zZ{Kod;|-YVTbf5lI?UmgKsyZ{WO)PEVvAQzcr;Jbbu zk^q^Y9Yycmud#}WQUs8!0NOx0oCJAW1**Q(Q!3k5H^Az!xJja-qNKCP+a7~sQ)#AgYP9m=k1mZ{dHGZ5E;)cg zM7=}MQ-GjZfB*frb!>QCTrPqVutB@TG$6Gh6CDOQ#v-;|=LWKYvI+0jS5>(1fAC&e2uo{U8%92(pXs5 z8%WTplj43(70%8gc?b6%erbLNjSalq>GDflkht75g@Q z_@D^*6WJ4%=LUBlyiKk&d)?AA)o;OwlW0C9zZRF72ZH%dnp8o&@hj%bJJlG&FVgU`0&Z8MT`-H z!j{T&;sP*TEYQq>9}t}+n%}ku@S9(_wd*Gs4G{qCtqk^kFeNJ>Tv!XlndRl>dCpa? z(qT9_I5xVfcj!-?2rOTT0P~O}a2)Ee=D=1UBYy+*o={ED$qU%x&Tx2Trms{5kmGZ@ z`}gkkwMJKZt*QgRhb;JDw7YrnMzAU{CCH40I5fbENR1L-|8^F(Y=78hA=u%7i_Ze8 zf?dBt6Ao5=M*7Wotd~6CgU(P^jdQ@uqDBfl@DTMx`8)6p0_*Mz-|C^~B{PskYl*kY zwwL?XbfBUsLsyM(gT-fB;Am6QB_ypDn5s|GfU<1?KC7lDy3lD{UM@~t4;{8j&tb(trD#JeFpL~7ECpf@pX+x` zgjj*p>wuCivm5~yaed!)Q(!xzDu74KIek&e0#PCbW?z z0yb$7YmMN1gFYGY%;4^oUiJc?(lS^tk$zIl7YEr53H-?1j(DBU-TNf^CD}R;h%m1fzA*}KLqRwkCHCUbNx4ieBj3+svDvF zLmy$*TY+>#0uFRxfTV$Q?Xww_c!&K#li3R2@U>sG1+!Y3W1r$3*OvAR{_kIuE7o!7p^K)-r z^JWF4rTuZ|LD+qx64QuI%wow4adtCG9N$G5cGiMIzAjk>j!@H>;2xC?O;1q{lR$Nj zWXniamX`@MA4J~Z)ijCvt+`9wZa?F6V7tPA(;#=aEH`30cDiit^nBwS3u{QTSqL56 zk!nV)t2Pio$aV#Y$~f}_97zG!!w_L{b`uyamvlqwlb{8L=KgiVUlqG11>LY;7~sV> zZ{7@Umv5|&HP{trmXv&Xxha7k86FmfxMuUIErU6|rj zM6(%G-?%O>(MqC8@MpqV)eNwdA!{0PJl2~iU%!4IJbQF88Gnug-95QJUQkuND&E%G z>I}?pw~YY692SrTz|DfKH4;wrQ+p3J=SABj0tR5y+4

jm{=6j<5s=>d+f!6`W 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˳V|Bti_|BPy%xoVvV| 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�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

cVag%FsaIlT z1Gv9w+vj$)Uk$pBk?WYvGDs>zLuWH9(9amB7)XogGMijTKkEkW8Sod&6S+$H!~ZLUm# z$bQ;672;E6Ka}-qw|%kv43|ri;WP%e1(ZoIcIU%Q{7wsrgXXXwJb0kJW<89w1Y_^6 z@-wC#PfZ!AU&ST}P$SYZ8J#TxT88xfR?yy+?}NkP{X;l$5_f{~_enkUBvs?~Oei5c zD+UVV2=6SrG;N#4%H#&qHu;KjiqM)rX_Ori_hD)tbKj;mu0LwJ4)|H?OoJ2RKGtPs4Iia%O<2#)i|=VM zu^txD9D+37%xQtq<%#Jrhq0!rz7=ShsciAvht-M@+W8#3`s%KEO+X(T{Z~(6Tvzes zvN&4<-UI_pY>M6PxcJJU_P8nA{|6osTPd~v`*4WkpB19`D-T6I#(fOWI~l=PP*{9G z@fX`GO=X!4^(~||nBj2)17rIrAMyvDfMJ85*UEQAurqvrT=B@!%h$AWK+-t=xZLD6 z9+LHrn!@Z7N1rzsO|s{?OjO})`JF~BRkeuI!NMsM1E?zRagz*or@1{j5Z8HTsCUba zVMj%?EGAlPBIP7nff3qcvj*e>svJx6kIG1(#|$}cF1WP=^iTa>J8`MdOQ&RHw9AuC znSsf_)XB_5chC68qM5gC$Cnc!Y&XM*&TzUztILSRL(>77V{_N))zI>fTo8aUR>uRc zlg0gm*y^Xl@yV+9M2C+r6%yCt8JQTD4%vH-9d*XG=muTEh)#&YVI6Qi+cNyCa(^N; z;rsNO%tMCG^2Y%)Z9w}suNwMvO>CMT^_Ht$_{4uB>bebJvUs#yKPFb zO_`xn59R)Q=I7;F{p0%JZ^Lrm1bPXCS|!kcnQE_KLL?QV&Y8~ z1873YYRw!FkLnv_J09W@fk@|oNJFh(QvCgMe96~6b8qFjXAq`eshg8f2PN!=$b6?F zCqhg61o8mK0^A^z-skTKjyt31MI7nP0}c%hF6n7HK4mDzF}9=d(_u@>^~=GJQ8h0^ zSm%;5(L?2yR3KQghsB3@xnFGNMSJwvB?kT@bO&(pWMq79l6XSwWln_a?2p;K{9*J~ ziZtoUQiu+n_brGz5j@o7uV>U!hXom$-8NQltAyM(RLvEk1CWgtBVd`|n6|#maGjuVz{L z^zDDUi-0myarNJ;Ny$>!Z}gQ!dc0_MDc%5%7a2laSQ_BF#EcIS{#!MXB2FW->DWka z2wDOiD93_h!q}mes7%c?jxk0si)fXk^H=!ur9+JYXr!@~B@lp~wq-Y8`ltlFHM%8Z zVcl`^xKc_uhucF085jQYDoyG9(dwCKqLC06+2 z+)nrDKKj@Pb36p^Lbl_aEq1_$E26>03r`_(<73_%=@W@o@ia6@t8i8rHzr$l z=`3u}synsWkRYWDZ}N2q>YDsumtu{xikbe2Yvia-H^voBp?OGcK2NVBq0c*MavIUA zc!`Vzg@IGs-1#4&NL&-v@8(qe!ZZQbzeBy6;-dQ~g;=LpWdvpq%|l09(?s#I&zr3bVn4b2J>ck ztMf(YgIR<660mPvSRjC%9Ie~D+CAN_7k4DoH*qJb+p1NnHqiwHQ1oP)j#Ouu=UX5L zg6%r|%0KytaQp)@+`v`805{VXcnr8(0|adm7z`qg!}iZNS{9dOn^zyhg(NU5x%=1e z2pESXX)3X6MpnDy%>VS{fc)$Fcj$=F!*qqAF6Yykh?)1Md}eBh1ge7rFVEbsFOMTP z84+FLs2T!PO~PM2gSQ04Pk4rm@CGj9YhMxP&faO+L(x;z&X8XxSG!Q zdtIJYPD(D?3RIU1rim818bDp;dGJ(|Y9PO+#gDOgd#tq)9am*7@a3bn5ARr;W}b zeH^Yjx@;&wg`|^ua$FJH1)Iw&ehHOeqv2y=9&Tl-q{TlZ9(F11tct<%Yd92lBO`aE z4$2cVT+Bwfh-Hsr#O<%M6O2>HvSm^g3ubuYUPTg;hEI-a;phG=4~5Q_3?a5$SB@$O zreuDyXmMo=@>;JaTM$$eG^Fn&IOF)`lHz42LD;l*IMv+m5TPp-!HUa( z`H)xp=#&`6kt#9EH#OS1@2kmIUyav2f;G2qd{r%_&dtk!nPU{-9-0m+7feKT#x!CA zLw}z2o=|`A%h>bhG8!Ze2^^ADF9uokg~14fR^TE7^C4)9#y$e96iy#vTY?5_}nBchmq`_~Qepx_y&)9Mjh~M7l7Ue34`(3p-O8@PTNNvVKm}>!X@BNRnh}dnW0Ribh>xlnI|3Fo za5wx1vGskFiN6Wvv1feu72?;1Khi}=_kceBheN$huPyg8-vpV<_}R~Cg3$z{SVq~Q zpV7)2COXV7hH?NBX~V+e_XS}>s#!LzUVpe;aIgteTscU81ICkYk7q95R=HT6rNje9 zuJ8q4teBVNfwDh+=t{iC*dFeEfHsF1UszuTs z8~9N(;>*y|(C87IP2+p@VvjLZb7$H3=WJ8Vwpx4`NAkIudQNPS)<5$zS^jSykoT;M zzdq_;I5ma052``kjkw|Hqh$47v@ub}{hq$!w&svjkFML-i|PA*UqMrqO?CGNMwqJK z;L@A^L=UrFqZRr$+c>sgpjPt7lQ0cl{nC1CU~)x}(xsq`7?r@(z_nrXGEXzJSl?@5 z^whQ{l&J8y!|3%p?m&IIJspq&1WC}9c&M7|oOlnwf|u57We0rcg)ZsxyvNTq2AeJ}RX zjBB=Uc30Lj_XJyIE&%u=#_jR)>+x+Ox^mr`=pPwfX^&ftsZ-3pm~|RHT?*8(e)BzB zYz@r+h0!8;^?R8JPj6HM_Cv$QR|Gd(L2|n`Vx;6a2mZ~pt5_#$sW;{Rb>v^?$@lA? z9Qcg68B2sW<}A_!7RKclChKL8jll3GX^i6RcB>}4Gjj7 z$Ag9sCHH)2mLf^p7PJKwI$3Rqcu{G(&3mJ{Xt}vP`ThDUF~7K4Qm=Cf+6Gekr1?k2 z3O(PRn^fKZk~8Ck?-A708^Wh$sZTb1=}TADplmO?){`=GI<9@N9G*2e_V@EjKkopI zRlQos5VF;1_^yw@prnDm{K2aS+t+xtxItogz37HFrZKMA@E`wkc_TId5lC9*2g6)% z`9%uTnYfYYo1Sjxr;|Q88eeCNedSIJ%1YAnI^4_1vv|cmc)xh+D1nRe$(hege|EL6 z8D5&`>V*w3+F1n-T)%u!F;5>--e*Cn_+xLA`=p-JNhE4f7=(1}UoZ|!V!TY6MwUh- zdMHbl>4K-Ez-}_(lZ8z$wrniWu-JhGxL}dYlObAVYV#dTS7B&)c2XW41zo9{63_1+ zJab0LKo>@ao(u}M3IBeX=9@mWg*DwNQU>%h%L^JZ)|1skuP*z)14P^%;z8~v%!^zNSM&_a%$tdk zuu+)$9sM~Ot4WaQGq&e{_v^1!hZ~vqeDc%9 zdafSM3#E!_@0>0=&TeJbv#mYR(?Te!3J8?zURh)4<1DQ@^O?l#k^7nA@qSTxcb2#} zgImQb@k49pSQ2uwK8P5rcmA})L^6%NjJss@oYmCTV-VJQ0hu1nhKOsnn45(-p9GZI z8qeM6VE_H;EcWk`?JZhv_+DI<%kjdsE2DP4x>4nL8q-T4xkawQr(ISV%)%icl8yH9 z_rB4YXI87f6sS|#mIo5vISnJ-33wrV$+5Aqp3$R>xk>DEuiJ#>T2geElA3~1ZBsZz#AZN(HX6>gS?|roIM)iu7-3p@+`o$rJ!*N;%&De+R6bLuzSF6havX1~eDJ@B zfRh9QQBzpVf_OB*wk*+;s;C+z=pr74xHgm#(?17-6}tD(^KG>hISqob`2P?I+>(lyFt>g9S{ zo%?!VT2q4fPMxT#=~FN>1XEz6LBCo}uX_$9rB2*h^iK9;lP;?ZJ87&}8yq;l0i3P+ zyn4m;ZEsiuNf-dRx;dU>{^(yP4df`mMD9@~%CvC|@L>&Ed7I_hTwYNL32DpJ zSBLQ`1R|2Rxdld4BfRbTxtozs#$9o_@s~I#zw;qhe3D#;-n}_FzZB7S{RjrvA9lVd8%z?uHRm7$*hf0pze-k@c^fgU)Nfg2{vW#j_@ z{_rp`C}b^&1JwbDES$n5PfSuS^Vd~i=LY;0muX<`f#)@IgFm+$4fT~ZS*#T#SsmU) zcq)Z+hfhDA0g8y3sF)|-{>M{3F}zsF2*S~$TWeN>2HnwJSlL93h1i31zL^K<`W@+D z7jdoD>kWwe2+M&a=9_ObRkCyh-q+9KdS$StR* z=Y>dP-8R;xDCCFqLl^G@Isty;U5meJ$k}fqH5nQja#p8!iT+txgd%{9!Aunc@R!T` zI?So{haP^&a7A{Hofa1R$!gz-)DIs%cy3RN6^qI(i0~15L4elHEGKN-Z`N@*pd`C9 zxXYt3FuOB*l=4*Ch|55LpT&b2671t*_Km zN#v~iQ1s1Mzl%e@$(wa=u3>37uV2FtYMMj^woV+E7(OF4PLr@n*A956WOTLgj_G4_ zK`grp7w6LdZfG5Ye^^KK$`D8c=Gvc$C@|-2GzhS<)ywrMd*15kGTiSN*|J}c)Z~<% zn+64R%#CaakY*K(22$>&)dURcUhK$EW@oFViDpxghZAPO60qJg>j-}?P`br zi=RN|r<;6b?X6%zQ`_*0es~>oA!MP)8LHAV4{+LNH>l4p$Xv9`Z3@?GCZA8L(jBZW z`P+<>&Eqhp4yuyhV}reZMOToYz_s8Mq$HKWQ2jorOF$=0;#^%$!}I1MFK6{z zNpx7fuOaLI`P1)TISTpp4+fkpW9{W1J`o+VaJElLE`% zo<5pi|CuDU-v9Nu7Ls|h*RA{uBwF&-BRS%^g}BrXUazy#M%I-)J33T3&q97DUZGWfHbmQD@?T@eAg#= z^(cj7!Mfa}#2mg}{#oVW#1f>IkrrK&_DzU+P$Ig0D_rF`|2qYmaMs>a)}LvudCxc( z_kpnRD0;E&p)4(v;|AhYC~JZs?iTbo^&X;*##Gs4G9{H1(9J`aFnu>GM?pM|^|W`t z!XhPlG8|Pi2d*T!7d*&$%83C{hvZCC*QAab&rTZU*UVl~z3UxxtnaUI%#?Em>O&7a zzuwg@HM1z1giiesUEV)?p+g*WYY?ACtWti#JS`WkZ--5njb^+(l=fbUGNJuhgid?@ zP+bD4y8GX^%Z9UZI=4=AG_zW(#OUG_2h4jvYJ>!Yl&<*3b8Xd*2>6;%*UJbtk^T?L zT>lR)*!nbnj9+Vic~u#0KMk2qK*QAnWX(5+y`x~RF;DxT+1Yg`k>cohn#AAWfO|SH zy~%L4c!yMcF)RG22~b~4>-{jEe~!g|0f~_fZq0y!IM4k#5UY9UabU8dGJJQHde2>u zGI4D2Gfd#mSkr!pQ`nxwtgq(-7k^7e3Ckl)UYuGQnq?B@$~@34iu9r4`{_!a4g>0W zvla$Es~bIux1-JmX@Yc>-5oRRxUPr0Mpuj3O;I$>BrDt7@L}A=kO%Y{o-@yp!TfxD zMe_M)ryx?5aKfQXo`rx-KImMGB0<$9vNm@zsy0wcJ$j-6F-$n`xG&-Iju4#4F+Uel z5bu3U77cn>OSf1x+-^ zO)%E9n?ND$H*tpVZ8GTe)!esdlOFYDqOp5N{anlay*!I=QKE5|zTS5XKinm|ryFez z&qGI5((c*oT%H5Zo|v)5mzkZE%5L`d*|TSktE_J|vdSka@6I|$^yB;LpZM!IYw zwCl3HC+-TRat!PfQkF$+pQCepK7M<3N~yj!lgL*p5w_i!_859WdsELz?JhPj|EcgIW<_WaGe2_Uk|?B7H(;eJwqtSBC!%U(NXkoYO!XOf!b zsivAeW0p-jR@7Jkp2xGvSLk2g(09{eM>LbtC(55S&XYRX0>U$VNJ+#UmeS9O8*b<3 zPvc_%=9BucfCJX9=~G@(6Ps?4He7|@T0e^KV_(rg^X-GJW zwJ37wJP_sWs}=SwC9J_67-Hg=;Qsqk%MdzgSkJnqXv}(ozY9Y_=NnX3`tg?DnGZSj zdpD=Hz&4f5*d6{`$So268>jN%Cn4g~6Hrx+>`fJn()%q;r2CjW5viHazRTFmO|c44 zO##SB;{c#4_7F6Xe!85iyKiCPQ(AN6i~^*Zn<_*@h z&3FvIbD#8tXGdwrMAB1Rcr;%dOb{RHInHp-%*!;dx0$+@DRNN$(4WRdJr3t5^}s`h-(Z2V3 zo>{On>Pz9ojpI&{Wk#w0)R4<-ri?z78iuh{(LwRUO>Y;f=tMVxp%ilxp)eu zF=pV&?QN1P1*@xz6SafJZNX=Qp8nRV`RPyV@yda)v+fqR%+IL08^*%g@NWI-$gLZw z3{cD!rp88My4WeiZ+ZOzE**j#0aavIC`H`6=D3Hp&D0QgsI>plMU1bB#y+5#jtE+V z{yzbsVIuQd5zBSE{2pV%$Ty~qDr%Ke0$%f}S+5zXmB&j(SG5deq%MBBN6I@)jCNIgysjiXZ&}>Q14 z$LR;q#Jy01T4}=~;`Z+P-NWI5T7!ddmxy%+ zy-nWscCK+B}h@HSF3em@&-_hH|;@8?pH;u7vivd^E2{fey54V4I32T2+v%J6ip>40tB z!1GNWjC2?qxzCe(e5^_d@Hs0SKjALzVUg{FY*Sjl`0F4bc8D_*bABSb?GrrLEQGvO zB+5%G#aKndOMQIlW+FEXIeV0C&_p}9&1-YStb&8peh$+ z;d8*4&&bip_TdAGzqgd@5O11rvBJC61mB?;w?caO?LO0TM8mAEKu&p zCP6dj!6Wa7ZtT2+5=6eZU?8N}-QFF9-$&MCmpzo`kNGZSdFA*4$t82(-5jC(p8+d& z9&CfSUa@`9JOeWMg%$bp&L0&_4*$QdVhL3oI`(cZ3&n6nsG=k~zQ4PBF5EJ~?b0hO zW^xpG$#uLJ68aH#inC=QNnZGVQoiqx>plGF-XtiK)dpsFNE7i7YULI;y3~?D z(_)nOv(K0;Ar7KUnJdI9F*UD_w5(=f#!eAtgo6pLB81z7`9yI_1u%D%5ZLC z@p5Dem-yVrL20uDHv@?vfV1DYod#;WQzja{reIeljl8WKxG9`G@o-MY3jp0BQPt{< z5foO9Fcda8y0}M8{p}JZzdSEs)i?KIx|t=f)NIz+zrJ{!*ojHKP)~vFZXBO5K?oE4Qc;{{WJC@qRxK#6Q19uhhnIVUY zac-K?D@^q@^`0N(Yv^G>Of;!eZUr)flK&flFFDQXCnh$pn9N|pD*;kgt*;@fgHG?I zRV;`LzY4nr?A$>xAXf(d+Y0h>{^N}hy9g)nYI4|_+$V$WDcgOUaP|K@8HI2gon0TR z^-?tms(zzTCF$YeAsU&qMlE3G6eD33EWxpu5vVH&~+8c_S`@{Et~!75+53 zU-)z19>(~UmiutGa=j>c6Q37Lv^=;0;ypBKM&aKYP2+5!S%HBS@%c`)N(Dq=^%wOj zC+8b$)OLb!Qt@q@q$w`BtpTsi_P(vY58wWlbHTRvPpUQLBuaC`ggS=te1R`75cm_U zIL5BND9MPCWWW`0`_1B$TLG!Xrz5Uq@OkS^T{_<$o9n$JRTc=H7=`MYwbBds7}W*& zb#Tk5p1)_E8n_29fLPnTv^hcewEj`kDd&xLe@LY7WyQS3xfWPGqHXHNvqp7$ZHG~S zomvIzd+!%nwIk1^yNi5Tn{I9qu}QF`)@A#6r4!Rg;X*AvdU-iT)t5wnmlDG&c09tB z?d5zRnZ(@BUkxEGxMI=HTxsd)#XU}t@Ci11T(|isJMA*F*7talMWb9&dtEO1cFtII z^GyY>O&_@bsSJ#*?rX!{Bua8E*|dvg00{%MXD03vY$WH=(nt0bhs?i!i+P;V*p+)M zJ-~<+3M;AnmdDE8_*u)hd}KaUe?8>1I;&S_k55H$!GF>Y!RL-yD&PH5oN#Nu^`VJP zRpP|P<}&o-V zfQz~}?zZ#1x$h261&Q9%^9u?r9^I9boV7GT-oKElrJ-l*J2W;qso*}P2_^a%44qAA2K=?KCD=Em5Sj@+-o!IOlu~US^N7q>Ih)*ZyH%QN2G`f5ruf&#UE%OiC={Ce zqs8d_jnMLHUthn8pP`T9s|Vqr6i9V^gEselU9k`mSp;(nYAdr>c@E!9M=X&=Px2CW zyf37{Ec6a{_IN9_OdBdH@{aeDTTgcbFb>DZXWlVi8+Ga$*(fPCxZ-)1E1V$oT zOKq-p$u(!=z^twf^n$lKm~2a?w0{a{e{XN5Xttm19&2#Jta7dXBD_m`nw?5*=ENqj zxV#*g7F3@k@#~__m5Q7?R^21}?SAjJ z9p*Rp+Zn4D=hE&E@tCZx28{ft&4ccthgmn_TOWKLEx)HynRK77|7!yG5?t=@?fr7O za(<=uNh(mvG;MbGH&|Af)uR`FElp{Ntr*HtE(>*IH(iuj$E8}8Kxwp*gaA8@TKn>+ z=H^+ft;%f7t;S3F)hB_^Ly1~U9Otk!i|$-*+dkC8B*UQ#^M9u8-qKK+%-raAzdG4k zL?Br&{X;cv@jLfz%c^-SSYkp;09WkLkJl3#f^$cMC$rk`3tK(~m79?q`<_>yu6>I{ z{Oe!IA0#@MX1wB9hXsh?EXTcl;<&Lz@WxlOuF zy=x;S6MB0S13GtIjZG?X++1KCZ61>V)9Pcte%+QMY}<#Vc8B3x^>yOR*vej2Cw(|L zb^D_6ZM7MtkK1 zT-Klr{}t(mY1`3ZYxt}k{Yz7Kp6JLqEI9)w!>`tuK9h#!&ow=5`>#H9W>xc<^dAH? z-l>r zxYXaj*8=0VB}?e60{6}g{a>FZ9w)X`o{o&OQctCL=zqi2XMo=(DBUq86tCVHdSZg) z`#5W4`L(<0WrfZQQ>xgCgsv-5B2XC@}yfU0Frl-H(W*(il>iz3BG2?~x^q-M& zRW}zmUS_LUt|l&Rcdjy0?xx(oPhU8<{%!C-RvQ@?Z*hG0r$|3?^C=UfRYW9^_I{c0 z_1Zxo$M3TQO?Jm#c`|}yVCH`F2;fe-7**?P)>I!w=7k+bRBLMQKdI_3x)BaYY4Ebf zOSYs%oCjd(7h34hzdhf{yDhadtG2kT7}U6yYW^8E)XtOmVHI_1}LWB!QjfgL_ zvG1Z67BM*rXkr237?*A>yndN*iXSsaw=(H2C-Tn@nRZ9gvMSEE{4p&`r0MicfuYxD zy8CwxX0VTg3M@l5_jt=}Y;4-|Tvn#5udQbGw>3MVvzoIyP9PO~rnRL^rXT!!ilZBXgRh0*c;ID}{ZzFqeZ8vPP(DIs$PI*e*ad?(_|9-{z1?U_J) zXF|JJVPT>8?3XobTib}1X~yQI>K>E4-~}r2C?LtwHRF^0&@9sO71In$9T=gpSoO(* zj6E5VNjK1Mg~bUm#R*Xb7rEbCgSJ)P#jyYku$$+CwNM<@n;qT@vZ;8THr<%tif{dI5zmH_fwFS`q=KAgO( zDPz^gCie-PUbDT*utJj>XI#g|D?co>=^)q3KSut~iuXaRId(@P+uAwJ3QjSLsI!5# zqvM)hGr-7u?-ry#=rgLL8>FWTG`5NTuyB4wZ*s!_?ft?-+%wG7`KyG8M*J;|M*i}E zRQ6YTOwIdJ5?AK>5QfT(*QLH3+wWz^#7=(8)|I;TmY*He+ zkOxw?)Phxm(}JgnoUf>Mhy^a_MpH~ijD&&If}b6?D^tSD@!i$sdFQv#hgBaLAdxo*Bw9ELE&OvOf$zyNi*Loi zLrjD$Vhk8K&0q*T?C`{o$K<6Mb4?Vo&EGzk4_Te?e=AvDzQ@?{zDKU*+xn(^i-;Q! zdV!MkLC3Sok%->SU||B*Z|h}E1$`|d!FdT=eThoUy~!_|Fz9}bvs79Wy5OmIWIU3l ze}qAg?Rm9URQ56b)m}Gi^V**xK4T}rJLNBpoi>F!-tz??j*KgXpig`dC8TbAgLFtn z_e*9I$@YXhQu#tCqlszyeOkBb?ekYctHO=%4-A!$d8Ul~%PNmnAsz2d;KXCEiPGTJ z_l-#=tyokwzqIu@wMLK>5i-*k+r9*n{7CwEIx#@)wRQy1;Cr2MdXr3CGaSF!C(fIp z6J&T1vw`ttvsc?~pXTF*K6mWQwTgk^(I*+r)k6?*aBGzHrNI5TldA5dpO;W_Goni1 zV!)B`C5Rap~+M!`&ET97+{7Um01 z)UA#0nW5ZM}IOO_hTAS64HT_S{#NHrMA zFtUwp$X+tm7-Y#FWoaz$rKg@>-{ZZHqbB`vyXL;H>$9Gp^DGGnMN>u}T=@|Ybo-eS zvPSRESxOYnXh4er+}$vQrHsthR8xM6MeYfKV)Ra5fJaJRhVyIlQZakWOP5$*Zi1K-wGu*LGmFA{H@!)rVW+`C zE5$qJeb6p`IR%L6AmTXTp|R~Qf>aGuQyYn6`R_J3WB5WI6R)CX7X(oDwVqK28d8j+ z#_`}qi$-9jA`sIpRyFr{%zn64z3=MMk9%2)ms~wcX{2~?%TatzKcqd`K&Rr?up^XB z3h!vkeGVdE3c+`6+QfCB_Xrl>5C8;+WZsIs#aSk7{NvKA$Uxc@XeY^kox^8j_ZUF`$lb~o zixW_nS>B08Okbu*w_LU($`^InjneYc+{%w<+A_>Kl5*a`Dn416{goQ0yviHU~P zUIR`0?al66gA)g9cW;Ym?yM+rk#^c7IHeMIx&A-8zR69oFJI9J$GB^vTvTR;E$TlGe4!iw6m?Dx$}%{Su^ZjeIjx)hKD z<#oV-gFHcbA2@!y7?_%gR}w=%f$TVPpmJ>YpMw7Nqb}n@4oF0 z8Sz*P^H=xPuzdIZ0Irzk6KjeB#)ZRTTuS#bUV!h5+2%bU>@^=Sw)_CQtr$MrhA4O;CHU#rK+WtAj5yXOg)kD? ziQ&$}>!&}SIb-uZZzVd6-L+-0LcO8NEx~SL^NIiI2|xOpRRo{)9a&bvY4sfwC6e0B z*e&==ktxgMi9Q8VH9cVGzxy@Z7IkM_(QvuCTZ3Wc{YC2;b#3xc7UB5IOX0w?P11&Z zKNFUHv!Tf_U!)P)UR3eJ7PP$}a67A(fT#;#>;8M?=B%XeoBGrVDV%s7h-#3kvMATy z7pxEyDYLltfvlOtM~{4bxIZ039k%O8BEe%3BSB-HBmH~VUz0BULbjS&*QoR_$;z64O#bP&ta`Zz2EClT|7`z$yjt1=2bn~lh zs}|SJor>3Zr{FTQ(0+zNSifJczE8!+_hZB zV^vE-RbsFjLxAFv#8q!|WX81Sx`O1~;<4Fn2sfILl9ZIhtquV~H9M#& zF5ZBz6$DAnqYLlW`0Y%a?0YO@LCu=8Y-^!+Mq93ScF1VaEq1(EHJfsu>m3&@A{Z?Itb*l<9!?R6SB~@Kjme z{*$BApdA_yj1YZfZCHZ9$jddYGZjxcvD$SM(1w&J8O*(3F?fMF|3JT4XGLe~ZtP4i==>@6WjW|RC z4Tyt00ZEQWzj}Z2<|t;U4ENRjXJYkUJ$l^a3CN>f;t78q)1_Cxf^V^@eDHExXl!kc zoG|84vV3>vI#VG8&^4#r>dAq=o!Jh>pPGK-*!fe%!|PTDZ0TM)672^&i5|)vcJ+OE zB@8g`9%ipeHtTy2_hfj;S#iCs%SOD1WAmuF8;d`q5dYL}v+2J4TV!OPWPY~Q*(nn% z1=4v7Q2!aDye?UMz>Kx0@9&4mF*rc4xsPk@Z=7y*svMw-jrj?oE_g!R>eN#}`2_~7 zB;$Zmgo@dn7B1r-Y8#mv`S)a)daE{AefK$OYwu^Y$~Az-RV;%2ps|e~bum?Wbv6a% zYhp0!w`X$HK&0D1Q_sHPzJaeRB`ot}jj-eTPfdre?L$I09^=<(QC?U77%>h>x?hE$ zyy`FE{jmrWv0?+K_MS+)X?zEIxLg*HD*)L$$0oIo_uf?@L z*IRk9`KEP&hr2YF+c0>xtNxP526>8F!hJOu$v!X=OhWsOn~uvl)%(okq2;#I_2(ns ztRiivY0M^Qf|Ve)k1LN*G$9}e9S;v#PRz_i&DC3!4`eVbiNcGI^4qngAbHff+x@as z$L}v85-OI1*m8A}K0`~$-m$(~iBD4jkzF=^{Yt33=uHRL>9=JGm5b}`DtisZcQlJ& zHPr7m#H5|gsnoBD)cCh#5&gh4q-@^rO&pX!V7zT^ATFR+!i z94I2L(tGjt=pN+<#*6iIR3+HMIuIQlojZ&7kUANuvw2)=RpCHw)u;p3*5$-pe9wHK zqDbI4C!y^APBbfy-)^-5jDw0VRKAn}Ujrn))IgcX{Y*m;HRiIWohzyLl~4hFE}x1m z3p!vMvw?De$_zuecj78;p?5_5$YeuG6;v;Tw|=UJ;z28y=Bu{k^+p0yj$emk7LzP0 zh9stS;C4>EM{x(Ot`VEmUbc2Ea-kPT$wL;cZhDq!GB#!S^0)YMX^!sH(su0GT~Dtt zT9E=1Oze)GP=UtQT9HvhIf&tYT~oQ=tuyxHiYS=f71Mziy-2}`{&y59rx5HrUO&-p zF>`c*z+~44Q4A5vuk{0w9nhq7O=ITe;RNa4wEdhlGrL~PD#U-gEr?7mB0eCcjcR#zukDjK-w^15H$HK+YI1uUNMrV4@rcc(4h7lk zCaa?J4JprS61HS~gZWhZ8)FLt}_MNG)o3vS4l?}%^eSFMTt-=3JhT7cf4O2;u|UG8zu zYBqUfNwdJ;sH`5s7djOXGWbUC^6x!$WS85D3#2KlM&?ua#g8Yr0e6DmJzZU0xrd)X zD69Ma{ri37S0~<4VIs+G5RD_}3oI8^Sbf=HZ)h+mgk;69CHwH`K?M|@IaBLn5htB` z_KA_8O^#OwuU`k?OYs%%;R32 zY$R1ccg_B>bTH5$C`vZCtM})4O^<^V_|qgfbS;E6LLaUi$J42+`L{ z%F4RPmEdN4C4rpBO3ByWf(>MaD{H}1_apLkn}-&M_pGBP0m$N-!G_b=X4@XCy>tCw)hQituqEpS2T2EI0C! zP<%rG$i}~Lm($Xh6?L~zgeX$A%##ZB84EB_>prZc#FujoGic%rJ;l(TfvH1RetE#G zlq5Q2mB5U47T?2*3G>aZxG!4Nn*=d6J{E*%zKrM>2Z3DOp4FM8qg5?x-=^AE-w78p zP0NTqIu-=83%Q0dS&WC)N;U7=^7jZ)o^I;@_gvayex*%DR!pW=s!`jw&n_ntQ=T2N z>o7(y)<+t71<=1B0krplRUOGxUj^weV5^zJn4M;zL zip*ug&ug*0TpUrPdBTbSvH85Hl`4&spiF^b;4+mL;uSj8+3a%cj}7`@_-g~TU9Mjl z@<#Iul#=yfa&!k?%RL2XcrElIAo5-NCgHaKr}wqJ1)DOZ?i05E;le_ZjP*AO09$cr zjPOcq%m?W@MB6!`a=D3@rif4WXBbF&N`_s%>;x7n#sZH>w@+P+MKFuOE*Xu-(Z|z~ zAYSegZFLX;`S!ssQy+(dqS(5)gjue3oH3wqY63)^`bI{aq%>Xf&?qU*vk0#Qm3LWI zHH!QcIdvY-Y8iudP6FxNYXVE!lgcOGWA$vOn&pe|w(r|k-k0Ug+0#77)P+q?CX7X^ zRIh(%aT}*>m#u1^Yh- z4LnmSQzpEyi_XC{fe{ca0Bv#5#99n?9^1gtk_7EUphJ)j>*J|g%+tI|pCrgb!y&$m zS*!ZpNL3QNcYoiyPhP~VU$3zfkat;-@NKVM4&t5PzL~1P)Bng>sE5UgU8aOgK48%= za+hNJea@Hh>1nU$x02M4llEDhbDP)Fl-;?{jR|%<5@!bT+IChaNedvg#ydZld)VB{ z3}`}9L5QUxhw5=Y^Q}9`Fhes)f3y_g5P|o;fub)qH-%)wS`4xg;f!1tKWuGY(;X_f zI!`Vd&rcO)H|q@<|Mny|^3ofR;Ag}*EiVcSrIU0075#Un6ti&}Orn~Nb&kr?4-Ljm zh7AoH#%2Y=`ozle>vY!O=fN-Py_V_sjwV_|6-&kM*rtzb7@TZ0L0hnQG?={WkaVca zHEzL=xQYn*@UEzT3os}ZTnbcb-1&T$?l}L(TCDbFAokFbBmVTjMuE-s-*)V#pp~zI z7WhHxX!lxa!xSK#6TPp>4=c8x^-fGoL~ykpFx}wS@V|R^Vh*_8K=P0kdB}^5WHSC< zN?azTNfhGu@Z_wJQ1k}@ogS;@a6VCSRX_9-2ISjm(NUq*vxIk0zS)D4PN$ZREG*aG zV+}^)y(hzG#%6js#ysn{%sv+0S}PHQ1J_aJif7;F{kFc6*=z|WBRxu`KRFcD46RjD z+r|UWx1j}Z$Lg4yoUrS-863{McTAEf_!vCmDOqamo_z<*^ePAoFym)kEAdJ}yOkxM z?0{{JQ?kr7JM#&t8ax}u2#PuP)NgD}94pt1+wQOR30bi56-DusKPKY7m=*Yon7#K! z6wl4=Hpf~ro4MSP#TZ~Rl`usOn)F#Pdf1Q9MGI}`pbLj4#i8s_P1t4kWrGJ01BOX` z7%zu$Q2ZmPE7Q`A#i1SKg*33-$?Gjmpl?=VQWraLpZOnh0#Hr9qN)vA2iN+Y$kwS5V*^}QZ`hx=4q zb2lli9s)pt(-W5s7_l&I`}P!>U7yJd{=3a-E3d~wBqlZWK%ujnJg@2vi7dwwqS(+doZ3GLdf|h zO6(_41tacfKylCy5#yo|dWuw!9_utgjNp=OQ*$lZj^_nT&fNRdQlJ}cE%S5@{l>=R zn!9Gpi|~c918FpO8h0|u<5CTJP2o)abaq0zfsa*x+oMFaUvHpw{SN3j7{KkOEiv_U z36}27JljAv9)Mc&{pc$kA)nnPw8~Roji2o~ebVUM>N&Bs&~}eitokK>z$hbM!bsVg z5p@_SQPLf-vw&sIwY)SGy^p;d>^O1i%pi~Aang51($$)KtX6(rM`V#@l6?^2A)hZ# z3P#186rE!c-87O>Y0lgYa|)+aRaI@r6;V@<^C9ORuuX&gAibs~U%hz@q%JTbeC;$- z1_@B3)Do^j!4TQ_et&i^tiQFhQ`WnVU(>(+tD{i!3deGUSy2T!E1H=*I?~x-Tu=*! zGVbv1e9!Kn$KaqnQch0lU(zPHbX#83jJmA0UYIPnX$aGD9_6EJ8?xlF=Xa8p6R{goDqqgr|7Td(?d zz=~S8mFJH|?_JR+-d{N_7N$TwZ>aZPCf`=oqciU%cJe~k)RteZ_)ITVWgYvrQAoJl ztsvh<-FbdTwr(OfXm8gF+}m{}6R%ElkuQA1r(4}(&^pMnH_gq0J4&NTq*iO}t zycBLC$ak}pa4P*mK>fPN>R5ucNyOy$_<=ow*tG3oIdPL*XRbFcV<$QoP(A2?<0R|_ zXN98^%I(Kar+M2od2eugQ2RJ4_6i;jKcKCcf?NckxQxT3uP93St@4N2am z4=L6A36CTe#6;N?DOSkUDsSQal)Or5IO)?TBw#;{rehAA8D2j6#=gBS`rEcs(PF%+ zZPLO07V=>U2}hlhpJo-Vp9L4VT<~JS+$b60Gh;12{+=N9DlQs6f146+<7>T&tqsAJl+$b|2mwRFQ zmgEu$F7(CXv**iM%YhyLbtNCG{MDEft19D)MPyItd-s$!kM5Vs@2EUuc>jL5mi2w> zCCAyGGirMihYKw#=qU&HBW4!{O85CIB>>GF^ddjMi1?HRebTn!zWQmOB_R>4ebb9I zF86IF*Brd}8^pA5&h#ozkcUCt{H%|gb^58nFzvYXNAokK{#)6FNPH=!w!H{nFg|oK zihITe)h_=ovq${}ML-MGrBuE@WV@1$pvyU2jK_#s`0#RV_dFjLGKbptA5W^Au@kjS|HZjrc#lBk1BYrZK7jMKNyNF zA@lUpD1MYd-Cb0s$Wg9;ivLpy`CSLmNv?@QEOXKJcd1ZMi1<@{rA(ym%1K%S4>t=c zgk8#$XN#-fNsI;U@n~^@QXYBeuzTM@PTSKx!v9D}6bNsfscZ#%hx5)tv@0m?0|4<( zAwCuQo{UV9wQc|cxJ<(ujqtUyWop9P0NAXnJ-LSA3o7wYz&eZme0 z7Y$lmuk?_-_QJn@L1ph};uj>^?JYUwQ?y>2Ywa?XZ#+;!mBvNA8_i_A9uP!VLTkq1 zsJEqOc>bnfQgAadi^xyJZ&PVRaU--ZqpCW3kDaa-d~MRO`0ZYuH5~!kZ&eMN zRyta&(Sg<(A%1M~U2YuhMUfiSd`FYLw6BTG*B>0R9v{wQkLD?;9n9!`R)n3lK zD`Ho~58wckoG5+!IAi(`;!von&Pd^R(Du&J#JonlkX~{eJ76a16pnDnTBbe^?k>a& zMvLg)6!cYh5>#~MO@#_7-bP}!7kRBNgJvTB)bh5Q7)|EI@z=wjn8`Cx30ROhD26KW z+qp8_b{l-uJYyXTSTaf6N}~qkA;A!NuH$BdWK$cp;~Gvb(qg5D*Fu!sm!VjgdPslR z@NL?8O0D^-?;)ya3|5!dRI!~VWqh;>moTt&AV*-UuPd4};kIoRlne8?_3UN$Y;iJd zu-;47-K0Nfv#wivtyZa2%yMz|1tWns=b1?U0M3S|@)m%z94uikZu`twKgEHn`mhmd zAO7-po8YG4zoDO4W`wHF`3LL5mGfN^y6RfGy7pFqU(e~!di;Ym+l#}mBPqVR#cEf1 zd&N=?4^zsRc1_xQ7AM_oO7Py8dO@*<(_;CL&6W`PX>AGlhAR21b*7UioW6@;+nTCY zk!}=m)D$;HV$3g56w(S6c}uQk`i>xF(hEhPeRw5daCFfQWE0M(e-OBij)OU(#fvk| zUmo;#5$OgRT$pN8j2T85cP-uO%oUethH`d6{#$##j!I#{+^@1}n%aZX&5!o}p zH_}as7r(kCO~B9G1Khr|{mYUKygvCRprPeD>&Nc1QN^1B$Y;zzq~$17z(fRDV?lmi z-Xlh7?W6JB zFpRA9#m`o_KB0pz7U2^nq&iMA%kdMqzQ?%p5W$jkOX2-itIDV!-KGTjcogS|t9#!d z(;U-DwiFzl#<3gz>@P*LU!@$QQ&ut;VH`nql+#(b@!Bh)1V0wuD36k1q#7ho@{V{j zExAXACt#h_70q#scc89~bU(crL||R<-;{q>2ufqVwfWJ2A4=O_F$*#7HxU-7*YHCM64AyT-gg^tzNe%q(l9uQcR*D6FC zFT|kj%hi9C=$oFeBE3)g2{a?<9+3IkJdROF?v)g0UC;Fw@u zBBI{Q8p_N(Sm4s+;rnU!CuI z<%$>O~PbK$;wcq93X1tWCsrPYgk7L(!UCd_eM=IW-RP;Q2~=Yq3~6Jc)h<(6S?J8wwDt z>qF@75?@bx@ZLDy7~npMN#mM1tyB+~xr_UYSjIDpCPdp_0WofAEA1$I$_B)hDMg7! zlZS zJ#Jnc!_1MwZKKMxAtLtCUaRlM`YYx#=9HNUss8BSfykGC3s4hnYt=u;JwBiQ5i@-9 z_K$fbskU)poKg_X22( zK9*?c(<8>XKe3TCM_)T^+CFsZJBtm|mhI##Vo*r-jWp2iyd*OkY0egQRTPpDGoZn0 z&LgwO4i7~KREqZS+!eeFN*saq)e;u>Wr=?E9?~P|#Y!TSK+f`kYED=M6`WU@z72>e z#)(OUm4R9UW@JOF#il9&qnY$k;CvuvWe?+WQM3WSp~mgRp2?`j)IneCF#Osb=6-0i z-`%#}n^0bSk*1%rgx(BU8a&Zq_}jR6yT2EA+dprd>SG&9{F7kJ*|BNpLlen^uEVbSZ!jCsUoC2co_ zpARW$-4pA*NW8!;%m%FOpvVho-FSoJGo|POUKDnIErdf$5%>sZX6LuG{Q`bBw_jY( zwFqtuhPgk(`!J7Wv7(qylESNWAB8X{1kjJbQB3KPezNk+G1!Ijlkh4i#t|bKYtR#4 z6Z{#oXeAFyL>DFBu@?T`48}IE_i>YA-%`v5ZMI6q-T5)i(fNs31BfirjpG!{D%C7!y*hv@Mm9FlTcPBdZ!<16xwxsUfVVymf!vOmBGCzO;y=B~zqXNa?)u zp2eXG90SlJqIaOVPKb7naj!30C$qO3Pm-&Co6a7=?%2C?y7E!A->vJ~lw( zuCWNO4!~!Y`fA^rz8Nl$R z9iTnArbVAO*E7Nact&c?TyuLKVg}e0ms_LN z;`rN(q&Y<_!0PJPzfORFrGp~=(>o371t-~D+i6@Gvh==u>HYZeC`}6;_975vE6RI) zvMBh2mbsn>E9UfZ6@?WztT?WWypRt+Wy&cvr? z_7rWqj&!kI9AC8zq|0_#e(zI@iC3(D{MLaDq2z&g6b~(Y+v)p3Of}BCA>gwbO{JcQUBnH2E=+gW{G__XpVj~VOcd^Hw(ZLu!p{$|kM89f zoAfW$EvsZCCv%&sw?Wt<-8B^5`vz(c-rEcemB_HLIhubr$MMRgTE#`CJVYW8{u;2rB`ND<_=+Cv~+nBIc_BVgB z=-uv_0}1-)p;0+{S7)$e_WgK3Kb zV!zfo)YYXPo4W6mwC6W~mdamO)W0X&nLQvDkpK(F6QtYx(Tn2AvI~aEZ$UB)1q|Mg z54pwGr+usw-luGpxTErLN_v;J^$mH6VOyldH@dZJ`5+}A$Ezd@rbau$zG=8%-M?0? z8U}IW3C_zqqJR495uoF(3%W<3G|5`A(}K(h#ctD=&??iV@n={4wHUESaGfc2DA-D` z4hGU%0VQ!5asoNbV_%G4*Ps~LxT{In#`^LP9F~-fs>k`C1*OrplloI_CNC|k*d|E{ z5Kz@?FDJ*x%%CpZEU4hT%QXN~hO7j~6F(4XQTAoIcN9tfbVAHu@bpDiMf6Xe49XzO z?t`vtVn#-W57!wY>n8s!txW8&`ro@+=b*{2miA&NYzVgZLk)*05nQQQrb-9P>!9e~ z(v#A7v_2wQGKs2gadEdwzzbtjFGK*ZPH*m9^jTUYi96BqV>mH}@Q|Mk;uiy zA-XyIKfDaw(MH|VvECm8;gucw;*t#JOA<5#TMC|QP--@;?E$BzIUpD=EGK8tUb#H0 z&iVJcz`qk;K6ThPW>n%1Z1JP`splKN(bEIO$R&b^=E zNrDqqG%N9uUi0t?)Iz;)#qu#|M12vUN%?Gw%O~vjnKblZ*Da zQlS>*&VrRgC*{JTTZ?bY^|0NQGgs2GrU=@|=Tsu5U5E;#m4WABgqbCYSEfbx#-Cqs zB0C#I)GB1{S5~e1df;muKdUAFdgH3fPdh+VlgI>Uf&Pv*6=P8@=AXR9#6n~!_b4nj z0hrI@L<3STXIE{ZF-$_j>;{nw=RRP2G2Bd;KGUp2?yq}`P3iyYzrQ*N z^l1;uxs4EN33|`VZ5!XU_IX`!kq|Q?N)RxE?Dh9#0jL=wYq8MD+P@*)n*#>9RyOUz zboBM=F6$F@$JFwDe_S$Oa9a|7xHq`*vlA}V9s~#$7+3M z!n@mY?PF@be%KkQHla}9)BkmO!7?9R{Wod2fKW$m&SBl%`B$*-eJ2a)c$@97a=g0a z5e$ZDRY-6nBdOlXwO0uCdtl~IyEsc|E%@&*$(Z}gH5+rUg;YYgB;s!X1qTEx&>!rV zL2WXC0R;r%V07fA>z|+lK4W1o4b>H=>xU{-kGcT{I5L9$e^&I*zbgv2EfS`AnDr)V zhF-&b+TDL_Ch?+M0!oyVx^O-(RF{4ZcJlVXNjVt#rJ;g6tz(f30CWBwEpTf6`-CoK z(WTH-f1B3QjuSC`aVE?Er&ZwQwqAH*xA^YI1S~yyAcEF>yuW z1hA%gV(-hyyX+-ouLEcp7{prg{#3e)ut+bb?#<{Hu%{*Ehj9_Iam=5q|<&Czv0 zQj1Y}2#F5-=_|EdO=SY@7Tft0*_+P#64KI1fTwz;?~A+I+QQDr9#O9&J3IASdl}h^ z)*ltParV6TA-!0{QxA{QLASbdPM>dQr_l?Zbb{3Fo`ylti^8;vkN$qn-Y5T^FX4^U~`YwZ|;>niRK!t*A@QC2ere<2~Me znn8wGva65>hHAtP_+WuLBu7w32J06S%M9MJSAWvUeGAf*0Fp%T`DK_-ncHd3*GhJ5 zV-IQyv4*v`c{zxcVyOv;X^DTtRaBnTheddfypgz2^A?}-K&19Dz|FGYett$)X+C@! zT>1A+_iqf$lw4i~f+S$)3`FY})P2}XV)&*9Kv?%C^}0g;;57sF;RvdL-KmKNpz-|w zUE-j}Qy+OS-DTK2NOh&sXK`VS?8b$pE)6%Y#}A9*z2-i}uoT-vKDSW=|NV9U{(b(O z%ejAFgtts8=>F3fHAqXD=-T3Hki7(Vh0@X!0DV0rbG~|pH@6=s zfKZ|T!w`J!8G@8h*ksxy-ndr%AUJ(W{nlSbKJnw#Uoj!rz`|zeoCk;(%soO*OwQ00 q7=U0r5B@+*U(Lw3}e@v + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + CFG + InstructionsDef-Use ChainsVariables + Call Graph + + + + + + + + + Class hierarchy + Supergraph + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Manual Queries + + Automated Analysis + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/docs/index.md b/docs/docs/index.md new file mode 100755 index 0000000000..d52cbba4f9 --- /dev/null +++ b/docs/docs/index.md @@ -0,0 +1,239 @@ +--- +title: "Home" +linkTitle: "Home" +weight: 20 +no_list: false +menu: + main: + weight: 20 +hide: + - navigation +description: > + The CPG library is a language-agnostic graph representation of source code. +--- + +## What does the CPG library offer? + + + +A Code Property Graph (CPG) is a graph-based representation of code which unites +several concepts such as an Abstract Syntax Tree (AST), Control Flow Graph +(CFG) or Evaluation Order Graph (EOG), Data Flow Graph (DFG) or Control Dependence +Graph (CDG), among others, in a single supergraph. This is beneficial because +the CPG contains the most relevant information to conduct static program +analysis and yet, the graph provides a certain abstraction of the respective +programming language. + + +

+ +

-
+

Highly Extensible

--- @@ -85,7 +85,7 @@ programming language. * analyses.
-
+

Handling Incomplete Code

--- @@ -97,7 +97,8 @@ programming language.
-
+
+
From c1ec0ae2392aade0299f303ee743dba70bec3f48 Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Thu, 15 Jun 2023 09:00:20 +0200 Subject: [PATCH 068/143] Hotfix for CI --- .github/workflows/build.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 22e27d35af..f44c492330 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,12 +7,12 @@ on: - main tags: - v*.** - paths: - - "!docs/**" + paths-ignore: + - "docs/**" pull_request: types: [opened, synchronize, reopened] - paths: - - "!docs/**" + paths-ignore: + - "docs/**" jobs: build-cpgo-osx: From 1b1be6942a07d8a778fa63791a4b9a027b8c9e04 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 15 Jun 2023 09:35:47 +0200 Subject: [PATCH 069/143] Update dependency gradle to v8 (#1213) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/wrapper/gradle-wrapper.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 070cb702f0..fae08049a6 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From 6e21176d1ebd1da49d0aa0da84a634e0269ec577 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 15 Jun 2023 09:55:46 +0200 Subject: [PATCH 070/143] Update actions/setup-python action to v4 (#1212) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 75642e346c..774f8c63a2 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -20,7 +20,7 @@ jobs: - name: Checkout uses: actions/checkout@v3 - name: Install python3 - uses: actions/setup-python@v3 + uses: actions/setup-python@v4 with: python-version: 3.x - name: Cache From 8268760d02fb178f1abcbf96f1d694f47c6d7c7c Mon Sep 17 00:00:00 2001 From: Tobias Specht Date: Thu, 15 Jun 2023 10:12:19 +0200 Subject: [PATCH 071/143] Add .cxx to supported extension list in unity build (#1202) --- .../main/kotlin/de/fraunhofer/aisec/cpg/TranslationManager.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationManager.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationManager.kt index d034a60fe4..bb5a52392a 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationManager.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationManager.kt @@ -184,7 +184,7 @@ private constructor( PrintWriter(tmpFile).use { writer -> list.forEach { - val cxxExtensions = listOf(".c", ".cpp", ".cc") + val cxxExtensions = listOf(".c", ".cpp", ".cc", ".cxx") if (cxxExtensions.contains(Util.getExtension(it))) { if (ctx.config.topLevel != null) { val topLevel = ctx.config.topLevel.toPath() From 3de0c16f93a8e36a400073a5b8d71a7546e3198c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 15 Jun 2023 08:25:10 +0000 Subject: [PATCH 072/143] Update module golang.org/x/mod to v0.11.0 (#1211) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- cpg-language-go/src/main/golang/go.mod | 2 +- cpg-language-go/src/main/golang/go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/cpg-language-go/src/main/golang/go.mod b/cpg-language-go/src/main/golang/go.mod index 76bb4cd174..e820295ff5 100644 --- a/cpg-language-go/src/main/golang/go.mod +++ b/cpg-language-go/src/main/golang/go.mod @@ -3,6 +3,6 @@ module cpg go 1.19 require ( - golang.org/x/mod v0.10.0 + golang.org/x/mod v0.11.0 tekao.net/jnigi v0.0.0-20230402215112-69b87aaf8714 ) diff --git a/cpg-language-go/src/main/golang/go.sum b/cpg-language-go/src/main/golang/go.sum index 793b174f76..a4a276172b 100644 --- a/cpg-language-go/src/main/golang/go.sum +++ b/cpg-language-go/src/main/golang/go.sum @@ -6,6 +6,8 @@ golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs= golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU= +golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= tekao.net/jnigi v0.0.0-20220921102452-ce6d0be0c331 h1:p5apvrQZPCacG+Ux6GMzLWX4mUZOPlguj0MrONXutrQ= tekao.net/jnigi v0.0.0-20220921102452-ce6d0be0c331/go.mod h1:SmVvXetJ8N0ov5c2eOC+IxmkdYGEyuXghTuBq5HWZ/Y= tekao.net/jnigi v0.0.0-20221227053512-56e0101fa996 h1:Vl0GEBxRKyS1+/fjd9H6ptV7t/CAmfgxtsanvqsCob8= From 6378d905b6ba96edadd0a8fe76ad8a9b7f99f27b Mon Sep 17 00:00:00 2001 From: KuechA <31155350+KuechA@users.noreply.github.com> Date: Thu, 15 Jun 2023 13:30:01 +0200 Subject: [PATCH 073/143] More updates to the documentation page (#1208) * fix docs with alex * docs: more markdown is always better * Set background color of button * Remove useless style * Inter font as asset * Font variation * Slightly smaller font size --------- Co-authored-by: Maximilian Kaul Co-authored-by: Christian Banse --- docs/docs/assets/fonts/Inter/LICENSE.txt | 92 +++++++ .../Inter/font-files/Inter-italic.var.woff2 | Bin 0 -> 245036 bytes .../Inter/font-files/Inter-roman.var.woff2 | Bin 0 -> 227180 bytes .../fonts/Inter/font-files/Inter.var.woff2 | Bin 0 -> 324864 bytes .../assets/fonts/Inter/font-files/inter.css | 200 ++++++++++++++++ docs/docs/assets/fonts/Inter/inter.css | 206 ++++++++++++++++ docs/docs/index.md | 13 +- docs/docs/stylesheets/extra.css | 224 +++++++++--------- docs/mkdocs-material-plugins.txt | 2 +- docs/mkdocs.yaml | 40 ++-- 10 files changed, 636 insertions(+), 141 deletions(-) create mode 100644 docs/docs/assets/fonts/Inter/LICENSE.txt create mode 100644 docs/docs/assets/fonts/Inter/font-files/Inter-italic.var.woff2 create mode 100644 docs/docs/assets/fonts/Inter/font-files/Inter-roman.var.woff2 create mode 100644 docs/docs/assets/fonts/Inter/font-files/Inter.var.woff2 create mode 100644 docs/docs/assets/fonts/Inter/font-files/inter.css create mode 100644 docs/docs/assets/fonts/Inter/inter.css diff --git a/docs/docs/assets/fonts/Inter/LICENSE.txt b/docs/docs/assets/fonts/Inter/LICENSE.txt new file mode 100644 index 0000000000..9b2ca37b3f --- /dev/null +++ b/docs/docs/assets/fonts/Inter/LICENSE.txt @@ -0,0 +1,92 @@ +Copyright (c) 2016 The Inter Project Authors (https://github.com/rsms/inter) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION AND CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/docs/docs/assets/fonts/Inter/font-files/Inter-italic.var.woff2 b/docs/docs/assets/fonts/Inter/font-files/Inter-italic.var.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..b826d5af84b3bd70535b6bb993f443a2deb46894 GIT binary patch literal 245036 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�LzqE;f#83j;-svHFcR`yUq8K)}}#m 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 C+W+PN literal 0 HcmV?d00001 diff --git a/docs/docs/assets/fonts/Inter/font-files/Inter-roman.var.woff2 b/docs/docs/assets/fonts/Inter/font-files/Inter-roman.var.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..6a256a068f0dce7b44f8314bdc735ef91b35e2e3 GIT binary patch literal 227180 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�J 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≫bY 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≥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

6&qaNDxLwZnq2 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

ycBey{rl;kDc{Ai+(=LzE1TWNYW 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

+
-
-

About Us - -

+
-

We're a team of researchers at Fraunhofer AISEC. +## About Us + +We're a team of researchers at Fraunhofer AISEC. We're interested in different topics in the area of static program analysis. If you're interested in our work, feel free to reach out to us - we're happy to collaborate and push the boundaries of static code analysis. -

+

diff --git a/docs/docs/stylesheets/extra.css b/docs/docs/stylesheets/extra.css index 5c78c78f26..b9f34e4eb6 100755 --- a/docs/docs/stylesheets/extra.css +++ b/docs/docs/stylesheets/extra.css @@ -1,24 +1,24 @@ [data-md-color-scheme="light"] { - --md-primary-fg-color: var(--fraunhofer-green); - --md-secondary-fg-color: var(--fraunhofer-blue); - --md-accent-fg-color: #728bab; - --md-typeset-color: #555555; -} + --md-primary-fg-color: var(--fraunhofer-green); + --md-secondary-fg-color: var(--fraunhofer-blue); + --md-accent-fg-color: #728bab; + --md-typeset-color: #555555; +} [data-md-color-scheme="slate"] { - --md-primary-fg-color: var(--fraunhofer-green); - --md-footer-bg-color--dark: var(--fraunhofer-blue); + --md-primary-fg-color: var(--fraunhofer-green); + --md-footer-bg-color--dark: var(--fraunhofer-blue); /* need to set the typeset color because it uses the primary color otherwise*/ - --md-typeset-a-color: #5981b4; + --md-typeset-a-color: #5981b4; --md-hue: 220; - --md-accent-fg-color: #a4bede; + --md-accent-fg-color: #a4bede; } :root { - --md-text-font-family: FrutigerLTW02,"Helvetica Neue",Helvetica,Arial,sans-serif; + --md-text-font-family: "Inter var experimental", "Helvetica Neue", Helvetica, Arial, sans-serif; --md-footer-bg-color--dark: var(--fraunhofer-blue); --md-footer-bg-color--light: var(--fraunhofer-blue); - --md-default-fg-color--light: #1f82c0; + --md-default-fg-color--light: #1f82c0; --fraunhofer-green: rgb(23, 156, 125); --fraunhofer-green-light: rgba(23, 156, 125, 0.2); --fraunhofer-blue: rgb(0, 91, 127); @@ -29,28 +29,30 @@ } body { - --md-text-font-family: FrutigerLTW02,"Helvetica Neue",Helvetica,Arial,sans-serif; + --md-text-font-family: "Inter var experimental", "Helvetica Neue", Helvetica, Arial, sans-serif; + font-feature-settings: "cv11", "ss01" } .md-typeset .admonition.paper, .md-typeset details.paper { border-color: var(--fraunhofer-green); - font-size: 0.8rem; + font-size: 0.7rem; } .md-typeset hr { border: 1px solid #c7cacc; - border-top: none; + border-top: none; display: flow-root; margin: 1.5em 0; - margin-top: 0; - margin-right: 0; - margin-bottom: 1.5em; - margin-left: 0; + margin-top: 0; + margin-right: 0; + margin-bottom: 1.5em; + margin-left: 0; } .md-typeset { line-height: 1.1rem; + font-size: 0.7rem; } .md-typeset h1 { @@ -58,7 +60,7 @@ body { font-size: 2em; line-height: 1.3; border: 1px solid #c7cacc; - border-bottom: none; + border-bottom: none; padding-top: 1.2rem; padding-right: 0.8rem; padding-bottom: 1.2rem; @@ -70,8 +72,8 @@ body { .md-typeset p { border: 1px solid #c7cacc; - border-bottom: none; - border-top: none; + border-bottom: none; + border-top: none; padding-right: 0.8rem; padding-left: 0.8rem; padding-bottom: 0.8rem; @@ -80,8 +82,8 @@ body { .md-typeset div.papers { border: 1px solid #c7cacc; - border-bottom: none; - border-top: none; + border-bottom: none; + border-top: none; padding-top: 1.5625em; padding-bottom: 1.5625em; padding-right: 0.8rem; @@ -93,9 +95,9 @@ body { color: var(--md-default-fg-color--light); font-size: 1.5625em; line-height: 1.4; - font-weight:bold; + font-weight: bold; border: 1px solid #c7cacc; - border-bottom: none; + border-bottom: none; padding-top: 1.2rem; padding-right: 0.8rem; padding-bottom: 1.2rem; @@ -107,14 +109,14 @@ body { color: var(--md-default-fg-color--light); font-size: 1.2em; line-height: 1.2; - font-weight:bold; + font-weight: bold; padding-top: 1.2rem; padding-right: 0.8rem; padding-bottom: 0.8rem; padding-left: 0.8rem; margin: 0; border: 1px solid #c7cacc; - border-bottom: none; + border-bottom: none; } .md-typeset h4 { @@ -123,8 +125,8 @@ body { padding-left: 0.8rem; margin: 0; border: 1px solid #c7cacc; - border-top: none; - border-bottom: none; + border-top: none; + border-bottom: none; } .md-typeset h5 { @@ -133,8 +135,8 @@ body { padding-left: 0.8rem; margin: 0; border: 1px solid #c7cacc; - border-top: none; - border-bottom: none; + border-top: none; + border-bottom: none; } .md-typeset .highlight { @@ -142,28 +144,29 @@ body { padding-left: 0.8rem; margin: 0; border: 1px solid #c7cacc; - border-top: none; - border-bottom: none; + border-top: none; + border-bottom: none; } -.md-typeset .highlight > pre{ - margin:0; - padding-top:1em; - padding-bottom:1em; +.md-typeset .highlight>pre { + margin: 0; + padding-top: 1em; + padding-bottom: 1em; } -[dir="ltr"] .md-typeset ol, [dir="ltr"] .md-typeset ul { +[dir="ltr"] .md-typeset ol, +[dir="ltr"] .md-typeset ul { padding-right: 0.8rem; padding-left: 1.2rem; - padding-top:0em; - padding-bottom:1em; + padding-top: 0em; + padding-bottom: 1em; margin: 0; border: 1px solid #c7cacc; - border-top: none; - border-bottom: none; + border-top: none; + border-bottom: none; } -.md-typeset .box h3 + hr { +.md-typeset .box h3+hr { padding-top: 1.2rem; border-left: none; border-right: none; @@ -174,13 +177,13 @@ body { padding-left: 1rem; margin: 0; border: 1px solid #c7cacc; - border-top: none; - border-bottom: none; + border-top: none; + border-bottom: none; } .md-typeset .child { - background:#dddddd; - border-radius:5%; + background: #dddddd; + border-radius: 5%; line-height: 26px; display: inline-block; text-align: center; @@ -190,22 +193,23 @@ body { } .md-typeset .classLabel { - background:var(--fraunhofer-blue-light); + background: var(--fraunhofer-blue-light); color: #ffffff; } .md-typeset .relationship { - background:var(--fraunhofer-blue); + background: var(--fraunhofer-blue); color: #ffffff; } -.md-typeset .relationship > a:link, -.md-typeset .relationship > a:visited, -.md-typeset .relationship > a:hover, -.md-typeset .relationship > a:active { - color:#FFFFFF; + +.md-typeset .relationship>a:link, +.md-typeset .relationship>a:visited, +.md-typeset .relationship>a:hover, +.md-typeset .relationship>a:active { + color: #FFFFFF; } -.md-typeset h2 + h3 { +.md-typeset h2+h3 { margin: 0; } @@ -213,7 +217,7 @@ body { color: var(--md-default-fg-color--light); font-size: 1.2em; line-height: 1.2; - font-weight:bold; + font-weight: bold; padding-top: 1.2rem; padding-right: 0.8rem; padding-bottom: 0rem; @@ -222,7 +226,8 @@ body { border: none; } -[dir="ltr"] .md-typeset .box ol, [dir="ltr"] .md-typeset .box ul { +[dir="ltr"] .md-typeset .box ol, +[dir="ltr"] .md-typeset .box ul { border: none; } @@ -234,23 +239,23 @@ body { height: 2em; } -.md-typeset .paper > .admonition-title, -.md-typeset .paper > summary { +.md-typeset .paper>.admonition-title, +.md-typeset .paper>summary { background-color: var(--fraunhofer-green); border-color: var(--fraunhofer-green); - font-size: 0.9rem; - padding-left:3rem; + font-size: 0.8rem; + padding-left: 3rem; margin-left: 0px; margin-top: .625em; min-height: 2rem; vertical-align: middle; } -.md-typeset .paper > .admonition-title::before, -.md-typeset .paper > summary::before { +.md-typeset .paper>.admonition-title::before, +.md-typeset .paper>summary::before { background-color: #FFFFFF; -webkit-mask-image: var(--md-admonition-icon--paper); - mask-image: var(--md-admonition-icon--paper); + mask-image: var(--md-admonition-icon--paper); height: 2rem; width: 2rem; top: 0px; @@ -258,45 +263,45 @@ body { .md-typeset .paper p { border: none; - padding:0px; + padding: 0px; } .md-typeset .paper { - border-radius:0px; + border-radius: 0px; background-color: var(--fraunhofer-green); - color:#FFFFFF; - margin-top:1px; - margin-bottom:1px; + color: #FFFFFF; + margin-top: 1px; + margin-bottom: 1px; } -.md-typeset details > .admonition-title::before, -.md-typeset details > summary::before { +.md-typeset details>.admonition-title::before, +.md-typeset details>summary::before { background-color: #FFFFFF; } -.md-typeset details { +.md-typeset details { background-color: var(--fraunhofer-blue); color: #FFFFFF; } -.md-typeset details > summary { +.md-typeset details>summary { text-transform: uppercase; } .authors { font-weight: 300; - margin:0; + margin: 0; } .conference { font-size: .7rem; - margin:0; + margin: 0; } div.left { - float:left; - width:80%; - padding-left:3rem; + float: left; + width: 80%; + padding-left: 3rem; } div.right-picture { @@ -318,9 +323,9 @@ div.right-picture img { order: 0; align-self: stretch; flex-grow: 0; -} - -div.right-picture-text { +} + +div.right-picture-text { display: flex; flex-direction: column; align-items: flex-start; @@ -328,18 +333,18 @@ div.right-picture-text { flex: none; order: 1; flex-grow: 0; -} +} div.right-picture-text p { border: none; } - + div.right-picture-text h2 { border: none; } -div.left-picture { +div.left-picture { display: flex; flex-direction: column; align-items: flex-start; @@ -357,7 +362,7 @@ div.left-picture img { align-self: stretch; flex-grow: 0; object-fit: cover; - height:100%; + height: 100%; } div.left-picture-text { @@ -386,7 +391,7 @@ div.float-container { align-items: center; padding: 0px; border: 1px solid #c7cacc; - border-bottom: none; + border-bottom: none; flex: none; order: 0; align-self: stretch; @@ -394,30 +399,24 @@ div.float-container { } div.right { - float:right; - width:18%; - padding-left:2%; + float: right; + width: 18%; + padding-left: 2%; vertical-align: middle; } -.md-typeset details > summary { +.md-typeset details>summary { background-color: var(--fraunhofer-green-light); border-color: var(--fraunhofer-green); } .md-typeset details { - margin-top:0; + margin-top: 0; border-color: var(--fraunhofer-green); } - -.md-typeset .3-cards-grid { - display: grid; - grid-template-columns: 100px 100px 100px; -} - .box { - border: 1px solid #c7cacc; + border: 1px solid #c7cacc; } .box p { @@ -425,24 +424,23 @@ div.right { } a.green-button { - -webkit-appearance: button; - -moz-appearance: button; - appearance: button; - color:#FFFFFF; - border:1px solid #FFFFFF; - padding-top:0.2rem; - padding-bottom:0.2rem; - color: initial; - width:100%; - display: inline-block; - text-align:center; - margin-top: 0.3em; - text-transform: uppercase; + -webkit-appearance: button; + -moz-appearance: button; + appearance: button; + border: 1px solid #FFFFFF; + padding-top: 0.2rem; + padding-bottom: 0.2rem; + background-color: var(--fraunhofer-green); + width: 100%; + display: inline-block; + text-align: center; + margin-top: 0.3em; + text-transform: uppercase; } a.green-button:link, a.green-button:visited, a.green-button:hover, a.green-button:active { - color:#FFFFFF; -} + color: #FFFFFF; +} \ No newline at end of file diff --git a/docs/mkdocs-material-plugins.txt b/docs/mkdocs-material-plugins.txt index 5bc1347690..9d918e48ba 100755 --- a/docs/mkdocs-material-plugins.txt +++ b/docs/mkdocs-material-plugins.txt @@ -1,3 +1,3 @@ mkdocs-git-revision-date-localized-plugin mkdocs-glightbox -mkdocs-minify-plugin +mkdocs-minify-plugin \ No newline at end of file diff --git a/docs/mkdocs.yaml b/docs/mkdocs.yaml index d88d99f2dd..1703a5ddb0 100755 --- a/docs/mkdocs.yaml +++ b/docs/mkdocs.yaml @@ -1,9 +1,8 @@ - # yaml-language-server: $schema=https://squidfunk.github.io/mkdocs-material/schema.json # Project information site_name: Code Property Graph -site_url: https://fraunhofer-aisec.github.io/cpg/ +site_url: https://fraunhofer-aisec.github.io/cpg/ site_author: Fraunhofer AISEC site_description: Language-agnostic code analysis @@ -61,11 +60,12 @@ theme: toggle: icon: material/brightness-4 name: Switch to light mode - + font: false extra_css: - stylesheets/extra.css + - assets/fonts/Inter/inter.css # Plugins plugins: @@ -149,23 +149,23 @@ markdown_extensions: - pymdownx.tilde nav: - - 'Home': index.md - - 'Getting Started': + - "Home": index.md + - "Getting Started": - GettingStarted/index.md - - 'Installing the CPG library': GettingStarted/installation.md - - 'Usage as library': GettingStarted/library.md - - 'Using the Interactive CLI': GettingStarted/cli.md - - 'Using the Query API': GettingStarted/query.md - - 'Specifications': + - "Installing the CPG library": GettingStarted/installation.md + - "Usage as library": GettingStarted/library.md + - "Using the Interactive CLI": GettingStarted/cli.md + - "Using the Query API": GettingStarted/query.md + - "Specifications": - CPG/specs/index.md - - 'Graph Schema': CPG/specs/graph.md - - 'Dataflow Graph (DFG)': CPG/specs/dfg.md - - 'Evaluation Order Graph (EOG)': CPG/specs/eog.md - - 'Implementation': + - "Graph Schema": CPG/specs/graph.md + - "Dataflow Graph (DFG)": CPG/specs/dfg.md + - "Evaluation Order Graph (EOG)": CPG/specs/eog.md + - "Implementation": - CPG/impl/index.md - - 'Language Frontends': CPG/impl/language.md - - 'Scopes': CPG/impl/scopes.md - - 'Passes': CPG/impl/passes.md - - 'Contributing': - - 'Contributing to the CPG library': Contributing/index.md - - 'Documentation': dokka/dokkaCustomMultiModuleOutput + - "Language Frontends": CPG/impl/language.md + - "Scopes": CPG/impl/scopes.md + - "Passes": CPG/impl/passes.md + - "Contributing": + - "Contributing to the CPG library": Contributing/index.md + - "Documentation": dokka/dokkaCustomMultiModuleOutput From ff1ce61cc39b06a93ec187400f8dbf3efa1e7161 Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Thu, 15 Jun 2023 16:24:11 +0200 Subject: [PATCH 074/143] Only timeout on walker speed, not AST creation (#1216) Only timeoout on walker speed, not AST creation --- .../fraunhofer/aisec/cpg/graph/WalkerTest.kt | 80 +++++++++---------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/WalkerTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/WalkerTest.kt index ee6d757367..6fa8a8be57 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/WalkerTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/WalkerTest.kt @@ -42,60 +42,60 @@ import org.junit.jupiter.api.assertTimeout class WalkerTest : BaseTest() { @Test fun testWalkerSpeed() { - // Traversal of about 80.000 nodes should not exceed 1s (on GitHub). On a recently fast - // machine, such as MacBook M1, this should take about 200-300ms. - assertTimeout(Duration.of(1500, ChronoUnit.MILLIS)) { - val tu = TranslationUnitDeclaration() - - // Let's build some fake CPG trees with a good amount of classes - for (i in 0..100) { - val record = RecordDeclaration() - record.name = Name("class${i}") - - // Each class should have a couple of dozen functions - for (j in 0..20) { - val method = MethodDeclaration() - method.name = Name("method${j}", record.name) + val tu = TranslationUnitDeclaration() - val comp = CompoundStatement() + // Let's build some fake CPG trees with a good amount of classes + for (i in 0..100) { + val record = RecordDeclaration() + record.name = Name("class${i}") - // Each method has a body with contains a fair amount of variable declarations - for (k in 0..10) { - val stmt = DeclarationStatement() - val decl = VariableDeclaration() - decl.name = Name("var${i}") + // Each class should have a couple of dozen functions + for (j in 0..20) { + val method = MethodDeclaration() + method.name = Name("method${j}", record.name) - // With a literal initializer - val lit = Literal() - lit.value = k - decl.initializer = lit + val comp = CompoundStatement() - stmt.addToPropertyEdgeDeclaration(decl) + // Each method has a body with contains a fair amount of variable declarations + for (k in 0..10) { + val stmt = DeclarationStatement() + val decl = VariableDeclaration() + decl.name = Name("var${i}") - comp.addStatement(stmt) - } + // With a literal initializer + val lit = Literal() + lit.value = k + decl.initializer = lit - method.body = comp + stmt.addToPropertyEdgeDeclaration(decl) - record.addMethod(method) + comp.addStatement(stmt) } - // And a couple of fields - for (j in 0..40) { - val field = FieldDeclaration() - field.name = Name("field${j}", record.name) + method.body = comp - // With a literal initializer - val lit = Literal() - lit.value = j - field.initializer = lit + record.addMethod(method) + } - record.addField(field) - } + // And a couple of fields + for (j in 0..40) { + val field = FieldDeclaration() + field.name = Name("field${j}", record.name) - tu.addDeclaration(record) + // With a literal initializer + val lit = Literal() + lit.value = j + field.initializer = lit + + record.addField(field) } + tu.addDeclaration(record) + } + + // Traversal of about 80.000 nodes should not exceed 1s (on GitHub). On a recently fast + // machine, such as MacBook M1, this should take about 200-300ms. + assertTimeout(Duration.of(1500, ChronoUnit.MILLIS)) { val bench = Benchmark(WalkerTest::class.java, "Speed of Walker") val flat = SubgraphWalker.flattenAST(tu) bench.stop() From 5b981fcff1861786bb18eb169b8bf9ae2d35cb3d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 15 Jun 2023 14:36:44 +0000 Subject: [PATCH 075/143] Update dependency org.eclipse.platform:org.eclipse.core.runtime to v3.27.0 (#1205) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index cd82eb19c2..407773c565 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -23,7 +23,7 @@ neo4j-ogm-bolt = { module = "org.neo4j:neo4j-ogm-bolt-driver", version.ref = "ne javaparser = { module = "com.github.javaparser:javaparser-symbol-solver-core", version = "3.25.0"} jackson = { module = "com.fasterxml.jackson.module:jackson-module-kotlin", version = "2.15.0"} -eclipse-runtime = { module = "org.eclipse.platform:org.eclipse.core.runtime", version = "3.26.0"} +eclipse-runtime = { module = "org.eclipse.platform:org.eclipse.core.runtime", version = "3.27.0"} osgi-service = { module = "org.osgi:org.osgi.service.prefs", version = "1.1.2"} icu4j = { module = "com.ibm.icu:icu4j", version = "73.1"} eclipse-cdt-core = { module = "org.eclipse.cdt:core", version = "8.0.0.202211292120"} From cb41f47b0a5cce6b697ec02609ba5afec5f06abd Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Thu, 15 Jun 2023 16:39:37 +0200 Subject: [PATCH 076/143] Trying to publish Javadoc (#1214) --- .github/workflows/build.yml | 13 ++++++++++--- .github/workflows/docs.yml | 1 + 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f44c492330..26d55dc522 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -115,13 +115,13 @@ jobs: run: | if [ "$SONAR_TOKEN" != "" ] then - ./gradlew --no-daemon --parallel -Pversion=$VERSION -Pexperimental -PenableJavaFrontend=true -PenableCXXFrontend=true -PenableGoFrontend=true -PenablePythonFrontend=true -PenableLLVMFrontend=true -PenableTypeScriptFrontend=true -Pintegration spotlessCheck -x spotlessApply build -x distZip -x distTar testCodeCoverageReport sonar \ + ./gradlew --no-daemon --parallel -Pversion=$VERSION -Pexperimental -PenableJavaFrontend=true -PenableCXXFrontend=true -PenableGoFrontend=true -PenablePythonFrontend=true -PenableLLVMFrontend=true -PenableTypeScriptFrontend=true -Pintegration spotlessCheck -x spotlessApply build -x distZip -x distTar testCodeCoverageReport sonar dokkaHtmlMultiModule \ -Dsonar.projectKey=Fraunhofer-AISEC_cpg \ -Dsonar.organization=fraunhofer-aisec \ -Dsonar.host.url=https://sonarcloud.io \ -Dsonar.login=$SONAR_TOKEN else - ./gradlew --no-daemon --parallel -Pversion=$VERSION -Pexperimental -PenableJavaFrontend=true -PenableCXXFrontend=true -PenableGoFrontend=true -PenablePythonFrontend=true -PenableLLVMFrontend=true -PenableTypeScriptFrontend=true -Pintegration spotlessCheck -x spotlessApply build -x distZip -x distTar + ./gradlew --no-daemon --parallel -Pversion=$VERSION -Pexperimental -PenableJavaFrontend=true -PenableCXXFrontend=true -PenableGoFrontend=true -PenablePythonFrontend=true -PenableLLVMFrontend=true -PenableTypeScriptFrontend=true -Pintegration spotlessCheck -x spotlessApply build -x distZip -x distTar dokkaHtmlMultiModule fi id: build env: @@ -144,11 +144,18 @@ jobs: ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.GPG_PASSWORD }} ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }} ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} - - name: Publish JavaDoc + - name: Publish JavaDoc (version) if: startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, 'beta') && !contains(github.ref, 'alpha') uses: JamesIves/github-pages-deploy-action@v4 with: folder: build/dokkaCustomMultiModuleOutput + target-folder: dokka/${{ env.version }} + - name: Publish JavaDoc (main) + if: github.ref == 'refs/head/main' + uses: JamesIves/github-pages-deploy-action@v4 + with: + folder: build/dokkaCustomMultiModuleOutput + target-folder: dokka/main - name: "Create Release" if: startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, 'beta') && !contains(github.ref, 'alpha') id: create_release diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 774f8c63a2..c532c7681e 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -44,3 +44,4 @@ jobs: uses: JamesIves/github-pages-deploy-action@v4 with: folder: docs/site + clean-exclude: dokka/** From 690fdd27123b5306b4d9b57deef34821cedd05c3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 15 Jun 2023 14:58:06 +0000 Subject: [PATCH 077/143] Update dependency com.ibm.icu:icu4j to v73.2 (#1204) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 407773c565..2630a9b0ed 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -25,7 +25,7 @@ javaparser = { module = "com.github.javaparser:javaparser-symbol-solver-core", v jackson = { module = "com.fasterxml.jackson.module:jackson-module-kotlin", version = "2.15.0"} eclipse-runtime = { module = "org.eclipse.platform:org.eclipse.core.runtime", version = "3.27.0"} osgi-service = { module = "org.osgi:org.osgi.service.prefs", version = "1.1.2"} -icu4j = { module = "com.ibm.icu:icu4j", version = "73.1"} +icu4j = { module = "com.ibm.icu:icu4j", version = "73.2"} eclipse-cdt-core = { module = "org.eclipse.cdt:core", version = "8.0.0.202211292120"} commons-io = { module = "commons-io:commons-io", version = "2.13.0"} jetbrains-annotations = { module = "org.jetbrains:annotations", version = "24.0.0"} From 663019de3ce5927b79791a36202ca2a6e9b57d3f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 15 Jun 2023 15:20:40 +0000 Subject: [PATCH 078/143] Update dependency webpack to v5.87.0 (#1210) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .../src/main/nodejs/package.json | 2 +- .../src/main/nodejs/yarn.lock | 28 +++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/cpg-language-typescript/src/main/nodejs/package.json b/cpg-language-typescript/src/main/nodejs/package.json index b4e8e5611e..09a3c378bd 100644 --- a/cpg-language-typescript/src/main/nodejs/package.json +++ b/cpg-language-typescript/src/main/nodejs/package.json @@ -11,7 +11,7 @@ }, "license": "Apache-2.0", "devDependencies": { - "webpack": "5.86.0", + "webpack": "5.87.0", "webpack-cli": "5.1.0" } } \ No newline at end of file diff --git a/cpg-language-typescript/src/main/nodejs/yarn.lock b/cpg-language-typescript/src/main/nodejs/yarn.lock index c583d4be75..def4827b4a 100644 --- a/cpg-language-typescript/src/main/nodejs/yarn.lock +++ b/cpg-language-typescript/src/main/nodejs/yarn.lock @@ -336,10 +336,10 @@ electron-to-chromium@^1.3.723: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.772.tgz#fd1ed39f9f3149f62f581734e4f026e600369479" integrity sha512-X/6VRCXWALzdX+RjCtBU6cyg8WZgoxm9YA02COmDOiNJEZ59WkQggDbWZ4t/giHi/3GS+cvdrP6gbLISANAGYA== -enhanced-resolve@^5.14.1: - version "5.14.1" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.14.1.tgz#de684b6803724477a4af5d74ccae5de52c25f6b3" - integrity sha512-Vklwq2vDKtl0y/vtwjSesgJ5MYS7Etuk5txS8VdKL4AOS1aUlD96zqIfsOSLQsdv3xgMRbtkWM8eG9XDfKUPow== +enhanced-resolve@^5.15.0: + version "5.15.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz#1af946c7d93603eb88e9896cee4904dc012e9c35" + integrity sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg== dependencies: graceful-fs "^4.2.4" tapable "^2.2.0" @@ -634,10 +634,10 @@ schema-utils@^3.1.1: ajv "^6.12.5" ajv-keywords "^3.5.2" -schema-utils@^3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.1.2.tgz#36c10abca6f7577aeae136c804b0c741edeadc99" - integrity sha512-pvjEHOgWc9OWA/f/DE3ohBWTD6EleVLf7iFUkoSwAxttdBhB9QUebQgxER2kWueOvRJXPHNnyrvvh9eZINB8Eg== +schema-utils@^3.2.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.3.0.tgz#f50a88877c3c01652a15b622ae9e9795df7a60fe" + integrity sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg== dependencies: "@types/json-schema" "^7.0.8" ajv "^6.12.5" @@ -772,10 +772,10 @@ webpack-sources@^3.2.3: resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== -webpack@5.86.0: - version "5.86.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.86.0.tgz#b0eb81794b62aee0b7e7eb8c5073495217d9fc6d" - integrity sha512-3BOvworZ8SO/D4GVP+GoRC3fVeg5MO4vzmq8TJJEkdmopxyazGDxN8ClqN12uzrZW9Tv8EED8v5VSb6Sqyi0pg== +webpack@5.87.0: + version "5.87.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.87.0.tgz#df8a9c094c6037f45e0d77598f9e59d33ca3a98c" + integrity sha512-GOu1tNbQ7p1bDEoFRs2YPcfyGs8xq52yyPBZ3m2VGnXGtV9MxjrkABHm4V9Ia280OefsSLzvbVoXcfLxjKY/Iw== dependencies: "@types/eslint-scope" "^3.7.3" "@types/estree" "^1.0.0" @@ -786,7 +786,7 @@ webpack@5.86.0: acorn-import-assertions "^1.9.0" browserslist "^4.14.5" chrome-trace-event "^1.0.2" - enhanced-resolve "^5.14.1" + enhanced-resolve "^5.15.0" es-module-lexer "^1.2.1" eslint-scope "5.1.1" events "^3.2.0" @@ -796,7 +796,7 @@ webpack@5.86.0: loader-runner "^4.2.0" mime-types "^2.1.27" neo-async "^2.6.2" - schema-utils "^3.1.2" + schema-utils "^3.2.0" tapable "^2.1.1" terser-webpack-plugin "^5.3.7" watchpack "^2.4.0" From 84293e756ed693dd99829f75986e6e1fc8c11781 Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Fri, 16 Jun 2023 15:17:39 +0200 Subject: [PATCH 079/143] Fixed typo in JavaDoc publish --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 26d55dc522..de7030ee02 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -151,7 +151,7 @@ jobs: folder: build/dokkaCustomMultiModuleOutput target-folder: dokka/${{ env.version }} - name: Publish JavaDoc (main) - if: github.ref == 'refs/head/main' + if: github.ref == 'refs/heads/main' uses: JamesIves/github-pages-deploy-action@v4 with: folder: build/dokkaCustomMultiModuleOutput From 2627012a7e94c74a7d752a29f2a1f8d7a40933c0 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 19 Jun 2023 10:51:11 +0200 Subject: [PATCH 080/143] Update dependency org.mockito:mockito-core to v5.4.0 (#1219) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 2630a9b0ed..9a5ba2ec73 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -36,7 +36,7 @@ llvm = { module = "org.bytedeco:llvm-platform", version = "16.0.4-1.5.9"} # test junit-params = { module = "org.junit.jupiter:junit-jupiter-params", version = "5.9.1"} -mockito = { module = "org.mockito:mockito-core", version = "5.3.0"} +mockito = { module = "org.mockito:mockito-core", version = "5.4.0"} # plugins needed for build.gradle.kts in buildSrc kotlin-gradle = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } From 8156fc7f635694fbc45300cb28e5169e333b7a27 Mon Sep 17 00:00:00 2001 From: Tobias Specht Date: Tue, 20 Jun 2023 10:54:13 +0200 Subject: [PATCH 081/143] Add pre-save hook to fix name property of CallExpression (#1220) --- .../aisec/cpg_vis_neo4j/Application.kt | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/cpg-neo4j/src/main/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/Application.kt b/cpg-neo4j/src/main/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/Application.kt index cb615852e9..185fd06ac0 100644 --- a/cpg-neo4j/src/main/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/Application.kt +++ b/cpg-neo4j/src/main/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/Application.kt @@ -28,6 +28,7 @@ package de.fraunhofer.aisec.cpg_vis_neo4j import de.fraunhofer.aisec.cpg.* import de.fraunhofer.aisec.cpg.frontends.CompilationDatabase.Companion.fromFile import de.fraunhofer.aisec.cpg.graph.Node +import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression import de.fraunhofer.aisec.cpg.helpers.Benchmark import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker import de.fraunhofer.aisec.cpg.passes.* @@ -38,6 +39,8 @@ import java.net.ConnectException import java.nio.file.Paths import java.util.concurrent.Callable import kotlin.reflect.KClass +import kotlin.reflect.full.memberProperties +import kotlin.reflect.jvm.javaField import kotlin.system.exitProcess import org.neo4j.driver.exceptions.AuthenticationException import org.neo4j.ogm.config.Configuration @@ -475,9 +478,23 @@ class Application : Callable { } class AstChildrenEventListener : EventListenerAdapter() { + private val nodeNameField = + Node::class + .memberProperties + .first() { it.name == "name" } + .javaField + .also { it?.isAccessible = true } + override fun onPreSave(event: Event?) { val node = event?.`object` as? Node ?: return node.astChildren = SubgraphWalker.getAstChildren(node) + if (node is CallExpression) fixBackingFields(node) + } + + private fun fixBackingFields(node: CallExpression) { + // CallExpression overwrites name property and must be copied to JvmField + // to be visible by Neo4jOGM + nodeNameField?.set(node, node.name) } } From ce847f2b0e10dd89e15d00ee34c8643dcd24e297 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 22 Jun 2023 14:59:56 +0200 Subject: [PATCH 082/143] Update dependency webpack to v5.88.0 (#1221) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- cpg-language-typescript/src/main/nodejs/package.json | 2 +- cpg-language-typescript/src/main/nodejs/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cpg-language-typescript/src/main/nodejs/package.json b/cpg-language-typescript/src/main/nodejs/package.json index 09a3c378bd..b3a9f1d911 100644 --- a/cpg-language-typescript/src/main/nodejs/package.json +++ b/cpg-language-typescript/src/main/nodejs/package.json @@ -11,7 +11,7 @@ }, "license": "Apache-2.0", "devDependencies": { - "webpack": "5.87.0", + "webpack": "5.88.0", "webpack-cli": "5.1.0" } } \ No newline at end of file diff --git a/cpg-language-typescript/src/main/nodejs/yarn.lock b/cpg-language-typescript/src/main/nodejs/yarn.lock index def4827b4a..eb31cf8fa1 100644 --- a/cpg-language-typescript/src/main/nodejs/yarn.lock +++ b/cpg-language-typescript/src/main/nodejs/yarn.lock @@ -772,10 +772,10 @@ webpack-sources@^3.2.3: resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== -webpack@5.87.0: - version "5.87.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.87.0.tgz#df8a9c094c6037f45e0d77598f9e59d33ca3a98c" - integrity sha512-GOu1tNbQ7p1bDEoFRs2YPcfyGs8xq52yyPBZ3m2VGnXGtV9MxjrkABHm4V9Ia280OefsSLzvbVoXcfLxjKY/Iw== +webpack@5.88.0: + version "5.88.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.88.0.tgz#a07aa2f8e7a64a8f1cec0c6c2e180e3cb34440c8" + integrity sha512-O3jDhG5e44qIBSi/P6KpcCcH7HD+nYIHVBhdWFxcLOcIGN8zGo5nqF3BjyNCxIh4p1vFdNnreZv2h2KkoAw3lw== dependencies: "@types/eslint-scope" "^3.7.3" "@types/estree" "^1.0.0" From 182b9ec991e484db543296c2f2fc5e37968c5c27 Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Sat, 24 Jun 2023 10:34:56 +0200 Subject: [PATCH 083/143] Fix `collectInitialPasses` (#1225) fix collectInitialPasses by putting the args of PassWithDependencies in the correct order --- .../de/fraunhofer/aisec/cpg/TranslationConfiguration.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationConfiguration.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationConfiguration.kt index cb3d27218e..a3f9f9135f 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationConfiguration.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationConfiguration.kt @@ -701,8 +701,8 @@ private constructor( workingList.addToWorkingList( PassWithDependencies( p, - hardDependencies[p] ?: mutableSetOf(), - softDependencies[p] ?: mutableSetOf() + softDependencies[p] ?: mutableSetOf(), + hardDependencies[p] ?: mutableSetOf() ) ) } From 95b9b9d71595cdb83ec5e355f77ac6840d5fbaa9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 2 Jul 2023 16:25:42 +0200 Subject: [PATCH 084/143] Update dependency gradle to v8.2 (#1228) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/wrapper/gradle-wrapper.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index fae08049a6..15de90249f 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From b80770b2bf81be93bf71a54338640f77ace0bb1e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 5 Jul 2023 08:36:05 +0200 Subject: [PATCH 085/143] Update module golang.org/x/mod to v0.12.0 (#1229) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- cpg-language-go/src/main/golang/go.mod | 2 +- cpg-language-go/src/main/golang/go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/cpg-language-go/src/main/golang/go.mod b/cpg-language-go/src/main/golang/go.mod index e820295ff5..22bc2aeaf4 100644 --- a/cpg-language-go/src/main/golang/go.mod +++ b/cpg-language-go/src/main/golang/go.mod @@ -3,6 +3,6 @@ module cpg go 1.19 require ( - golang.org/x/mod v0.11.0 + golang.org/x/mod v0.12.0 tekao.net/jnigi v0.0.0-20230402215112-69b87aaf8714 ) diff --git a/cpg-language-go/src/main/golang/go.sum b/cpg-language-go/src/main/golang/go.sum index a4a276172b..8d1114cd60 100644 --- a/cpg-language-go/src/main/golang/go.sum +++ b/cpg-language-go/src/main/golang/go.sum @@ -8,6 +8,8 @@ golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU= golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= tekao.net/jnigi v0.0.0-20220921102452-ce6d0be0c331 h1:p5apvrQZPCacG+Ux6GMzLWX4mUZOPlguj0MrONXutrQ= tekao.net/jnigi v0.0.0-20220921102452-ce6d0be0c331/go.mod h1:SmVvXetJ8N0ov5c2eOC+IxmkdYGEyuXghTuBq5HWZ/Y= tekao.net/jnigi v0.0.0-20221227053512-56e0101fa996 h1:Vl0GEBxRKyS1+/fjd9H6ptV7t/CAmfgxtsanvqsCob8= From e524e19b14b41a562de6b457613ea15ecf831736 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 7 Jul 2023 20:29:27 +0200 Subject: [PATCH 086/143] Update kotlin monorepo to v1.9.0 (#1232) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9a5ba2ec73..7b7e0127c2 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -kotlin = "1.8.0" +kotlin = "1.9.0" neo4j = "4.0.2" log4j = "2.20.0" sonarqube = "4.2.0.3129" From 9dbf4a4f6773745b5d77e6712660bade45038596 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 18 Jul 2023 09:13:49 +0200 Subject: [PATCH 087/143] Update spotless to v6.20.0 (#1239) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7b7e0127c2..b4e31de397 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,7 +3,7 @@ kotlin = "1.9.0" neo4j = "4.0.2" log4j = "2.20.0" sonarqube = "4.2.0.3129" -spotless = "6.19.0" +spotless = "6.20.0" [libraries] kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin"} From 82a29647e59951e3a7a4ef857693af09b998d4c2 Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Tue, 18 Jul 2023 10:11:14 +0200 Subject: [PATCH 088/143] Removing hidden dependency between `yarnInstall` and `spotlessPython` (#1241) Fixes #1240 --- .../src/main/kotlin/cpg.formatting-conventions.gradle.kts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/buildSrc/src/main/kotlin/cpg.formatting-conventions.gradle.kts b/buildSrc/src/main/kotlin/cpg.formatting-conventions.gradle.kts index 9f8c7d874e..4c739ba254 100644 --- a/buildSrc/src/main/kotlin/cpg.formatting-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/cpg.formatting-conventions.gradle.kts @@ -108,6 +108,11 @@ spotless { } python { + targetExclude( + fileTree(project.projectDir) { + include("**/node_modules") + } + ) target("src/main/**/*.py") licenseHeader(headerWithHashes, "from").yearSeparator(" - ") } @@ -116,4 +121,4 @@ spotless { target("src/main/golang/**/*.go") licenseHeader(headerWithStars, "package").yearSeparator(" - ") } -} \ No newline at end of file +} From 1bf8d14fb91b845bed6a61f21757b7abb217b63d Mon Sep 17 00:00:00 2001 From: Konrad Weiss Date: Tue, 18 Jul 2023 12:34:31 +0200 Subject: [PATCH 089/143] Changing visibility to protected for all members of passes (#1242) Changing visibility to protected for all members of passes as this allows to subclass and therefore extend passes --- .../aisec/cpg/passes/CallResolver.kt | 38 +++++++------- .../cpg/passes/ControlFlowSensitiveDFGPass.kt | 20 +++---- .../de/fraunhofer/aisec/cpg/passes/DFGPass.kt | 52 +++++++++---------- .../aisec/cpg/passes/EdgeCachePass.kt | 6 +-- .../cpg/passes/EvaluationOrderGraphPass.kt | 20 +++---- .../aisec/cpg/passes/FilenameMapper.kt | 2 +- .../aisec/cpg/passes/TypeHierarchyResolver.kt | 2 +- .../aisec/cpg/passes/TypeResolver.kt | 2 +- .../aisec/cpg/passes/VariableUsageResolver.kt | 18 +++---- 9 files changed, 80 insertions(+), 80 deletions(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolver.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolver.kt index 729b77a8df..e1322036a0 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolver.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolver.kt @@ -66,7 +66,7 @@ open class CallResolver(ctx: TranslationContext) : SymbolResolverPass(ctx) { * their parent record (more accurately their type). Seems to be only used by * [getOverridingCandidates] and should probably be replaced through a scope manager call. */ - private val containingType = mutableMapOf() + protected val containingType = mutableMapOf() override fun cleanup() { containingType.clear() @@ -96,13 +96,13 @@ open class CallResolver(ctx: TranslationContext) : SymbolResolverPass(ctx) { } } - private fun registerMethods(currentClass: RecordDeclaration?, currentNode: Node?) { + protected fun registerMethods(currentClass: RecordDeclaration?, currentNode: Node?) { if (currentNode is MethodDeclaration && currentClass != null) { containingType[currentNode] = currentNode.parseType(currentClass.name) } } - private fun fixInitializers(node: Node?) { + protected fun fixInitializers(node: Node?) { if (node is VariableDeclaration) { // check if we have the corresponding class for this type val typeString = node.type.root.name @@ -159,7 +159,7 @@ open class CallResolver(ctx: TranslationContext) : SymbolResolverPass(ctx) { } } - private fun handleCallExpression(curClass: RecordDeclaration?, call: CallExpression) { + protected fun handleCallExpression(curClass: RecordDeclaration?, call: CallExpression) { // Function pointers are handled by extra pass, so we are not resolving them here if (call.callee?.type is FunctionPointerType) { return @@ -233,7 +233,7 @@ open class CallResolver(ctx: TranslationContext) : SymbolResolverPass(ctx) { * * In case a resolution is not possible, `null` can be returned. */ - private fun resolveCallee( + protected fun resolveCallee( callee: Expression?, curClass: RecordDeclaration?, call: CallExpression @@ -260,7 +260,7 @@ open class CallResolver(ctx: TranslationContext) : SymbolResolverPass(ctx) { } } - private fun handleArguments(call: CallExpression) { + protected fun handleArguments(call: CallExpression) { val worklist: Deque = ArrayDeque() call.arguments.forEach { worklist.push(it) } while (worklist.isNotEmpty()) { @@ -283,7 +283,7 @@ open class CallResolver(ctx: TranslationContext) : SymbolResolverPass(ctx) { * Resolves a [CallExpression.callee] of type [DeclaredReferenceExpression] to a possible list * of [FunctionDeclaration] nodes. */ - private fun resolveReferenceCallee( + protected fun resolveReferenceCallee( callee: DeclaredReferenceExpression, curClass: RecordDeclaration?, call: CallExpression @@ -365,7 +365,7 @@ open class CallResolver(ctx: TranslationContext) : SymbolResolverPass(ctx) { return invocationCandidates } - private fun retrieveInvocationCandidatesFromCall( + protected fun retrieveInvocationCandidatesFromCall( call: CallExpression, curClass: RecordDeclaration?, possibleContainingTypes: Set @@ -392,7 +392,7 @@ open class CallResolver(ctx: TranslationContext) : SymbolResolverPass(ctx) { * @param possibleContainingTypes * @param call */ - private fun createMethodDummies( + protected fun createMethodDummies( possibleContainingTypes: Set, call: CallExpression ): List { @@ -416,11 +416,11 @@ open class CallResolver(ctx: TranslationContext) : SymbolResolverPass(ctx) { * @param call * @return true if we should stop searching parent, false otherwise */ - private fun shouldSearchForInvokesInParent(call: CallExpression): Boolean { + protected fun shouldSearchForInvokesInParent(call: CallExpression): Boolean { return scopeManager.resolveFunctionStopScopeTraversalOnDefinition(call).isEmpty() } - private fun handleConstructExpression(constructExpression: ConstructExpression) { + protected fun handleConstructExpression(constructExpression: ConstructExpression) { if (constructExpression.instantiates != null && constructExpression.constructor != null) return val typeName = constructExpression.type.name @@ -464,7 +464,7 @@ open class CallResolver(ctx: TranslationContext) : SymbolResolverPass(ctx) { } } - private fun handleExplicitConstructorInvocation(eci: ExplicitConstructorInvocation) { + protected fun handleExplicitConstructorInvocation(eci: ExplicitConstructorInvocation) { eci.containingClass?.let { containingClass -> val recordDeclaration = recordMap[eci.parseName(containingClass)] val signature = eci.arguments.map { it.type } @@ -478,7 +478,7 @@ open class CallResolver(ctx: TranslationContext) : SymbolResolverPass(ctx) { } } - private fun getPossibleContainingTypes(node: Node?): Set { + protected fun getPossibleContainingTypes(node: Node?): Set { val possibleTypes = mutableSetOf() if (node is MemberCallExpression) { node.base?.let { base -> @@ -523,7 +523,7 @@ open class CallResolver(ctx: TranslationContext) : SymbolResolverPass(ctx) { } } - private fun getInvocationCandidatesFromParents( + protected fun getInvocationCandidatesFromParents( name: String?, call: CallExpression, possibleTypes: Set @@ -553,12 +553,12 @@ open class CallResolver(ctx: TranslationContext) : SymbolResolverPass(ctx) { } } - private val Language?.isCPP: Boolean + protected val Language?.isCPP: Boolean get() { return this != null && this::class.simpleName == "CPPLanguage" } - private fun getOverridingCandidates( + protected fun getOverridingCandidates( possibleSubTypes: Set, declaration: FunctionDeclaration ): Set { @@ -572,7 +572,7 @@ open class CallResolver(ctx: TranslationContext) : SymbolResolverPass(ctx) { * @param recordDeclaration matching the class the ConstructExpression wants to construct * @return ConstructorDeclaration that matches the provided signature */ - private fun getConstructorDeclarationDirectMatch( + protected fun getConstructorDeclarationDirectMatch( signature: List, recordDeclaration: RecordDeclaration ): ConstructorDeclaration? { @@ -591,7 +591,7 @@ open class CallResolver(ctx: TranslationContext) : SymbolResolverPass(ctx) { * there is no valid ConstructDeclaration we will create an implicit ConstructDeclaration that * matches the ConstructExpression. */ - private fun getConstructorDeclaration( + protected fun getConstructorDeclaration( constructExpression: ConstructExpression, recordDeclaration: RecordDeclaration ): ConstructorDeclaration { @@ -616,7 +616,7 @@ open class CallResolver(ctx: TranslationContext) : SymbolResolverPass(ctx) { .createInferredConstructor(constructExpression.signature) } - private fun getConstructorDeclarationForExplicitInvocation( + protected fun getConstructorDeclarationForExplicitInvocation( signature: List, recordDeclaration: RecordDeclaration ): ConstructorDeclaration { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt index d800dbe8b8..0eca2ac509 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt @@ -73,7 +73,7 @@ open class ControlFlowSensitiveDFGPass(ctx: TranslationContext) : TranslationUni * Removes all the incoming and outgoing DFG edges for each variable declaration in the block of * code [node]. */ - private fun clearFlowsOfVariableDeclarations(node: Node) { + protected fun clearFlowsOfVariableDeclarations(node: Node) { for (varDecl in node.variables) { varDecl.clearPrevDFG() varDecl.clearNextDFG() @@ -90,7 +90,7 @@ open class ControlFlowSensitiveDFGPass(ctx: TranslationContext) : TranslationUni * - Assignments with an operation e.g. of the form "variable += rhs" * - Read operations on a variable */ - private fun handleStatementHolder(node: Node) { + protected fun handleStatementHolder(node: Node) { // The list of nodes that we have to consider and the last write operations to the different // variables. val worklist = @@ -291,7 +291,7 @@ open class ControlFlowSensitiveDFGPass(ctx: TranslationContext) : TranslationUni * Removes the DFG edges for a potential implicit return statement if it is not in * [reachableReturnStatements]. */ - private fun removeUnreachableImplicitReturnStatement( + protected fun removeUnreachableImplicitReturnStatement( node: Node, reachableReturnStatements: MutableSet ) { @@ -311,7 +311,7 @@ open class ControlFlowSensitiveDFGPass(ctx: TranslationContext) : TranslationUni * was the path through the EOG to reach this state but apparently, all the writes in the * different branches are obsoleted by one common write access which happens afterwards. */ - private fun worklistHasSimilarPair( + protected fun worklistHasSimilarPair( worklist: MutableList>>>, newPair: Pair>> ): Boolean { @@ -372,20 +372,20 @@ open class ControlFlowSensitiveDFGPass(ctx: TranslationContext) : TranslationUni * Checks if the node performs an operation and an assignment at the same time e.g. with the * operators +=, -=, *=, ... */ - private fun isCompoundAssignment(currentNode: Node) = + protected fun isCompoundAssignment(currentNode: Node) = currentNode is BinaryOperator && currentNode.operatorCode in (currentNode.language?.compoundAssignmentOperators ?: setOf()) && (currentNode.lhs as? DeclaredReferenceExpression)?.refersTo != null /** Checks if the node is a simple assignment of the form `var = ...` */ - private fun isSimpleAssignment(currentNode: Node) = + protected fun isSimpleAssignment(currentNode: Node) = currentNode is BinaryOperator && currentNode.operatorCode == "=" && (currentNode.lhs as? DeclaredReferenceExpression)?.refersTo != null /** Checks if the node is an increment or decrement operator (e.g. i++, i--, ++i, --i) */ - private fun isIncOrDec(currentNode: Node) = + protected fun isIncOrDec(currentNode: Node) = currentNode is UnaryOperator && (currentNode.operatorCode == "++" || currentNode.operatorCode == "--") && (currentNode.input as? DeclaredReferenceExpression)?.refersTo != null @@ -397,7 +397,7 @@ open class ControlFlowSensitiveDFGPass(ctx: TranslationContext) : TranslationUni * * @return true if a loop was detected, false otherwise */ - private fun loopDetection( + protected fun loopDetection( currentNode: Node, writtenDecl: Declaration?, currentWritten: Node, @@ -437,7 +437,7 @@ open class ControlFlowSensitiveDFGPass(ctx: TranslationContext) : TranslationUni * Copies the map. We remove all the declarations which are no longer relevant because they are * in a child scope of the next hop. */ - private fun copyMap( + protected fun copyMap( map: Map>, nextNode: Node ): MutableMap> { @@ -456,7 +456,7 @@ open class ControlFlowSensitiveDFGPass(ctx: TranslationContext) : TranslationUni return result } - private fun Node.hasOuterScopeOf(node: Node): Boolean { + protected fun Node.hasOuterScopeOf(node: Node): Boolean { var parentScope = node.scope?.parent while (parentScope != null) { if (this.scope == parentScope) { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/DFGPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/DFGPass.kt index 6e9ab553d9..2ca7531daa 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/DFGPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/DFGPass.kt @@ -55,7 +55,7 @@ class DFGPass(ctx: TranslationContext) : ComponentPass(ctx) { // Nothing to do } - private fun handle(node: Node?, parent: Node?, inferDfgForUnresolvedSymbols: Boolean) { + protected fun handle(node: Node?, parent: Node?, inferDfgForUnresolvedSymbols: Boolean) { when (node) { // Expressions is CallExpression -> handleCallExpression(node, inferDfgForUnresolvedSymbols) @@ -90,7 +90,7 @@ class DFGPass(ctx: TranslationContext) : ComponentPass(ctx) { } } - private fun handleAssignExpression(node: AssignExpression) { + protected fun handleAssignExpression(node: AssignExpression) { // If this is a compound assign, we also need to model a dataflow to the node itself if (node.isCompoundAssignment) { node.lhs.firstOrNull()?.let { @@ -117,7 +117,7 @@ class DFGPass(ctx: TranslationContext) : ComponentPass(ctx) { * For a [MemberExpression], the base flows to the expression if the field is not implemented in * the code under analysis. Otherwise, it's handled as a [DeclaredReferenceExpression]. */ - private fun handleMemberExpression( + protected fun handleMemberExpression( node: MemberExpression, inferDfgForUnresolvedCalls: Boolean ) { @@ -132,7 +132,7 @@ class DFGPass(ctx: TranslationContext) : ComponentPass(ctx) { * Adds the DFG edge for a [VariableDeclaration]. The data flows from initializer to the * variable. */ - private fun handleVariableDeclaration(node: VariableDeclaration) { + protected fun handleVariableDeclaration(node: VariableDeclaration) { node.initializer?.let { node.addPrevDFG(it) } } @@ -140,14 +140,14 @@ class DFGPass(ctx: TranslationContext) : ComponentPass(ctx) { * Adds the DFG edge for a [FunctionDeclaration]. The data flows from the return statement(s) to * the function. */ - private fun handleFunctionDeclaration(node: FunctionDeclaration) { + protected fun handleFunctionDeclaration(node: FunctionDeclaration) { node.allChildren().forEach { node.addPrevDFG(it) } } /** * Adds the DFG edge for a [FieldDeclaration]. The data flows from the initializer to the field. */ - private fun handleFieldDeclaration(node: FieldDeclaration) { + protected fun handleFieldDeclaration(node: FieldDeclaration) { node.initializer?.let { node.addPrevDFG(it) } } @@ -155,7 +155,7 @@ class DFGPass(ctx: TranslationContext) : ComponentPass(ctx) { * Adds the DFG edge for a [ReturnStatement]. The data flows from the return value to the * statement. */ - private fun handleReturnStatement(node: ReturnStatement) { + protected fun handleReturnStatement(node: ReturnStatement) { node.returnValues.forEach { node.addPrevDFG(it) } } @@ -167,7 +167,7 @@ class DFGPass(ctx: TranslationContext) : ComponentPass(ctx) { * unwrap the [VariableDeclaration]. If this is not the case, we assume that the last * [VariableDeclaration] in the statement is the one we care about. */ - private fun handleForEachStatement(node: ForEachStatement) { + protected fun handleForEachStatement(node: ForEachStatement) { node.iterable?.let { iterable -> if (node.variable is DeclarationStatement) { (node.variable as DeclarationStatement).declarations.forEach { @@ -184,7 +184,7 @@ class DFGPass(ctx: TranslationContext) : ComponentPass(ctx) { * Adds the DFG edge from [ForEachStatement.variable] to the [ForEachStatement] to show the * dependence between data and the branching node. */ - private fun handleDoStatement(node: DoStatement) { + protected fun handleDoStatement(node: DoStatement) { node.condition?.let { node.addPrevDFG(it) } } @@ -193,7 +193,7 @@ class DFGPass(ctx: TranslationContext) : ComponentPass(ctx) { * [ForStatement] to show the dependence between data and the branching node. Usage of one or * the other in the statement is mutually exclusive. */ - private fun handleForStatement(node: ForStatement) { + protected fun handleForStatement(node: ForStatement) { Util.addDFGEdgesForMutuallyExclusiveBranchingExpression( node, node.condition, @@ -206,7 +206,7 @@ class DFGPass(ctx: TranslationContext) : ComponentPass(ctx) { * [IfStatement] to show the dependence between data and the branching node. Usage of one or the * other in the statement is mutually exclusive. */ - private fun handleIfStatement(node: IfStatement) { + protected fun handleIfStatement(node: IfStatement) { Util.addDFGEdgesForMutuallyExclusiveBranchingExpression( node, node.condition, @@ -219,7 +219,7 @@ class DFGPass(ctx: TranslationContext) : ComponentPass(ctx) { * the [SwitchStatement] to show the dependence between data and the branching node. Usage of * one or the other in the statement is mutually exclusive. */ - private fun handleSwitchStatement(node: SwitchStatement) { + protected fun handleSwitchStatement(node: SwitchStatement) { Util.addDFGEdgesForMutuallyExclusiveBranchingExpression( node, node.selector, @@ -232,7 +232,7 @@ class DFGPass(ctx: TranslationContext) : ComponentPass(ctx) { * the [WhileStatement] to show the dependence between data and the branching node. Usage of one * or the other in the statement is mutually exclusive. */ - private fun handleWhileStatement(node: WhileStatement) { + protected fun handleWhileStatement(node: WhileStatement) { Util.addDFGEdgesForMutuallyExclusiveBranchingExpression( node, node.condition, @@ -244,7 +244,7 @@ class DFGPass(ctx: TranslationContext) : ComponentPass(ctx) { * Adds the DFG edges for an [UnaryOperator]. The data flow from the input to this node and, in * case of the operators "++" and "--" also from the node back to the input. */ - private fun handleUnaryOperator(node: UnaryOperator) { + protected fun handleUnaryOperator(node: UnaryOperator) { node.input.let { node.addPrevDFG(it) if (node.operatorCode == "++" || node.operatorCode == "--") { @@ -257,7 +257,7 @@ class DFGPass(ctx: TranslationContext) : ComponentPass(ctx) { * Adds the DFG edge for a [LambdaExpression]. The data flow from the function representing the * lambda to the expression. */ - private fun handleLambdaExpression(node: LambdaExpression) { + protected fun handleLambdaExpression(node: LambdaExpression) { node.function?.let { node.addPrevDFG(it) } } @@ -265,7 +265,7 @@ class DFGPass(ctx: TranslationContext) : ComponentPass(ctx) { * Adds the DFG edges for an [KeyValueExpression]. The value flows to this expression. TODO: * Check with python and JS implementation */ - private fun handleKeyValueExpression(node: KeyValueExpression) { + protected fun handleKeyValueExpression(node: KeyValueExpression) { node.value?.let { node.addPrevDFG(it) } } @@ -275,7 +275,7 @@ class DFGPass(ctx: TranslationContext) : ComponentPass(ctx) { * * TODO: This change seems to have performance issues! */ - private fun handleInitializerListExpression(node: InitializerListExpression) { + protected fun handleInitializerListExpression(node: InitializerListExpression) { node.initializers.forEach { it.registerTypeListener(node) node.addPrevDFG(it) @@ -286,7 +286,7 @@ class DFGPass(ctx: TranslationContext) : ComponentPass(ctx) { * Adds the DFG edge to an [ExpressionList]. The data of the last expression flow to the whole * list. */ - private fun handleExpressionList(node: ExpressionList) { + protected fun handleExpressionList(node: ExpressionList) { node.expressions.lastOrNull()?.let { node.addPrevDFG(it) } } @@ -294,7 +294,7 @@ class DFGPass(ctx: TranslationContext) : ComponentPass(ctx) { * Adds the DFG edge to an [NewExpression]. The data of the initializer flow to the whole * expression. */ - private fun handleNewExpression(node: NewExpression) { + protected fun handleNewExpression(node: NewExpression) { node.initializer?.let { node.addPrevDFG(it) } } @@ -304,7 +304,7 @@ class DFGPass(ctx: TranslationContext) : ComponentPass(ctx) { * - If the variable is read from, data flows from the variable declaration to this node. * - For a combined read and write, both edges for data flows are added. */ - private fun handleDeclaredReferenceExpression(node: DeclaredReferenceExpression) { + protected fun handleDeclaredReferenceExpression(node: DeclaredReferenceExpression) { node.refersTo?.let { when (node.access) { AccessValues.WRITE -> node.addNextDFG(it) @@ -321,7 +321,7 @@ class DFGPass(ctx: TranslationContext) : ComponentPass(ctx) { * Adds the DFG edge to a [ConditionalExpression]. Data flows from the then and the else * expression to the whole expression. */ - private fun handleConditionalExpression(node: ConditionalExpression) { + protected fun handleConditionalExpression(node: ConditionalExpression) { node.thenExpr?.let { node.addPrevDFG(it) } node.elseExpr?.let { node.addPrevDFG(it) } } @@ -330,14 +330,14 @@ class DFGPass(ctx: TranslationContext) : ComponentPass(ctx) { * Adds the DFG edge to an [ArraySubscriptionExpression]. The whole array `x` flows to the * result `x[i]`. */ - private fun handleArraySubscriptionExpression(node: ArraySubscriptionExpression) { + protected fun handleArraySubscriptionExpression(node: ArraySubscriptionExpression) { node.addPrevDFG(node.arrayExpression) } /** * Adds the DFG edge to an [ArrayCreationExpression]. The initializer flows to the expression. */ - private fun handleArrayCreationExpression(node: ArrayCreationExpression) { + protected fun handleArrayCreationExpression(node: ArrayCreationExpression) { node.initializer?.let { node.addPrevDFG(it) } } @@ -345,7 +345,7 @@ class DFGPass(ctx: TranslationContext) : ComponentPass(ctx) { * Adds the DFG edge to an [BinaryOperator]. The value flows to the target of an assignment or * to the whole expression. */ - private fun handleBinaryOp(node: BinaryOperator, parent: Node?) { + protected fun handleBinaryOp(node: BinaryOperator, parent: Node?) { when (node.operatorCode) { "=" -> { node.rhs.let { node.lhs.addPrevDFG(it) } @@ -375,7 +375,7 @@ class DFGPass(ctx: TranslationContext) : ComponentPass(ctx) { /** * Adds the DFG edge to a [CastExpression]. The inner expression flows to the cast expression. */ - private fun handleCastExpression(castExpression: CastExpression) { + protected fun handleCastExpression(castExpression: CastExpression) { castExpression.expression.let { castExpression.addPrevDFG(it) } } @@ -406,7 +406,7 @@ class DFGPass(ctx: TranslationContext) : ComponentPass(ctx) { * - from base (if available) to the CallExpression * - from all arguments to the CallExpression */ - private fun handleUnresolvedCalls(call: CallExpression, dfgTarget: Node) { + protected fun handleUnresolvedCalls(call: CallExpression, dfgTarget: Node) { if (call is MemberCallExpression && !call.isStatic) { call.base?.let { dfgTarget.addPrevDFG(it) } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EdgeCachePass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EdgeCachePass.kt index 88ebf574b6..bc15bb638a 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EdgeCachePass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EdgeCachePass.kt @@ -102,14 +102,14 @@ class EdgeCachePass(ctx: TranslationContext) : ComponentPass(ctx) { } } - private fun visitAST(n: Node) { + protected fun visitAST(n: Node) { for (node in SubgraphWalker.getAstChildren(n)) { val edge = Edge(n, node, EdgeType.AST) Edges.add(edge) } } - private fun visitDFG(n: Node) { + protected fun visitDFG(n: Node) { for (dfg in n.prevDFG) { val edge = Edge(dfg, n, EdgeType.DFG) Edges.add(edge) @@ -121,7 +121,7 @@ class EdgeCachePass(ctx: TranslationContext) : ComponentPass(ctx) { } } - private fun visitEOG(n: Node) { + protected fun visitEOG(n: Node) { for (eog in n.prevEOG) { val edge = Edge(eog, n, EdgeType.EOG) Edges.add(edge) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt index 9c837fcb1c..ff9e6b3349 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt @@ -71,21 +71,21 @@ import org.slf4j.LoggerFactory @DependsOn(CallResolver::class) open class EvaluationOrderGraphPass(ctx: TranslationContext) : TranslationUnitPass(ctx) { protected val map = mutableMapOf, (Node) -> Unit>() - private var currentPredecessors = mutableListOf() - private val nextEdgeProperties = EnumMap(Properties::class.java) + protected var currentPredecessors = mutableListOf() + protected val nextEdgeProperties = EnumMap(Properties::class.java) /** * Allows to register EOG creation logic when a currently visited node can depend on future * visited nodes. Currently used to connect goto statements and the target labeled statements. * Implemented as listener to connect nodes when the goto appears before the label. */ - private val processedListener = ProcessedListener() + protected val processedListener = ProcessedListener() /** * Stores all nodes currently handled to add them to the processedListener even if a sub node is * the next target of an EOG edge. */ - private val intermediateNodes = mutableListOf() + protected val intermediateNodes = mutableListOf() init { map[IncludeDeclaration::class.java] = { doNothing() } @@ -160,7 +160,7 @@ open class EvaluationOrderGraphPass(ctx: TranslationContext) : TranslationUnitPa map[LambdaExpression::class.java] = { handleLambdaExpression(it as LambdaExpression) } } - private fun doNothing() { + protected fun doNothing() { // Nothing to do for this node type } @@ -178,7 +178,7 @@ open class EvaluationOrderGraphPass(ctx: TranslationContext) : TranslationUnitPa * Removes EOG edges by first building the negative set of nodes that cannot be visited and then * remove there outgoing edges.In contrast to truncateLooseEdges this also removes cycles. */ - private fun removeUnreachableEOGEdges(tu: TranslationUnitDeclaration) { + protected fun removeUnreachableEOGEdges(tu: TranslationUnitDeclaration) { val eognodes = SubgraphWalker.flattenAST(tu) .filter { it.prevEOG.isNotEmpty() || it.nextEOG.isNotEmpty() } @@ -359,7 +359,7 @@ open class EvaluationOrderGraphPass(ctx: TranslationContext) : TranslationUnitPa * Tries to create the necessary EOG edges for the [node] (if it is non-null) by looking up the * appropriate handler function of the node's class in [map] and calling it. */ - private fun createEOG(node: Node?) { + protected fun createEOG(node: Node?) { if (node == null) { // nothing to do return @@ -768,7 +768,7 @@ open class EvaluationOrderGraphPass(ctx: TranslationContext) : TranslationUnitPa * @param prev the previous node * @param next the next node */ - private fun addEOGEdge(prev: Node, next: Node) { + protected fun addEOGEdge(prev: Node, next: Node) { val propertyEdge = PropertyEdge(prev, next) propertyEdge.addProperties(nextEdgeProperties) propertyEdge.addProperty(Properties.INDEX, prev.nextEOG.size) @@ -777,7 +777,7 @@ open class EvaluationOrderGraphPass(ctx: TranslationContext) : TranslationUnitPa next.addPrevEOG(propertyEdge) } - private fun addMultipleIncomingEOGEdges(prevs: List, next: Node) { + protected fun addMultipleIncomingEOGEdges(prevs: List, next: Node) { prevs.forEach { prev -> addEOGEdge(prev, next) } } @@ -943,7 +943,7 @@ open class EvaluationOrderGraphPass(ctx: TranslationContext) : TranslationUnitPa } companion object { - private val LOGGER = LoggerFactory.getLogger(EvaluationOrderGraphPass::class.java) + protected val LOGGER = LoggerFactory.getLogger(EvaluationOrderGraphPass::class.java) /** * Searches backwards in the EOG on whether there is a path from a function declaration to diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/FilenameMapper.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/FilenameMapper.kt index 03746a283b..228523783d 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/FilenameMapper.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/FilenameMapper.kt @@ -40,7 +40,7 @@ class FilenameMapper(ctx: TranslationContext) : TranslationUnitPass(ctx) { handle(tu, file) } - private fun handle(node: Node, file: String) { + protected fun handle(node: Node, file: String) { // Using a visitor to avoid loops in the AST node.accept( Strategy::AST_FORWARD, diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TypeHierarchyResolver.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TypeHierarchyResolver.kt index a8ce5ba3db..2ab2fffb06 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TypeHierarchyResolver.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TypeHierarchyResolver.kt @@ -95,7 +95,7 @@ open class TypeHierarchyResolver(ctx: TranslationContext) : ComponentPass(ctx) { ) } - private fun getAllMethodsFromSupertypes( + protected fun getAllMethodsFromSupertypes( supertypeRecords: Set ): List { return supertypeRecords.map { it.methods }.flatten() 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 5a0d702e97..b970f07d75 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 @@ -71,7 +71,7 @@ open class TypeResolver(ctx: TranslationContext) : ComponentPass(ctx) { * In the other case the parameter type is stored into the map and the parameter type is * returned */ - private fun obtainType(type: Type): Type { + protected fun obtainType(type: Type): Type { return if (type.root == type && type in typeState) { typeState.keys.first { it == type } } else { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/VariableUsageResolver.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/VariableUsageResolver.kt index b9155175f5..20249d3e8b 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/VariableUsageResolver.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/VariableUsageResolver.kt @@ -87,7 +87,7 @@ open class VariableUsageResolver(ctx: TranslationContext) : SymbolResolverPass(c } } - private fun resolveFunctionPtr(reference: DeclaredReferenceExpression): ValueDeclaration? { + protected fun resolveFunctionPtr(reference: DeclaredReferenceExpression): ValueDeclaration? { // Without FunctionPointerType, we cannot resolve function pointers val fptrType = reference.type as? FunctionPointerType ?: return null @@ -104,7 +104,7 @@ open class VariableUsageResolver(ctx: TranslationContext) : SymbolResolverPass(c ) } - private fun resolveLocalVarUsage( + protected fun resolveLocalVarUsage( currentClass: RecordDeclaration?, parent: Node?, current: Node? @@ -193,7 +193,7 @@ open class VariableUsageResolver(ctx: TranslationContext) : SymbolResolverPass(c * We get the type of the "scope" this node is in. (e.g. for a field, we drop the field's name * and have the class) */ - private fun getEnclosingTypeOf(current: Node): Type { + protected fun getEnclosingTypeOf(current: Node): Type { val language = current.language return if (language != null && language.namespaceDelimiter.isNotEmpty()) { @@ -204,7 +204,7 @@ open class VariableUsageResolver(ctx: TranslationContext) : SymbolResolverPass(c } } - private fun resolveFieldUsages(curClass: RecordDeclaration?, parent: Node?, current: Node?) { + protected fun resolveFieldUsages(curClass: RecordDeclaration?, parent: Node?, current: Node?) { if (current !is MemberExpression) { return } @@ -292,7 +292,7 @@ open class VariableUsageResolver(ctx: TranslationContext) : SymbolResolverPass(c current.refersTo = resolveMember(baseType, current) } - private fun resolveBase(reference: DeclaredReferenceExpression): Declaration? { + protected fun resolveBase(reference: DeclaredReferenceExpression): Declaration? { val declaration = scopeManager.resolveReference(reference) if (declaration != null) { return declaration @@ -308,7 +308,7 @@ open class VariableUsageResolver(ctx: TranslationContext) : SymbolResolverPass(c } } - private fun resolveMember( + protected fun resolveMember( containingClass: Type, reference: DeclaredReferenceExpression ): ValueDeclaration? { @@ -342,7 +342,7 @@ open class VariableUsageResolver(ctx: TranslationContext) : SymbolResolverPass(c } // TODO(oxisto): Move to inference class - private fun handleUnknownField(base: Type, name: Name, type: Type): FieldDeclaration? { + protected fun handleUnknownField(base: Type, name: Name, type: Type): FieldDeclaration? { // unwrap a potential pointer-type if (base is PointerType) { return handleUnknownField(base.elementType, name, type) @@ -399,7 +399,7 @@ open class VariableUsageResolver(ctx: TranslationContext) : SymbolResolverPass(c * resulting function/method has the signature and return type specified in [fctPtrType] and the * specified [name]. */ - private fun handleUnknownFunction( + protected fun handleUnknownFunction( declarationHolder: RecordDeclaration?, name: Name, fctPtrType: FunctionPointerType @@ -429,6 +429,6 @@ open class VariableUsageResolver(ctx: TranslationContext) : SymbolResolverPass(c } companion object { - private val log = LoggerFactory.getLogger(VariableUsageResolver::class.java) + protected val log = LoggerFactory.getLogger(VariableUsageResolver::class.java) } } From 6a96078d92e42cb8e894da7d80648d2270bed202 Mon Sep 17 00:00:00 2001 From: KuechA <31155350+KuechA@users.noreply.github.com> Date: Tue, 18 Jul 2023 15:46:35 +0200 Subject: [PATCH 090/143] Add more documentation (#1218) * Document shortcuts * Integrate shortcuts page * API reference - not yet automated * Merge files and remove duplicates, change links * CLI stuff * Small fix * Use dokka versioning plugin * Playing around a bit * Added GitHub Actions magic * Remove main to be sure * latest -> main --------- Co-authored-by: Christian Banse --- .github/workflows/build.yml | 12 +- README.md | 9 +- build.gradle.kts | 42 +- cpg-core/specifications/dfg.md | 589 -------------------- cpg-core/specifications/eog.md | 770 -------------------------- cpg-core/specifications/language.md | 33 -- docs/docs/API/index.md | 13 + docs/docs/CPG/specs/dfg.md | 114 ++++ docs/docs/GettingStarted/cli.md | 49 ++ docs/docs/GettingStarted/index.md | 2 + docs/docs/GettingStarted/library.md | 4 +- docs/docs/GettingStarted/query.md | 4 +- docs/docs/GettingStarted/shortcuts.md | 114 ++++ docs/docs/dokka/main/.gitkeep | 0 docs/mkdocs.yaml | 4 +- gradle/libs.versions.toml | 1 + 16 files changed, 356 insertions(+), 1404 deletions(-) delete mode 100644 cpg-core/specifications/dfg.md delete mode 100644 cpg-core/specifications/eog.md delete mode 100644 cpg-core/specifications/language.md create mode 100644 docs/docs/API/index.md create mode 100644 docs/docs/GettingStarted/shortcuts.md create mode 100644 docs/docs/dokka/main/.gitkeep diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index de7030ee02..52f970e20a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -111,6 +111,14 @@ jobs: with: name: libcpgo-amd64.dylib path: cpg-language-go/src/main/resources/ + - name: Download old dokka versions + run: | + # make sure the previousDocs folder exists + mkdir previousDocs && cd previousDocs + # retrieve the previous documentation folders for each published version (this also includes "main") + wget -O - https://github.com/Fraunhofer-AISEC/cpg/archive/gh-pages.tar.gz | tar -xz --strip=2 cpg-gh-pages/dokka + # in order to avoid duplicate mains, remove the "main" version from the previous versions + rm -rf main - name: Build ${{ env.version }} run: | if [ "$SONAR_TOKEN" != "" ] @@ -148,13 +156,13 @@ jobs: if: startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, 'beta') && !contains(github.ref, 'alpha') uses: JamesIves/github-pages-deploy-action@v4 with: - folder: build/dokkaCustomMultiModuleOutput + folder: build/dokkaCustomMultiModuleOutput/${{ env.version }} target-folder: dokka/${{ env.version }} - name: Publish JavaDoc (main) if: github.ref == 'refs/heads/main' uses: JamesIves/github-pages-deploy-action@v4 with: - folder: build/dokkaCustomMultiModuleOutput + folder: build/dokkaCustomMultiModuleOutput/main target-folder: dokka/main - name: "Create Release" if: startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, 'beta') && !contains(github.ref, 'alpha') diff --git a/README.md b/README.md index 75fe8b7820..5f2719894d 100644 --- a/README.md +++ b/README.md @@ -14,11 +14,12 @@ This library uses [Eclipse CDT](https://www.eclipse.org/cdt/) for parsing C/C++ ## Specifications In order to improve some formal aspects of our library, we created several specifications of our core concepts. Currently, the following specifications exist: -* [Dataflow Graph](./cpg-core/specifications/dfg.md) -* [Evaluation Order Graph](./cpg-core/specifications/eog.md) -* [Language and Language Frontend](./cpg-core/specifications/language.md) +* [Dataflow Graph](https://fraunhofer-aisec.github.io/cpg/CPG/specs/dfg/) +* [Evaluation Order Graph](https://fraunhofer-aisec.github.io/cpg/CPG/specs/eog/) +* [Graph Model in neo4j](https://fraunhofer-aisec.github.io/cpg/CPG/specs/graph/) +* [Language and Language Frontend](https://fraunhofer-aisec.github.io/cpg/CPG/impl/language/) -We aim to provide more specifications over time and also include them in a new generated documentation site. +We aim to provide more specifications over time. ## Usage diff --git a/build.gradle.kts b/build.gradle.kts index 0c05e80b60..4ea7d6b3f5 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,3 +1,7 @@ +import org.jetbrains.dokka.gradle.DokkaMultiModuleTask +import java.io.BufferedOutputStream +import java.io.ByteArrayOutputStream + /* * Copyright (c) 2019-2021, Fraunhofer AISEC. All rights reserved. * @@ -37,12 +41,48 @@ repositories { mavenCentral() } +allprojects { + plugins.apply("org.jetbrains.dokka") + + val dokkaPlugin by configurations + dependencies { + dokkaPlugin("org.jetbrains.dokka:versioning-plugin:1.8.10") + } +} + // configure dokka for the multi-module cpg project // this works together with the dokka configuration in the common-conventions plugin tasks.dokkaHtmlMultiModule { - outputDirectory.set(buildDir.resolve("dokkaCustomMultiModuleOutput")) + val stdout = ByteArrayOutputStream() + exec { + commandLine("sh", "-c", "git tag --points-at HEAD | cat") + standardOutput = stdout + } + val configuredVersion = stdout.toString() + if(configuredVersion.isNotEmpty()) { + generateDokkaWithVersionTag(this, configuredVersion) + } else { + generateDokkaWithVersionTag(this, "main") + } +} + +/** + * Takes the old dokka sites in build/dokkaCustomMultiModuleOutput/versions and generates a new site. + * This new site contains the old ones, so copying the newly generated site to the gh page is enough. + * Currently, the mkdocs plugin expects it in docs/dokka/latest. The tags in the dropdown will be + * named based on what we configured here. + */ +fun generateDokkaWithVersionTag(dokkaMultiModuleTask: org.jetbrains.dokka.gradle.AbstractDokkaParentTask, tag: String) { + val oldOutputPath = projectDir.resolve("previousDocs") + val id = "org.jetbrains.dokka.versioning.VersioningPlugin" + val config = """{ "version": "$tag", "olderVersionsDir":"${oldOutputPath.path}" }""" + val mapOf = mapOf(id to config) + + dokkaMultiModuleTask.outputDirectory.set(file(buildDir.resolve("dokkaCustomMultiModuleOutput").resolve(tag))) + dokkaMultiModuleTask.pluginsMapConfiguration.set(mapOf) } + // // Configure sonarqube for the whole cpg project // diff --git a/cpg-core/specifications/dfg.md b/cpg-core/specifications/dfg.md deleted file mode 100644 index 77fc8e727e..0000000000 --- a/cpg-core/specifications/dfg.md +++ /dev/null @@ -1,589 +0,0 @@ -# Specification: Data Flow Graph - -The Data Flow Graph (DFG) is built as edges between nodes. Each node has a set of incoming data flows (`prevDFG`) and outgoing data flows (`nextDFG`). In the following, we summarize how different types of nodes construct the respective data flows. - - -## CallExpression - -Interesting fields: - -* `invokes: List`: A list of the functions which are called -* `arguments: List`: The arguments which are used in the function call - -A call expressions calls another function. We differentiate two types of call expressions: 1) the called function is implemented in the program (and we can analyze the code) and 2) the called function cannot be analyzed (e.g., this is the case for library/API functions). For the first case, the `invokes` list contains values, in the second case, the list is empty. - -### Case 1: Known function - -For each function in the `invokes` list, the arguments of the call expression flow to the function's parameters. The value of the function declaration flows to the call. - -Scheme: - ```mermaid - flowchart LR - node([CallExpression]) -.- invokes["invokes[j]"]; - node -.- arguments["arguments[i]"]; - invokes ==> decl([FunctionDeclaration]) - decl -.- parameters["parameters[i]"] - arguments -- "for all i: DFG" --> parameters - invokes -- "forall j: DFG" --> node - ``` - -### Case 2: Unknown function - -The base and all arguments flow to the call expression. - -Scheme: - ```mermaid - flowchart LR - arguments["arguments[i]"] -- "for all i: DFG" --> node([CallExpression]); - base -- DFG --> node; - arguments -.- node; - node -.- base; - ``` - -## CastExpression - -Interesting fields: - -* `expression: Expression`: The inner expression which has to be casted - -The value of the `expression` flows to the cast expression. -Scheme: -```mermaid - flowchart LR - node([CastExpression]) -.- expression; - expression -- DFG --> node; -``` - -## BinaryOperator - -Interesting fields: - -* `operatorCode: String`: String representation of the operator -* `lhs: Expression`: The left-hand side of the operation -* `rhs: Expression`: The right-hand side of the operation - -We have to differentiate between the operators. We can group them into three categories: 1) Assignment, 2) Assignment with a Computation and 3) Computation. - -### Case 1: Assignment (`operatorCode: =`) - -The `rhs` flows to `lhs`. In some languages, it is possible to have an assignment in a subexpression (e.g. `a + (b=1)`). -For this reason, if the assignment's ast parent is not a `CompoundStatement` (i.e., a block of statements), we also add a DFG edge to the whole operator. - -Scheme: -```mermaid -flowchart LR - node([BinaryOperator]) -.- rhs(rhs); - rhs -- DFG --> lhs; - node([BinaryOperator]) -.- lhs(lhs); -``` - -```mermaid -flowchart LR - node([BinaryOperator]) -.- lhs(lhs); - node([BinaryOperator]) -.- rhs(rhs); - rhs -- DFG --> lhs; - rhs -- DFG --> node; -``` - - ```mermaid - flowchart LR - A[binaryOperator.rhs] -- DFG --> binaryOperator.lhs; - subgraph S[If the ast parent is not a CompoundStatement] - direction LR - binaryOperator.rhs -- DFG --> binaryOperator; - end - A --> S; - ``` - - -### Case 2: Assignment with a Computation (`operatorCode: *=, /=, %=, +=, -=, <<=, >>=, &=, ^=, |=` ) - -The `lhs` and the `rhs` flow to the binary operator expression, the binary operator flows to the `lhs`. - -Scheme: - ```mermaid - flowchart LR - node([BinaryOperator]) -.- lhs(lhs); - node([BinaryOperator]) -.- rhs(rhs); - lhs -- DFG --> node; - rhs -- DFG --> node; - node == DFG ==> lhs; - ``` - -*Dangerous: We have to ensure that the first two operations are performed before the last one* - - -### Case 3: Computation - -The `lhs` and the `rhs` flow to the binary operator expression. - -Scheme: - ```mermaid - flowchart - node([BinaryOperator]) -.- lhs(lhs); - node([BinaryOperator]) -.- rhs(rhs); - rhs -- DFG --> node; - lhs -- DFG --> node; - ``` - -## ArrayCreationExpression - -Interesting fields: - -* `initializer: Expression`: The initialization values of the array. - -The `initializer` flows to the array creation expression. - -Scheme: - ```mermaid - flowchart LR - node([ArrayCreationExpression]) -.- initializer(initializer) - initializer -- DFG --> node - ``` - -## NewExpression - -Interesting fields: -* `initializer: Expression`: The initializer of the expression. - -The `initializer` flows to the whole expression. - -Scheme: - ```mermaid - flowchart LR - node([NewExpression]) -.- initializer(initializer) - initializer -- DFG --> node - ``` - - -## ArraySubscriptionExpression - -Interesting fields: - -* `arrayExpression: Expression`: The array which is accessed -* `subscriptExpression: Expression`: The index which is accessed - -The `arrayExpression` flows to the subscription expression. This means, we do not differentiate between the field which is accessed. - -Scheme: - ```mermaid - flowchart LR - arrayExpression -- DFG --> node([ArraySubscriptionExpression]); - arrayExpression -.- node; - ``` - - -## ConditionalExpression - -Interesting fields: - -* `condition: Expression`: The condition which is evaluated -* `thenExpr: Expression`: The expression which is executed if the condition holds -* `elseExpr: Expression`: The expression which is executed if the condition does not hold - -The `thenExpr` and the `elseExpr` flow to the `ConditionalExpression`. This means that implicit data flows are not considered. - -Scheme: - ```mermaid - flowchart LR - thenExpr -- DFG --> node([ConditionalExpression]); - thenExpr -.- node; - elseExpr -.- node; - elseExpr -- DFG --> node; - ``` - -## DeclaredReferenceExpression - -Interesting fields: - -* `refersTo: Declaration`: The declaration e.g. of the variable or symbol -* `access: AccessValues`: Determines if the value is read from, written to or both - -This is the most tricky concept for the DFG edges. We have to differentiate between the DFG edges generated by the `DFGPass` and the ones generated by the `ControlFlowSensitiveDFGPass`. - -The `DFGPass` generates very simple edges based on the access to the variable as follows: - -* The value flows from the declaration to the expression for read access. Scheme: - ```mermaid - flowchart LR - refersTo -- DFG --> node([DeclaredReferenceExpression]); - refersTo -.- node; - ``` -* For write access, data flow from the expression to the declaration. Scheme: - ```mermaid - flowchart LR - node([DeclaredReferenceExpression]) -- DFG --> refersTo; - node -.- refersTo; - ``` -* For readwrite access, both flows are present. Scheme: - ```mermaid - flowchart LR - refersTo -- DFG 1 --> node([DeclaredReferenceExpression]); - refersTo -.- node; - node -- DFG 2 --> refersTo; - ``` - -This mostly serves one purpose: The current function pointer resolution requires such flows. Once the respective passes are redesigned, we may want to update this. - -The `ControlFlowSensitiveDFGPass` completely changes this behavior and accounts for the data flows which differ depending on the program's control flow (e.g., different assignments to a variable in an if and else branch, ...). The pass performs the following actions: - -* First, it clears all the edges between a `VariableDeclaration` and its `DeclaredReferenceExpression`. Actually, it clears all incoming and outgoing DFG edges of all VariableDeclarations in a function. This includes the initializer but this edge is restored right away. Scheme: - ```mermaid - flowchart LR - node([VariableDeclaration]) -.- initializer; - initializer -- DFG --> node; - ``` -* For each read access to a DeclaredReferenceExpression, it collects all potential previous assignments to the variable and adds these to the incoming DFG edges. You can imagine that this is done by traversing the EOG backwards until finding the first assignment to the variable for each possible path. Scheme: - ```mermaid - flowchart LR - node([DeclaredReferenceExpression]) -.- refersTo; - A == last write to ==> refersTo; - A[/Node/] -- DFG --> node; - ``` -* If we increment or decrement a variable with "++" or "--", the data of this statement flows from the previous writes of the variable to the input of the statement (= the DeclaredReferenceExpression). We write back to this reference and consider the lhs as a "write" to the variable! *Attention: This potentially adds loops and can look like a branch. Needs to be handled with care in subsequent passes/analyses!* Scheme: - ```mermaid - flowchart LR - node([UnaryOperator]) -.- input; - input -.- |"(optional)"| refersTo; - W -- DFG 1 --> input; - W[/Node/] == last write to ==> refersTo; - input -- DFG 2 --> node; - node -- DFG 3 --> input; - input -- DFG 4 --> R[/Node/]; - R == next read of ==> refersTo; - ``` -* For compound operators such as `+=, -=, *=, /=`, we have an incoming flow from the last writes to reference on the left hand side of the expression to the lhs. The lhs then flows to the whole expression. Also, the right hand side flows to the whole expression (if it's a read, this is processed separately). The data flows back to the lhs which is marked as the last write to the variable. *Attention: This potentially adds loops and can look like a branch. Needs to be handled with care in subsequent passes/analyses!* - ```mermaid - flowchart LR - node -.- rhs; - node -.- lhs; - lhs -.- refersTo; - W -- DFG 1 --> lhs; - W[/Node/] == last write to ==> refersTo; - rhs -- DFG 2 --> node; - lhs -- DFG 4 --> R; - lhs -- DFG 2 --> node([BinaryOperator]); - node -- DFG 3 --> lhs; - R[/Node/] == next read of ==> refersTo; - ``` -* If the variable is assigned a value (a binary operator `var = rhs`), the right hand side flows to the variable. This is considered as a write operation. - ```mermaid - flowchart LR - node -.- rhs; - node -.- lhs; - lhs -.- refersTo; - lhs -- DFG 2 --> node([BinaryOperator]); - R[/Node/] == next read of ==> refersTo; - rhs -- DFG --> lhs; - lhs -- DFG --> refersTo - ``` - -## MemberExpression - -Interesting fields: - -* `base: Expression`: The base object whose field is accessed -* `refersTo: Declaration?`: The field it refers to. If the class is not implemented in the code under analysis, it is `null`. - -The MemberExpression represents an access to an object's field and extends a DeclaredReferenceExpression with a `base`. - -If an implementation of the respective class is available, we handle it like a normal DeclaredReferenceExpression. -If the `refersTo` field is `null` (i.e., the implementation is not available), base flows to the expression. - -## ExpressionList - -Interesting fields: - -* `expressions: List` - -The data of the last statement in `expressions` flows to the expression. - -## InitializerListExpression - -Interesting fields: - -* `initializers: List`: The list of expressions which initialize the values. - -The data of all initializers flow to this expression. - -Scheme: -```mermaid - flowchart LR - inits["for all i: initializers[i]"] -- DFG --> node([InitializerListExpression]); - node -.- inits; -``` - -## KeyValueExpression - -Interesting fields: - -* `value: Expression`: The value which is assigned. - -The value flows to this expression. - -Scheme: -```mermaid - flowchart LR - value -- DFG --> node([KeyValueExpression]); - value -.- node; -``` - - -## LambdaExpression - -Interesting fields: - -* `function: FunctionDeclaration`: The usage of a lambda - -The data flow from the function representing the lambda to the expression. - -Scheme: -```mermaid - flowchart LR - function -- DFG --> node([LambdaExpression]); - function -.- node; -``` - -## UnaryOperator - -Interesting fields: - -* `input: Expression`: The inner expression -* `operatorCode: String`: A string representation of the operation - -The data flow from the input to this node and, in case of the operatorCodes ++ and -- also back from the node to the input. - -```mermaid - flowchart TD - node1([UnaryOperator]) -.- operator - operator ==> cmp - - cmp == "operator == '++' || - operator == '--'" ==> incdec; - - cmp == "operator != '++' && - operator != '--'" ==> finish[ ]; - - subgraph finish[ ] - node2([UnaryOperator]) -.- input2; - input2 -.- |"(optional)"| refersTo2; - W2[/Node/] == last write to ==> refersTo2; - W2 -- DFG 1 --> input2[input]; - input2 -- DFG 2 --> node2; - end - - subgraph incdec[ ] - node([UnaryOperator]) -.- input; - input -.- |"(optional)"| refersTo; - W[/Node/] == last write to ==> refersTo; - W -- DFG 1 --> input; - input -- DFG 2 --> node; - node -- DFG 3 --> input; - input -- DFG 4 --> R[/Node/]; - R == next read of ==> refersTo; - end -``` - -*Dangerous: We have to ensure that the first operation is performed before the last one (if applicable)* - - -## ReturnStatement - -Interesting fields: - -* `returnValue: Expression`: The value which is returned - -The return value flows to the whole statement. - -Scheme: -```mermaid - flowchart LR - returnValue -- DFG --> node([ReturnStatement]); - returnValue -.- node; -``` - -## Branching Statements -Specific statements lead to a branch in the control flow of a program. A value that influences the branching decision can lead to an implicit data flow via the branching and we therefore draw a dfg edge from the condition, to the branching node. - -### ForEachStatement - -Interesting fields: - -* `variable: Statement`: The statement which is used in each iteration to assign the current iteration value. -* `iterable: Statement`: The statement or expression, which is iterated - -The value of the iterable flow to the `VariableDeclaration` in the `variable`. Since some languages allow arbitrary logic, we differentiate between two cases: - -### Case 1. The `variable` is a `DeclarationStatement`. - -This is the case for most languages where we can have only a variable in this place (e.g., `for(e in list)`). Here, we get the declaration(s) in the statement and add the DFG from the iterable to this declaration. - - -Scheme: -```mermaid - flowchart LR - node([ForEachStatement]) -.- variable[variable: DeclarationStatement] - node -.- iterable[iterable] - variable -.- declarations["declarations[i]"] - iterable -- for all i: DFG --> declarations - ``` - -### Case 2. The `variable` is another type of `Statement`. - -In this case, we assume that the last VariableDeclaration is the one used for looping. We add a DFG edge only to this declaration. - -Scheme: -```mermaid - flowchart LR - node([ForEachStatement]) -.- statement[variable] - node -.- iterable[iterable] - statement -.- localVars[variables] - localVars -. "last" .-> variable - iterable -- DFG --> variable - variable -- DFG --> node - ``` - -### DoStatement - -Interesting fields: -* `condition: Statement`: The condition that is evaluated before making the branching decision - - -Scheme: -```mermaid - flowchart LR - node([DoStatement]) -.- condition(condition) - condition -- DFG --> node - ``` - - -### WhileStatement - -Interesting fields: -* `condition: Statement`: The condition that is evaluated before making the branching decision -* `conditionDeclaration: Statement`: A declaration containing the condition in the initializer, used instead of the condition - -Scheme: -```mermaid - flowchart LR - node([WhileStatement]) -.- condition(condition) - node -.- conditionDeclaration(conditionDeclaration) - condition -- DFG --> node - conditionDeclaration -- DFG --> node - ``` - - -### ForStatement - -Interesting fields: -* `condition: Statement`: The condition that is evaluated before making the branching decision -* `conditionDeclaration: Statement`: A declaration containing the condition in the initializer, used instead of the condition. - -Scheme: -```mermaid - flowchart LR - node([ForStatement]) -.- condition(condition) - node -.- conditionDeclaration(conditionDeclaration) - condition -- DFG --> node - conditionDeclaration -- DFG --> node - ``` - - -### IfStatement - -Interesting fields: -* `condition: Statement`: The condition that is evaluated before making the branching decision -* `conditionDeclaration: Statement`: A declaration containing the condition in the initialize, used instead of the condition. - -Scheme: -```mermaid - flowchart LR - node([IfStatement]) -.- condition(condition) - node -.- conditionDeclaration(conditionDeclaration) - condition -- DFG --> node - conditionDeclaration -- DFG --> node - ``` - - -### SwitchStatement -Interesting fields: -* `selector: Statement`: The expression that is evaluated before making the branching decision -* `selectorDeclaration: Statement`: A declaration containing the selector in the initializer, used instead of the selector. - -Scheme: -```mermaid - flowchart LR - node([SwitchStatement]) -.- selector(selector) - node -.- selectorDeclaration(selectorDeclaration) - selector -- DFG --> node - selectorDeclaration -- DFG --> node - ``` -## FunctionDeclaration - -Interesting fields: - -* `body: Expression`: The body (i.e., all statements) of the function implementation - -The values of all return expressions in the body flow to the function declaration. - -Scheme: -```mermaid - flowchart LR - returns -- DFG --> node([FunctionDeclaration]); - body -.- node; - body -.- |in all statements| returns["returns: ReturnStatement"] -``` - - -## FieldDeclaration - -Interesting fields: - -* `initializer: Expression?`: The value which is used to initialize a field (if applicable). - -The value of the initializer flows to the whole field. - -In addition, all writes to a reference to the field (via a `DeclaredReferenceExpression`) flow to the field, for all reads, data flow to the reference. - -Scheme: -```mermaid - flowchart LR - initializer -- DFG --> node([FieldDeclaration]); - initializer -.- node; - node -- DFG --> R[/Node/]; - R == next read of ==> node; -``` - -## VariableDeclaration - -Interesting fields: - -* `initializer: Expression?`: The value which is used to initialize a variable (if applicable). - -The value of the initializer flows to the variable declaration. The value of the variable declarations flows to all `DeclaredReferenceExpressions` which read the value before the value of the variable is written to through another reference to the variable. - -Scheme: -```mermaid - flowchart LR - initializer -- DFG --> node([VariableDeclaration]); - initializer -.- node; - node -- DFG --> R[/Node/]; - R == next read of ==> node; -``` - -## Assignment - -Interesting fields: - -* `value: Expression`: The rhs of the assignment -* `target: AssignmentTarget`: The lhs of the assignment - -This should already be covered by the declarations and binary operator "=". If not, the `value` flows to the `target` - -Scheme: -```mermaid - flowchart LR - value -.- node([Assignment]); - target -.- node; - value -- DFG --> target; -``` diff --git a/cpg-core/specifications/eog.md b/cpg-core/specifications/eog.md deleted file mode 100644 index b8eaa53dcd..0000000000 --- a/cpg-core/specifications/eog.md +++ /dev/null @@ -1,770 +0,0 @@ -# Specification: Evaluation Order Graph - -The Evaluation Order Graph (EOG) is built as edges between AST nodes after the initial translation of the code to the CPG. -Its purpose is to follow the order in which code is executed, similar to a CFG, and additionally differentiate on a finer level of granularity in which order expressions and subexpressions are evaluated. -Every node points to a set of previously evaluated nodes (`prevEOG`) and nodes that are evaluated after (`nextEOG`). -The EOG edges are intraprocedural and thus differentiate from INVOKES edges. -In the following, we summarize in which order the root node representing a language construct and its descendants in the AST tree are connected. - -An EOG always starts at root node representing a method/function or record that holds executable code and ends in the node representing the corresponding code or multiple return statements. -An implicit return statement with a code location of (-1,-1) is used if the actual source code does not have an explicit return statement. - -A distinct EOG is drawn for any declared component that can contain code, currently: `NamespaceDeclaration`, `TranslationUnitDeclaration`, `RecordDeclaration` and any subclass of `FunctionDeclaration`. - -The EOG is similar to a CFG which connects basic blocks of statements, but there are some subtle differences: - -* For methods without explicit return statement, the EOG will have an edge to a virtual return node with line number -1 which does not exist in the original code. - A CFG will always end with the last reachable statement(s) and not insert any virtual return statements. -* The EOG considers an opening blocking (`CompoundStatement`, indicated by a `{`) as a separate node. - A CFG will rather use the first actual executable statement within the block. -* For IF statements, the EOG treats the `if` keyword and the condition as separate nodes. - A CFG treats this as one `if` statement. -* The EOG considers a method header as a node. - A CFG will consider the first executable statement of the methods as a node. - -## General Structure - -The graphs in this specification abstract the representation of the handled graph, to formally specify how EOG edges are drawn between a parent node and the subgraphs rooted by its children. -Therefore, a collection of AST children are represented as abstract nodes showing the multiplicity of the node with an indicator (n), in case of sets, or as several nodes showing how the position in a list can impact the construction of an EOG, e.g., nodes (i - 1) to i. -The EOG is constructed as postorder of the AST traversal. -When building the EOG for the expression a + b, the entire expression is considered evaluated after the subexpression a and the subexpression b is evaluated, therefore EOG edges connect nodes of (a) and (b) before reaching the parent node (+). - -Note: Nodes describing the titled programing construct will be drawn round, while the rectangular nodes represent their abstract children, that can be atomic leaf nodes or deep AST subtrees. -EOG edges to these abstract nodes always mean that a subtree expansion would be necessary to connect the target of the EOG edge to the right node in the subtree. - -```mermaid -flowchart LR - classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; - prev:::outer --EOG--> lhs - node --EOG--> next:::outer - node([+]) -.-> lhs["a"] - node -.-> rhs["b"] - lhs --EOG--> rhs - rhs --EOG--> node -``` - -Whether a subgraph (a) or (b) is connected first, depends on the exact construct and sometimes the language that is translated into a CPG. -Note that, in the following graphics we will often draw an EOG edge to an abstract child node of a language construct that is an AST subtree. -The EOG path through that subtree will depend on the node types of that tree and mostly start connecting one of the AST leaf nodes. - -## FunctionDeclaration -A function declaration is the start of an intraprocedural EOG and contains its end. Therefore there is no incoming or outgoing edge to `previous` or `next` eog nodes that are not in its AST subtree. The EOG connects the code body, as well as the default values of parameters if they exist. - -Interesting fields: - -* `parameters: List`: The parameters of the function. -* `defaultValue: Expression`: Optional default values of the parameters that have to be evaluated before executing the function's body. -* `body: Statement`: One or multiple statements executed when this function is called. - -Scheme: -```mermaid -flowchart LR - classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; - parent(["FunctionDeclaration"]) --EOG-->child1["defaultValue(i-1)"] - child1 --EOG-->child2["defaultValue(i)"] - child2 --EOG--> body - parent -.-> body - parent -."parameters(n)".->child3["parameter(i-1)"] -.->child1 - parent -."parameters(n)".->child4["parameter(i)"] -.->child2 -``` - -## StatementHolder -StatementHolder is an interface for any node that is not a function and contains code that should be connected with an EOG. The following classes implement this interface: `NamespaceDeclaration`, `TranslationUnitDeclaration`, `RecordDeclaration` and `CompoundStatement`. The Node implementing the interface is the start of one or multiple EOGs. Note that code inside such a holder can be static or non static (bound to an instance of a record). Therefore, two separate EOGs may be built. - -Interesting fields: - -* `statements: List`: The code inside a holder. The individual elements are distinguished by a property marking them as `staticBlock` if they are a `CompoundStatement`. - -Scheme: -```mermaid -flowchart LR - classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; - holder([StatementHolder])-."statements(n)".->sblock1["StaticStatement(i-1)"] - holder([StatementHolder])-."statements(n)".->sblock2["StaticStatement(i)"] - holder-."statements(n)".->nblock1["NonStaticStatement(i-1)"] - holder-."statements(n)".->nblock2["NonStaticStatement(i)"] - holder--EOG-->sblock1 - sblock1--EOG-->sblock2 - holder--EOG-->nblock1 - nblock1--EOG-->nblock2 -``` - -## VariableDeclaration -Represents the declaration of a local variable. - -Interesting fields: - -* `initializer: Expression`: The result of evaluation will initialize the variable. - -Scheme: -```mermaid -flowchart LR - classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; - prev:::outer --EOG--> child - parent(["VariableDeclaration"]) --EOG--> next:::outer - parent -.-> child["initializer"] - child --EOG--> parent - -``` - -## CallExpression -Represents any type of call in a program. - -Interesting fields: - -* `callee: Expression`: The expression declaring the target of a call. This can be a base in a `MemberCallExpression` or a function pointer in a `CallExpression`or a reference. -* `arguments: List`: Mapped to the parameters of the call target but evaluated before the call happens. - -Scheme: -```mermaid -flowchart LR - classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; - prev:::outer --EOG--> child["callee"] - parent(["CallExpression"]) --EOG--> next:::outer - child --EOG--> arg1["Argument(i-1)"] - arg1--EOG--> arg2["Argument(i)"] - arg2["Argument(i)"] --EOG--> parent - parent -.-> child - parent -."arguments(n)".-> arg1 - parent -."arguments(n)".-> arg2 -``` - -## MemberExpression -Access to the field in a `RecordDeclaration`. - -Interesting fields: - -* `base: Expression`: The base evaluated to determine whose field we want to access. - -Scheme: -```mermaid -flowchart LR - classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; - prev:::outer --EOG--> child - parent(["MemberExpression"]) --EOG--> next:::outer - parent -.-> child["base"] - child --EOG--> parent -``` - -## ArraySubscriptionExpression -Array access in the form of `arrayExpression[subscriptExpression]`. - -Interesting fields: - -* `arrayExpression: Expression`: The array to be accessed. -* `subscriptExpression: Expression`: The index in the array. - -Scheme: -```mermaid -flowchart LR - classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; - prev:::outer --EOG--> child - child --EOG--> child2["subscriptExpression"] - parent(["ArraySubscriptionExpression"]) --EOG--> next:::outer - parent -.-> child["arrayExpression"] - parent -.-> child2 - child2 --EOG--> parent -``` - -## ArrayCreationExpression -Interesting fields: - -* `dimensions: List`: Multiple expressions that define the array's dimensions. -* `initializer: Expression`: The expression for array initialization. - -Scheme: -```mermaid -flowchart LR - classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; - prev:::outer --EOG--> child1["dimension(i-1)"] - child1 --EOG--> child2["dimension(i)"] - child2 --EOG--> initializer - parent(["ArrayCreationExpression"]) --EOG--> next:::outer - parent -.-> child1 - parent -.-> child2 - parent -.-> initializer - initializer --EOG--> parent -``` - -## KeyValueExpression -Represents a key / value pair that could be used in associative arrays, among others. - -Interesting fields: - -* `key: Expression`: The key used for later accessing this pair. -* `value: Expression`: The value of the pair. - -Scheme: -```mermaid -flowchart LR - classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; - prev:::outer --EOG--> child - child --EOG--> child2["value"] - parent(["KeyValueExpression"]) --EOG--> next:::outer - parent -.-> child["key"] - parent -.-> child2 - child2 --EOG--> parent -``` - -## DeclarationStatement - -Here, the EOG is only drawn to the child component if that component is a VariableDeclaration, not if it is a FunctionDeclaration. - -```mermaid -flowchart LR - classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; - prev:::outer --EOG--> child - parent(["DeclarationStatement"]) --EOG--> next:::outer - parent -.-> child(["VariableDeclaration"]) - child --EOG--> parent - -``` -## ReturnStatement -This forms the end of an EOG as this is the last statement to be executed in the function. - -Scheme: -```mermaid -flowchart LR - classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; - prev:::outer --EOG--> child - child["returnValue"] --EOG--> parent(["ReturnStatement"]) - parent -.-> child -``` - -## BinaryOperator - -Interesting fields: - -* `lhs: Expression`: Left hand side of a binary operation. -* `rhs: Expression`: Right hand side of a binary operation. -* `operatorCode: String`: The operation. - -We differentiate between two cases based on the `operatorCode`. - -### Short-circuit evaluation - -The operations `&&` and `||` have a short-circuit evaluation. This means that the expression can terminate early if the `lhs` is false (for `&&`) or `true` (for `||`). This affects the EOG by adding an EOG edge from `lhs` to the BinaryOperator. - -Scheme: -```mermaid -flowchart LR - classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; - prev:::outer --EOG--> lhs - node --EOG--> next:::outer - node([BinaryOperator]) -.-> lhs - node -.-> rhs - lhs --EOG--> rhs - lhs --EOG--> node - rhs --EOG--> node -``` -### Default case - -For the other binary operations like `+`, `-` but also assignments `=` and `+=` we follow the left before right order. The `lhs` is evaluated before the `rhs` as we assume left to right evaluation. - -Scheme: -```mermaid -flowchart LR - classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; - prev:::outer --EOG--> lhs - node --EOG--> next:::outer - node([BinaryOperator]) -.-> lhs - node -.-> rhs - lhs --EOG--> rhs - rhs --EOG--> node -``` - - -## CompoundStatement - -Represents an explicit block of statements. - -Interesting fields: - -* `statements:List`: Statements in a block of code that are evaluated sequentially. - -Scheme: -```mermaid -flowchart LR - classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; - prev:::outer --EOG--> child1["statement(i-1)"] - child1 --EOG-->child2["statement(i)"] - parent(["CompoundStatement"]) --EOG--> next:::outer - parent -."statements(n)".-> child1 - parent -."statements(n)".-> child2 - child2 --EOG--> parent - -``` - -## UnaryOperator -For unary operations like `!` but also writing operations: `++` and `--`. - -Interesting fields: - -* `input:Expression`: Wrapped by the unary operation. - -Scheme: -```mermaid -flowchart LR - classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; - prev:::outer --EOG--> child["input"] - child --EOG-->parent - parent(["UnaryOperator"]) --EOG--> next:::outer - parent -."statements(n)".-> child - -``` - - -### UnaryOperator for exception throws -Throwing of exceptions is modelled as unary operation. The EOG continues at an exception catching structure or a function that does a re-throw. - -Interesting fields: -* `input: Expression`: Exception to be thrown for exception handling. - -Scheme: -```mermaid -flowchart LR - classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; - prev:::outer --EOG--> child["input"] - child --EOG-->parent - parent(["throw"]) --EOG--> catchingContext:::outer - parent -."statements(n)".-> child - -``` - - -## AssertStatement -Statement that evaluates a condition and if the condition is false, evaluates a message, this message is generalized to a `Statement` to hold everything -from a single String, to an Exception construction. - -Interesting fields: - -* `condition: Expression` Its evaluation leads to evaluation of message and EOG termination or to the regular evaluation of the parent `AssertStatement`. -* `message: Statement`: A String message or Exception evaluated only if the assertion fails. - -Scheme: -```mermaid -flowchart LR - classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; - prev:::outer --EOG--> child1["condition"] - child1 --"EOG:false"-->child2["message"] - child1 --"EOG:true"-->parent - parent([AssertStatement]) --EOG--> next:::outer - parent -.-> child1 - parent -.-> child2 - -``` - -## TryStatement - -After the execution of the statement the control flow only proceeds with the next statement if all exceptions were handled. If not, execution is relayed to the next outer exception handling context. - -Interesting fields: - -* `resources:List`: Initialization of values needed in the block or special objects needing cleanup. -* `tryBlock:CompoundStatement`: The code that should be tried, exceptions inside lead to an eog edge to the catch clauses. -* `finallyBlock:CompoundStatement`: All EOG paths inside the `tryBlock` or the `catch` blocks will finally reach this block and evaluate it. -* `catchBlocks:List`: Children of `CatchClause` (omitted here), evaluated when the exception matches the clauses condition. - -Scheme: -```mermaid -flowchart LR - classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; - prev:::outer -- EOG --> resource["resource(i-1)"]; - resource -.- parent - resource -- EOG --> resourceI["resource(i)"] - resourceI -.- parent - resourceI -- EOG --> try["tryBlock"] - try -.- parent - throws:::outer -- EOG --> catch["catchBlock(i)"] - try -- EOG --> finally["finallyBlock"] - parent([TryStatement]) --EOG--> next:::outer - parent --EOG--> catchingContext:::outer - catch -- EOG --> finally - finally -- EOG --> parent - finally -.- parent - catch -.- parent -``` - -## ContinueStatement -The execution continues at the `condition` of a node associated to a `Continuable` scope, e.g. `WhileStatement`. This is not necessarily the closest enclosing node of this type, the `ContinueStatement` may contain a label specifying the exact outer node. - -Scheme: -```mermaid -flowchart LR - classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; - prev:::outer --EOG--> parent - parent(["ContinueStatement"]) --EOG--> conditionInContinuableContext:::outer - -``` -## BreakStatement -The execution continues after a node associated to a `Breakable` scope, e.g. `WhileStatement`or `SwitchStatement`. This is not necessarily the closest enclosing node of this type, the `BreakStatement` may contain a label specifying the exact outer node. - -Scheme: -```mermaid -flowchart LR - classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; - prev:::outer --EOG--> parent - parent(["BreakStatement"]) --EOG--> nextAfterBreakableContext:::outer -``` - -## DeleteExpression -Deletion of a specific object freeing memory or calling the destructor. - -Interesting fields: - -* `operand: Expression`: The result of the evaluation is the object to be deleted. - -Scheme: -```mermaid -flowchart LR - classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; - prev:::outer --EOG--> child["operand"] - child --EOG--> parent - parent(["DeleteExpression"]) --EOG--> next:::outer - parent -.-> child -``` - -## LabelStatement -The `LabelStatement` itself is not added to the EOG. EOG construction is directly forwarded to the labeled statement in the `subStatement`. - -Scheme: -```mermaid -flowchart LR - classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; - prev:::outer --EOG--> child["subStatement"] - child --EOG--> next:::outer - parent(["LabelStatement"]) -.-> child -``` - -## GotoStatement -Models a `goto`statement and an EOG-Edge is created to the appropriate `LabelStatement`. - -Scheme: -```mermaid -flowchart LR - classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; - prev:::outer --EOG--> child(["GotoStatement"]) - child --EOG--> labeledStatement:::outer -``` - -## NewExpression -Creates a new object, which is either an array or an instantiation of a `RecordDeclaration`. The initializer has to be evaluated to create the object. - -Interesting fields: - -* `initializer: Expression`: To be evaluated before creating a new object. - -* Scheme: -```mermaid -flowchart LR - classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; - prev:::outer --EOG--> child["initializer"] - child --EOG--> parent - parent(["NewExpression"]) --EOG--> next:::outer - parent -.-> child -``` - -## CastExpression -Interesting fields: - -* `expression: Expression`: An expression of a specific compile time type, cast to a specified other compile time type. - -Scheme: -```mermaid -flowchart LR - classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; - prev:::outer --EOG--> child["expression"] - child --EOG--> parent - parent(["CastExpression"]) --EOG--> next:::outer - parent -.-> child -``` - -## ExpressionList -List of several expressions that aer evaluated sequentially. The resulting value is the last evaluated expression. - -Interesting fields: - -* `expressions: List`: Several expressions in sequential order. - -Scheme: -```mermaid -flowchart LR - classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; - prev:::outer --EOG--> child1["expression(i-1)"] - child1 --EOG--> child2["expression(i)"] - child2 --EOG--> parent - parent(["ExpressionList"]) --EOG--> next:::outer - parent -."expressions(n)".-> child1 - parent -."expressions(n)".-> child2 -``` - -## InitializerListExpression -This expression initializes multiple variables or an object of multiple elements, e.g. arrays, lists. - -Scheme: -```mermaid -flowchart LR - classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; - prev:::outer --EOG--> child1["initializer(i-1)"] - child1 --EOG--> child2["initializer(i)"] - child2 --EOG--> parent - parent(["InitializerListExpression"]) --EOG--> next:::outer - parent -."initializers(n)".-> child1 - parent -."initializers(n)".-> child2 -``` - -## ConstructExpression -A ConstructExpression creates an object. - -Interesting fields: - -* `arguments: List`: Arguments to the construction, e.g. arguments for a call to a constructor. - -Scheme: -```mermaid -flowchart LR - classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; - prev:::outer --EOG--> child1["argument(i-1)"] - child1 --EOG--> child2["argument(i)"] - child2 --EOG--> parent - parent(["ConstructExpression"]) --EOG--> next:::outer - parent -."arguments(n)".-> child1 - parent -."arguments(n)".-> child2 -``` - -## SynchronizedStatement -The placement of the root node between expression and executed block is such that algorithms can be evaluated the expression and then encountering the information that this expression is used for synchronization. - -Interesting fields: - -* `expression: Expression`: Its evaluation returns an object that acts as a lock for synchronization. -* `blockStatement: CompoundStatement`: Code executed while the object evaluated from `expression` is locked. - -Scheme: -```mermaid -flowchart LR - classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; - prev:::outer --EOG--> child1["expression"] - child1 --EOG--> parent - parent --EOG--> child2["blockStatement"] - child2 --EOG--> next:::outer - parent -.-> child1 - parent -.-> child2 -``` - -## ConditionalExpression -A conditional evaluation of two expression, realizing the branching pattern of an `IfStatement` on the expression level. - -Interesting fields: - -* `condition:Expression`: Executed first to decide the branch of evaluation. -* `thenExpr:Expression`: Evaluated if `condition` evaluates to `true.` -* `elseExpr:Expression`: Evaluated if `condition` evaluates to `false.` - -Scheme: -```mermaid -flowchart LR - classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; - prev:::outer --EOG--> child1["condition"] - child1 --EOG--> parent(["ConditionalExpression"]) - parent --EOG:true--> child2["thenExpr"] - parent --EOG:false--> child3["elseExpr"] - child2 --EOG--> next:::outer - child3 --EOG--> next:::outer - parent -.-> child1 - parent -.-> child2 - parent -.-> child3 -``` - -## WhileStatement -This is a classic while loop where the condition is evaluated before every loop iteration. - -Note: The condition may be enclosed in a declaration, in that case the EOG will not contain a `condition` but rather a declaration of a variable where the `initializer` serves as loop condition. Uses of one or the other are currently mutually exclusive. - -Interesting fields: - -* `condition: Expression`: The condition for the loop. -* `conditionDeclaration: Declaration`: The declaration of a variable with condition as initializer. -* `statement: Statement`: The body of the loop to be iterated over. - -Scheme: -```mermaid -flowchart LR - classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; - prev:::outer --EOG--> child1["condition|conditionDeclaration"] - child1 --EOG--> parent - parent --EOG:false--> next:::outer - parent(["WhileStatement"]) --EOG:true--> child3["statement"] - child3 --EOG--> child1 - parent -.-> child1 - parent -.-> child3 -``` - -## DoStatement -This is a classic do while loop where the condition is evaluated after every loop iteration. - -Interesting fields: - -* `condition: Expression`: The condition of the loop. -* `statement: Statement`: The body of the loop to be iterated over. - -Scheme: -```mermaid -flowchart LR - classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; - prev:::outer --EOG--> child1["statement"]; - child1 --EOG--> child2["condition"]; - child2 --EOG--> parent(["DoStatement"]); - parent --EOG:false--> next:::outer - parent --EOG:true--> child1 - parent -.-> child1 - parent -.-> child2 -``` - -## ForEachStatement -This is a loop that iterates over all elements in a multi-element `iterable` with the single elements bound to the declaration of `variable` while evaluating `statement`. - -Interesting fields: - -* `iterable: Statement`: Elements of this iterable will trigger a loop iteration. -* `variable: Statement`: Variable declaring Statement that binds elements to a name. -* `statement: Statement`: Loop body to be iterated over. - -Scheme: -```mermaid -flowchart LR - classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; - prev:::outer --EOG--> child1["iterable"] - child1 --EOG--> child2["variable"] - child2 --EOG--> parent - parent --EOG:false--> next:::outer - parent(["ForEachStatement"]) --EOG:true--> child3["statement"] - child3 --EOG--> child1 - parent -.-> child2 - parent -.-> child1 - parent -.-> child3 -``` - -## ForStatement -This is a classic for-loop where a statement is executed before the loop run, a condition is evaluated before every loop iteration, and a post iteration statement can be declared. - -Note: The condition may be enclosed in a declaration. In this case, the EOG will not contain a `condition` but rather a declaration of a variable where the `initializer` serves as loop condition. Uses of one or the other are currently mutually exclusive. - -Interesting fields: - -* `initializerStatement:Statement`: Statement run once, before the loop starts. -* `condition: Expression`: The condition of the loop. -* `conditionDeclaration: Declaration`: The declaration of a variable with the condition as initializer. -* `statement: Statement`: The body of the loop to be iterated over. -* `iterationStatement: Statement`: The statement to be executed after each loop iteration. - -Scheme: -```mermaid -flowchart LR - classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; - iteration --EOG--> condition - statement --EOG--> iteration["iterationStatement"] - prev:::outer --EOG--> initializer["initializerStatement"] - parent --EOG:false--> next:::outer - initializer --EOG--> condition["condition|conditionDeclaration"] - condition --EOG--> parent - parent(["ForStatement"]) --EOG:true--> statement["statement"] -``` - -## IfStatement -This is a branching statement where the evaluation of a `condition` leads to the execution of one optional, or two mutually exclusive blocks of code. - -Note: The condition may be enclosed in a declaration, in that case the EOG will not contain a `condition` but rather a declaration of a variable where the `initializer` serves as branching condition. Uses of one or the other are currently mutually exclusive. - -Interesting fields: - -* `condition: Expression`: The condition of the branching decision. -* `conditionDeclaration: Declaration`: The declaration of a variable with condition as initializer. -* `thenStatement: Statement`: The body of the mandatory block that is evaluated if the `condition` evaluates to `true`. -* `elseStatement: Statement`: The body of an optional block that is evaluated if the `condition` evaluates to `false`. - -Scheme: -```mermaid -flowchart LR - classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; - prev:::outer --EOG--> child1["initializerStatement"] - child1 --EOG--> child2["condition|conditionDeclaration"] - child2 --EOG--> parent - parent(["IfStatement"]) --EOG:true--> child4["thenStatement"] - parent --EOG:false--> child5["elseStatement"] - parent --EOG--> next:::outer - child4 --EOG--> next:::outer - child5 --EOG--> next:::outer -``` - -## SwitchStatement -This is a switch statement where the evaluation of a `selector` decides the entry point in a large block of code. `CaseStatements` serve as entry points and `BreakStatements` are needed to prevent all cases after the entry to be evaluated. - -Note: The `selector` may be enclosed in a declaration. In this case, the EOG will not contain a selector but rather a declaration of a variable where the `initializer` serves as switch selector. Uses of one or the other are currently mutually exclusive. - -Interesting fields: - -* `selector: Expression`: The evaluated selector which needs to match the expression evaluation of the expression in a `caseStatement` or the entry will be the `defaultStatement`. -* `selectorDeclaration: Declaration`: The declarations `initializer` serves as `selector`. -* `statement: Statement`: The body containing all entry points and statements to be executed. -* `caseStatement: Statement`: The entry point into the evaluation of the switch body if the `selector` matches its `caseExpression`. -* `defaultStatement: Statement`: The default entry point if no `caseExpression` matched the selector. - -Scheme: -```mermaid -flowchart LR - classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; - prev:::outer --EOG--> child1["initializerStatement"] - child1 --EOG--> child2["selector|selectorDeclaration"] - child2 --EOG--> parent - parent(["SwitchStatement"]) --EOG--> child4["caseStatement"] - parent --EOG--> child5["defaultStatement"] - child7["statement(n-1)"] --EOG--> child6["statement"] - parent -.->child6 - child6 -."statements(n)".-> child4 - child6 -."statements(n)".-> child5 - child6 -."statements(n)".-> child7 - child6 --EOG--> next:::outer -``` - -## CaseStatement - -Serves as an entry point inside a `SwitchStatement`, the statements executed after entry are not children of this structure but can be found on the same AST hierarchy level. - -Interesting fields: - -* `caseExpression: Expression`: serves as an entry point if its evaluation matches the `selector` evaluation in `SwitchStatement` - -Scheme: -```mermaid -flowchart LR - classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; - prev:::outer --EOG--> child["caseExpression"] - child --EOG--> parent - parent(["CaseStatement"]) --EOG--> next:::outer - parent -.-> child - -``` -## LambdaExpression -The expression itself is connected to the outer EOG. A separate EOG is built for the expressed code, as the code itself is not executed at this point. - -Interesting fields: - -* `function: FunctionDeclaration`: The function declared by the lambda that can be executed at different points in the program. - -Scheme: -```mermaid -flowchart LR - classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; - prev:::outer --EOG--> parent["LambdaExpression"] - parent --EOG--> next:::outer - parent -.-> child - child(["function"]) --EOG-->internalNext:::outer - -``` - - - - - - diff --git a/cpg-core/specifications/language.md b/cpg-core/specifications/language.md deleted file mode 100644 index b672be2dd7..0000000000 --- a/cpg-core/specifications/language.md +++ /dev/null @@ -1,33 +0,0 @@ -# Specification: Language and Language Frontends - -Even though we are aiming for a language-independent representation of source code, we still need to parse source code depending on the original programming language used. Therefore, we are introduce two concepts that help developers and users to understand how the CPG translates language-specific code into an abstract form. - -## `Language` - -The first concept is a `Language`. It represents the programming language as a general concept and contains meta-information about it. This includes: -* The name of the language, e.g. C++ -* The delimiter used to separate namespaces, e.g., `::` -* The [`LanguageFrontend`](#LanguageFrontend) used to parse it -* Additional [`LanguageTrait`](#LanguageTrait) implementations - -Each `Node` has a `language` property that specifies its language. - -### `LanguageTrait` - -A language trait aims to further categorize a programming language based on conceptual paradigms. This can be easily extended by introducing new interfaces based on `LanguageTrait`. Examples include: -* Are default arguments supported? -* Does the language have structs or classes? -* Are function pointers supported? -* Are templates or generics used in the language? - -These traits are used during the pass execution phase to fine-tune things like call resolution or type hierarchies. - -## `LanguageFrontend` - -In contrast to the `Language` concept, which represents the generic concept of a programming language, a `LanguageFrontend` is a specific module in the CPG library that does the actual translating of a programming language's source code into our CPG representation. - -At minimum a language frontend needs to parse the languages' code and translate it to specific CPG nodes. It will probably use some library to retrieve the abstract syntax tree (AST). The frontend will set the nodes' `AST` edges and establish proper scopes via the scope manager. Everything else, such as call or symbol resolving is optional and will be done by later passes. However, if a language frontend is confident in setting edges, such as `REFERS_TO`, it is allowed to and this is respected by later passes. However, one must be extremely careful in doing so. - -The frontend has a limited life-cycle and only exists during the *translation* phase. Later, during the execution of passes, the language frontend will not exist anymore. Language-specific customization of passes are done using [`LanguageTraits`](#LanguageTrait). - -To create nodes, a language frontend MUST use the node builder functions in the `ExpressionBuilder`, `DeclarationBuilder` or `StatementBuilder`. These are Kotlin extension functions that automatically inject the context, such as language, scope or code location of a language frontend or its handler into the created nodes. \ No newline at end of file diff --git a/docs/docs/API/index.md b/docs/docs/API/index.md new file mode 100644 index 0000000000..c68e46cd9c --- /dev/null +++ b/docs/docs/API/index.md @@ -0,0 +1,13 @@ +--- +title: "API Reference" +no_list: true +weight: 2 +--- + + +# API Reference + +We auto-generate an API reference using [dokka](https://github.com/Kotlin/dokka). The following versions are available: + +* [main](../dokka/main/) +* [v7.0.1](../dokka/v7.0.1/) diff --git a/docs/docs/CPG/specs/dfg.md b/docs/docs/CPG/specs/dfg.md index 6de6b62ad1..d1923a1155 100755 --- a/docs/docs/CPG/specs/dfg.md +++ b/docs/docs/CPG/specs/dfg.md @@ -401,6 +401,120 @@ Scheme: returnValue -- DFG --> node([ReturnStatement]); returnValue -.- node; ``` +## Branching Statements +Specific statements lead to a branch in the control flow of a program. A value that influences the branching decision can lead to an implicit data flow via the branching and we therefore draw a dfg edge from the condition, to the branching node. + +### ForEachStatement + +Interesting fields: + +* `variable: Statement`: The statement which is used in each iteration to assign the current iteration value. +* `iterable: Statement`: The statement or expression, which is iterated + +The value of the iterable flow to the `VariableDeclaration` in the `variable`. Since some languages allow arbitrary logic, we differentiate between two cases: + +### Case 1. The `variable` is a `DeclarationStatement`. + +This is the case for most languages where we can have only a variable in this place (e.g., `for(e in list)`). Here, we get the declaration(s) in the statement and add the DFG from the iterable to this declaration. + + +Scheme: +```mermaid + flowchart LR + node([ForEachStatement]) -.- variable[variable: DeclarationStatement] + node -.- iterable[iterable] + variable -.- declarations["declarations[i]"] + iterable -- for all i: DFG --> declarations +``` + +### Case 2. The `variable` is another type of `Statement`. + +In this case, we assume that the last VariableDeclaration is the one used for looping. We add a DFG edge only to this declaration. + +Scheme: +```mermaid + flowchart LR + node([ForEachStatement]) -.- statement[variable] + node -.- iterable[iterable] + statement -.- localVars[variables] + localVars -. "last" .-> variable + iterable -- DFG --> variable + variable -- DFG --> node +``` + +### DoStatement + +Interesting fields: +* `condition: Statement`: The condition that is evaluated before making the branching decision + + +Scheme: +```mermaid + flowchart LR + node([DoStatement]) -.- condition(condition) + condition -- DFG --> node +``` + +### WhileStatement + +Interesting fields: +* `condition: Statement`: The condition that is evaluated before making the branching decision +* `conditionDeclaration: Statement`: A declaration containing the condition in the initializer, used instead of the condition + +Scheme: +```mermaid + flowchart LR + node([WhileStatement]) -.- condition(condition) + node -.- conditionDeclaration(conditionDeclaration) + condition -- DFG --> node + conditionDeclaration -- DFG --> node +``` + +### ForStatement + +Interesting fields: +* `condition: Statement`: The condition that is evaluated before making the branching decision +* `conditionDeclaration: Statement`: A declaration containing the condition in the initializer, used instead of the condition. + +Scheme: +```mermaid + flowchart LR + node([ForStatement]) -.- condition(condition) + node -.- conditionDeclaration(conditionDeclaration) + condition -- DFG --> node + conditionDeclaration -- DFG --> node +``` + + +### IfStatement + +Interesting fields: +* `condition: Statement`: The condition that is evaluated before making the branching decision +* `conditionDeclaration: Statement`: A declaration containing the condition in the initialize, used instead of the condition. + +Scheme: +```mermaid + flowchart LR + node([IfStatement]) -.- condition(condition) + node -.- conditionDeclaration(conditionDeclaration) + condition -- DFG --> node + conditionDeclaration -- DFG --> node +``` + + +### SwitchStatement +Interesting fields: +* `selector: Statement`: The expression that is evaluated before making the branching decision +* `selectorDeclaration: Statement`: A declaration containing the selector in the initializer, used instead of the selector. + +Scheme: +```mermaid + flowchart LR + node([SwitchStatement]) -.- selector(selector) + node -.- selectorDeclaration(selectorDeclaration) + selector -- DFG --> node + selectorDeclaration -- DFG --> node +``` ## FunctionDeclaration diff --git a/docs/docs/GettingStarted/cli.md b/docs/docs/GettingStarted/cli.md index 9c8588360e..3b663a172e 100755 --- a/docs/docs/GettingStarted/cli.md +++ b/docs/docs/GettingStarted/cli.md @@ -8,3 +8,52 @@ description: > Using the Interactive CLI (cpg-console) --- +# The Interactive CLI + +If you want to explore the graph from the command line, we provide an interactive interface for this. + +To build the interface from source, simply type `./gradlew +:cpg-console:installDist` from the root of the repository. You can then start +the CLI with `cpg-console/build/install/cpg-console/bin/cpg-console`. + +The CLI comes with only few basic commands: +* `:tr ` or `:translate ` translates the file(s) under the given + path. +* `:c ` or `:code ` prints the code of the given node +* `:e neo4j ` exports the graph to neo4j +* `:translateCompilationDatabase ` or `:trdb ` translates the source + code files using the provided compilation database into the CPG +* `:r` or `:run` runs two pre-defined analyzers: a null pointer check or an + out-of-bounds check +* `:help` prints a help text with available commands +* `:q` quits the session + +After translating a file/project, the translation result will be kept in +`result`. You can now explore all edges and nodes in the graph as in any kotlin +project. You can also use the [shortcuts](shortcuts.md) or the analyses provided +by the [Query API](query.md). + +Example: +```kotlin +[0] :tr cpg-analysis/src/test/resources/value_evaluation/size.java + ... +[10] val mainFun = result.functions["main"] +[20] :code mainFun!! + 2: public static void main(String[] args) { + 3: int[] array = new int[3]; + 4: for(int i = 0; i < array.length; i++) { + 5: array[i] = i; + 6: } + 7: System.out.println(array[1]); + 8: + 9: String str = "abcde"; + 10: System.out.println(str); + 11: return 0; + 12: } + +[21] sizeof(mainFun.calls["println"]!!.arguments[0]).value +res21: Int = 3 +[22] :q + +Bye! +``` diff --git a/docs/docs/GettingStarted/index.md b/docs/docs/GettingStarted/index.md index bcb90b7493..8185da075f 100644 --- a/docs/docs/GettingStarted/index.md +++ b/docs/docs/GettingStarted/index.md @@ -17,3 +17,5 @@ After [installing the library](./installation), it can be used in different ways * [Via an interactive command line interface](./cli) * [With custom automated analyses using the Query API](./query) +In all these cases, the [Shortcuts](./shortcuts) provide you a convenient way to +quickly explore some of the most relevant information. diff --git a/docs/docs/GettingStarted/library.md b/docs/docs/GettingStarted/library.md index 00e3f89821..da8bfe840d 100644 --- a/docs/docs/GettingStarted/library.md +++ b/docs/docs/GettingStarted/library.md @@ -42,9 +42,9 @@ The following lines give you a small example: ```kotlin val inferenceConfig = InferenceConfiguration .builder() - .guessCastExpression(true) + .guessCastExpressions(true) .inferRecords(true) - .inferDfgForUnresolvedSymbols(true) + .inferDfgForUnresolvedCalls(true) .build() val translationConfig = TranslationConfiguration diff --git a/docs/docs/GettingStarted/query.md b/docs/docs/GettingStarted/query.md index eb47ed1b58..88c735129f 100755 --- a/docs/docs/GettingStarted/query.md +++ b/docs/docs/GettingStarted/query.md @@ -1,6 +1,6 @@ --- -title: "Implementation and Concepts - Query API" -linkTitle: "Implementation and Concepts - Query API" +title: "The Query API" +linkTitle: "The Query API" weight: 20 no_list: false menu: diff --git a/docs/docs/GettingStarted/shortcuts.md b/docs/docs/GettingStarted/shortcuts.md new file mode 100644 index 0000000000..f97492c443 --- /dev/null +++ b/docs/docs/GettingStarted/shortcuts.md @@ -0,0 +1,114 @@ +--- +title: "Shortcuts to Explore the Graph" +linkTitle: "Shortcuts to Explore the Graph" +weight: 20 +no_list: false +menu: + main: + weight: 20 +description: > + The CPG library is a language-agnostic graph representation of source code. +--- + +# Shortcuts to Explore the Graph + +When analyzing software, there are some information which are interesting to +explore. To facilitate accessing the information even without in-depth knowledge +about the graph and the graph model, we provide a number of shortcuts which can +be used on all nodes to find the nodes you're looking for. + +All you have to do to use this functionality is to add the `import +de.fraunhofer.aisec.cpg.graph.*`. + +## AST subtree traversal + +It is often useful to find nodes which are in the AST subtree of another node. +We provide the following shortcuts to gain a quick overview of relevant types of +nodes: + +Starting from node `n`... +* ...get all function/method calls with `n.calls` +* ...get all member calls (i.e., calls which are called on an object or class) + with `n.mcalls` +* ...get all method declarations with `n.methods` +* ...get all function (and method) declarations with `n.functions` +* ...get all field declarations with `n.fields` +* ...get all parameters with `n.parameters` +* ...get all record declarations (e.g. classes, structs) with `n.records` +* ...get all namespaces with `n.namespaces` +* ...get all variables with `n.variables` +* ...get all literals with `n.literals` +* ...get all references to variables, fields, functions, etc. with `n.refs` +* ...get all assignments with `n.assignments` + +## Filtering the results + +The lists you get here can be quite long and it's a good idea to filter them. To +do so, we provide different operators: +* To retrieve a single element, you can use the `[]` (get) operator and specify + your criterion inside the brackets. +* To retrieve a single element and get an exception if there are multiple + options, add the `SearchModifiers.UNIQUE` to the query. +* To retrieve a list of nodes, you can use the `()` (invokes) operator to + specify your criterion. + +Both notations allow you to quickly filter for the name by providing the +respective string or by accessing the fields and writing conditions on them. + +Examples: +```kotlin +import de.fraunhofer.aisec.cpg.graph.* + +// returns the first variable in the graph which has the name "a" +var a = result.variables["a"] + +// returns the only variable with the name "a" or an exception otherwise +var theOnlyA = result.variables["a", SearchModifiers.UNIQUE] + +// returns the first variable in the graph which does have an initialiser +var anyWithInitializer = result.variables[{ it.initializer != null }] + +// returns the only variable in the graph which does not have an initialiser or throws an exception +var uniqueWithInitializer = result.variables[{ it.initializer != null }, SearchModifiers.UNIQUE] + +// returns a list of all VariableDeclarations in the graph with the name "a" +var aList = result.variables("a") + +// returns a list of FunctionDeclarations that have no parameter +var noArgs = result.functions { it.parameters.isEmpty() } +``` + +## More information needed? + +In some cases, the AST-based traversals won't suffice to filter the nodes that +you're interested in. For this reason, there are a number of additional methods +which search for other patterns in the graph. Note that these are often less +stable than the information from above! + +* The size of an array is evaluated using + `ArraySubscriptionExpression.arraySize`. Unfortunately, this only works if the + size is given in the initialization. Updates are not considered. +* Control dependencies are currently available via the extensions + `Node.controlledBy()` and `IfStatement.controls()`. +* `Node.eogDistanceTo(to: Node)` calculates the number of EOG edges between + this node and `to`. +* `FunctionDeclaration.get(n: Int)`: Returns the n-th statement of the body of + this function. +* `FunctionDeclaration.callees`: Returns the functions which are called from + this function. +* `TranslationResult.callersOf(function: FunctionDeclaration)` determines which + functions call the specified function. +* `Node.followPrevDFG(predicate: (Node) -> Boolean)` returns a list of nodes + which form a path between this node and the first node (as a start of the + dataflow) matching the predicate. Note that this flow might not occur on + runtime!. +* `Node.followPrevEOG(predicate: (Node) -> Boolean)` + and `Node.followNextEOG(predicate: (Node) -> Boolean)` return a list of edges + which form an EOG path between this node and the first node matching the + predicate. Note that this flow might not happen on runtime! +* The methods `Node.followPrevDFGEdgesUntilHit(predicate: (Node) -> Boolean)`, + `Node.followNextDFGEdgesUntilHit(predicate: (Node) -> Boolean)`, + `Node.followPrevEOGEdgesUntilHit(predicate: (Node) -> Boolean)`, and + `Node.followNextGEdgesUntilHit(predicate: (Node) -> Boolean)` work in a similar + way but return all failed and all fulfilled paths. This allows reasoning more + precisely about the program's behavior. diff --git a/docs/docs/dokka/main/.gitkeep b/docs/docs/dokka/main/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/mkdocs.yaml b/docs/mkdocs.yaml index 1703a5ddb0..7ab9c29691 100755 --- a/docs/mkdocs.yaml +++ b/docs/mkdocs.yaml @@ -156,6 +156,7 @@ nav: - "Usage as library": GettingStarted/library.md - "Using the Interactive CLI": GettingStarted/cli.md - "Using the Query API": GettingStarted/query.md + - "Shortcuts to Explore the Graph": GettingStarted/shortcuts.md - "Specifications": - CPG/specs/index.md - "Graph Schema": CPG/specs/graph.md @@ -168,4 +169,5 @@ nav: - "Passes": CPG/impl/passes.md - "Contributing": - "Contributing to the CPG library": Contributing/index.md - - "Documentation": dokka/dokkaCustomMultiModuleOutput + # This assumes that the most recent dokka build was generated with the "main" tag! + - "API Reference": dokka/main diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b4e31de397..bc83195f9f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -41,6 +41,7 @@ mockito = { module = "org.mockito:mockito-core", version = "5.4.0"} # plugins needed for build.gradle.kts in buildSrc kotlin-gradle = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } dokka-gradle = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version = "1.8.10" } # the dokka plugin is slightly behind the main Kotlin release cycle +dokka-versioning= {module = "org.jetbrains.dokka:versioning-plugin", version = "1.8.10"} sonarqube-gradle = { module = "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin", version.ref = "sonarqube" } spotless-gradle = { module = "com.diffplug.spotless:spotless-plugin-gradle", version.ref = "spotless" } From 0519c8fdd38cca704ba03fd3023ddbccdbb35632 Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Wed, 19 Jul 2023 13:31:37 +0200 Subject: [PATCH 091/143] Delete docs/docs/dokka directory --- docs/docs/dokka/main/.gitkeep | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 docs/docs/dokka/main/.gitkeep diff --git a/docs/docs/dokka/main/.gitkeep b/docs/docs/dokka/main/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 From c179cec66290743713d44ccb8b2d4c0f57528342 Mon Sep 17 00:00:00 2001 From: KuechA <31155350+KuechA@users.noreply.github.com> Date: Wed, 19 Jul 2023 13:50:38 +0200 Subject: [PATCH 092/143] Get rid of shell command when generating docs (#1245) * Get rid of shell command * Ignore error --------- Co-authored-by: Konrad Weiss --- .github/workflows/build.yml | 2 +- build.gradle.kts | 9 ++------- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 52f970e20a..8c74536099 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -116,7 +116,7 @@ jobs: # make sure the previousDocs folder exists mkdir previousDocs && cd previousDocs # retrieve the previous documentation folders for each published version (this also includes "main") - wget -O - https://github.com/Fraunhofer-AISEC/cpg/archive/gh-pages.tar.gz | tar -xz --strip=2 cpg-gh-pages/dokka + wget -O - https://github.com/Fraunhofer-AISEC/cpg/archive/gh-pages.tar.gz | tar -xz --strip=2 cpg-gh-pages/dokka || echo "No dokka directory present. Will continue as if nothing happened" # in order to avoid duplicate mains, remove the "main" version from the previous versions rm -rf main - name: Build ${{ env.version }} diff --git a/build.gradle.kts b/build.gradle.kts index 4ea7d6b3f5..725ff39e52 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -53,13 +53,8 @@ allprojects { // configure dokka for the multi-module cpg project // this works together with the dokka configuration in the common-conventions plugin tasks.dokkaHtmlMultiModule { - val stdout = ByteArrayOutputStream() - exec { - commandLine("sh", "-c", "git tag --points-at HEAD | cat") - standardOutput = stdout - } - val configuredVersion = stdout.toString() - if(configuredVersion.isNotEmpty()) { + val configuredVersion = project.version.toString() + if(configuredVersion.isNotEmpty() && configuredVersion != "unspecified") { generateDokkaWithVersionTag(this, configuredVersion) } else { generateDokkaWithVersionTag(this, "main") From d4a4358a59a7008be46bd096cd0113d5e625b47e Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Wed, 19 Jul 2023 19:49:44 +0200 Subject: [PATCH 093/143] Only building dokka on main and releases (#1246) --- .github/workflows/build.yml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8c74536099..c55f8225b1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -123,13 +123,13 @@ jobs: run: | if [ "$SONAR_TOKEN" != "" ] then - ./gradlew --no-daemon --parallel -Pversion=$VERSION -Pexperimental -PenableJavaFrontend=true -PenableCXXFrontend=true -PenableGoFrontend=true -PenablePythonFrontend=true -PenableLLVMFrontend=true -PenableTypeScriptFrontend=true -Pintegration spotlessCheck -x spotlessApply build -x distZip -x distTar testCodeCoverageReport sonar dokkaHtmlMultiModule \ + ./gradlew --no-daemon --parallel -Pversion=$VERSION -Pexperimental -PenableJavaFrontend=true -PenableCXXFrontend=true -PenableGoFrontend=true -PenablePythonFrontend=true -PenableLLVMFrontend=true -PenableTypeScriptFrontend=true -Pintegration spotlessCheck -x spotlessApply build -x distZip -x distTar testCodeCoverageReport sonar \ -Dsonar.projectKey=Fraunhofer-AISEC_cpg \ -Dsonar.organization=fraunhofer-aisec \ -Dsonar.host.url=https://sonarcloud.io \ -Dsonar.login=$SONAR_TOKEN else - ./gradlew --no-daemon --parallel -Pversion=$VERSION -Pexperimental -PenableJavaFrontend=true -PenableCXXFrontend=true -PenableGoFrontend=true -PenablePythonFrontend=true -PenableLLVMFrontend=true -PenableTypeScriptFrontend=true -Pintegration spotlessCheck -x spotlessApply build -x distZip -x distTar dokkaHtmlMultiModule + ./gradlew --no-daemon --parallel -Pversion=$VERSION -Pexperimental -PenableJavaFrontend=true -PenableCXXFrontend=true -PenableGoFrontend=true -PenablePythonFrontend=true -PenableLLVMFrontend=true -PenableTypeScriptFrontend=true -Pintegration spotlessCheck -x spotlessApply build -x distZip -x distTar fi id: build env: @@ -146,12 +146,16 @@ jobs: if: startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, 'beta') && !contains(github.ref, 'alpha') run: | export ORG_GRADLE_PROJECT_signingKey=`echo ${{ secrets.GPG_PRIVATE_KEY }} | base64 -d` - ./gradlew --no-daemon -Dorg.gradle.internal.publish.checksums.insecure=true --parallel -Pversion=$VERSION -PenableJavaFrontend=true -PenableCXXFrontend=true -PenableGoFrontend=true -PenablePythonFrontend=true -PenableLLVMFrontend=true -PenableTypeScriptFrontend=true publish dokkaHtmlMultiModule + ./gradlew --no-daemon -Dorg.gradle.internal.publish.checksums.insecure=true --parallel -Pversion=$VERSION -PenableJavaFrontend=true -PenableCXXFrontend=true -PenableGoFrontend=true -PenablePythonFrontend=true -PenableLLVMFrontend=true -PenableTypeScriptFrontend=true publish env: VERSION: ${{ env.version }} ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.GPG_PASSWORD }} ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }} ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} + - name: Build JavaDoc + if: startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, 'beta') && !contains(github.ref, 'alpha') || github.ref == 'refs/heads/main' + run: | + ./gradlew --no-daemon --parallel -Pversion=$VERSION -PenableJavaFrontend=true -PenableCXXFrontend=true -PenableGoFrontend=true -PenablePythonFrontend=true -PenableLLVMFrontend=true -PenableTypeScriptFrontend=true dokkaHtmlMultiModule - name: Publish JavaDoc (version) if: startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, 'beta') && !contains(github.ref, 'alpha') uses: JamesIves/github-pages-deploy-action@v4 From 708ab68baff8622f40998ceee9b4007df8c135cf Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Wed, 19 Jul 2023 22:45:39 +0200 Subject: [PATCH 094/143] Building with a custom runner (#1243) --- .github/workflows/build.yml | 33 +- .../cpg.formatting-conventions.gradle.kts | 13 - cpg-language-typescript/build.gradle.kts | 26 +- .../src/main/nodejs/package-lock.json | 425 +++++++++ .../src/main/nodejs/package.json | 10 +- .../src/main/nodejs/rollup.config.mjs | 19 + .../src/main/nodejs/src/parser.ts | 16 +- .../src/main/nodejs/tsconfig.json | 2 +- .../src/main/nodejs/webpack.config.js | 10 - .../src/main/nodejs/yarn.lock | 815 ------------------ docs/docs/dokka/main/.gitkeep | 0 11 files changed, 490 insertions(+), 879 deletions(-) create mode 100644 cpg-language-typescript/src/main/nodejs/package-lock.json create mode 100644 cpg-language-typescript/src/main/nodejs/rollup.config.mjs delete mode 100644 cpg-language-typescript/src/main/nodejs/webpack.config.js delete mode 100644 cpg-language-typescript/src/main/nodejs/yarn.lock create mode 100644 docs/docs/dokka/main/.gitkeep diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c55f8225b1..c058df4d6e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -47,7 +47,7 @@ jobs: path: cpg-language-go/src/main/resources/libcpgo-arm64.dylib build: - runs-on: ubuntu-latest + runs-on: [self-hosted, linux, x64, faster] needs: build-cpgo-osx steps: - uses: actions/checkout@v3 @@ -59,16 +59,15 @@ jobs: with: distribution: "zulu" java-version: "17" - cache: "gradle" - uses: actions/setup-python@v4 with: python-version: "3.10" - uses: actions/setup-node@v3 with: - node-version: "16" + node-version: "18" - name: Setup neo4j run: | - docker run -d --env NEO4J_AUTH=neo4j/password -p7474:7474 -p7687:7687 neo4j + docker run -d --env NEO4J_AUTH=neo4j/password -p7474:7474 -p7687:7687 neo4j || true - name: Setup Go uses: actions/setup-go@v4 with: @@ -96,7 +95,9 @@ jobs: - name: Install JEP run: | pip3 install jep==$(grep "black.ninia:jep" gradle/libs.versions.toml | grep -o -E "[0-9]\d*(\.[0-9]\d*)*") - find /opt/hostedtoolcache/Python/ -name libjep.so -exec sudo cp '{}' /usr/lib/ \; + if [ -d "/opt/hostedtoolcache/Python" ]; then + find /opt/hostedtoolcache/Python/ -name libjep.so -exec sudo cp '{}' /usr/lib/ \; + fi - name: Install pycodestyle run: | pip3 install pycodestyle @@ -123,25 +124,37 @@ jobs: run: | if [ "$SONAR_TOKEN" != "" ] then - ./gradlew --no-daemon --parallel -Pversion=$VERSION -Pexperimental -PenableJavaFrontend=true -PenableCXXFrontend=true -PenableGoFrontend=true -PenablePythonFrontend=true -PenableLLVMFrontend=true -PenableTypeScriptFrontend=true -Pintegration spotlessCheck -x spotlessApply build -x distZip -x distTar testCodeCoverageReport sonar \ + ./gradlew --no-daemon --parallel -Pversion=$VERSION -Pintegration spotlessCheck -x spotlessApply build -x distZip -x distTar testCodeCoverageReport sonar \ -Dsonar.projectKey=Fraunhofer-AISEC_cpg \ -Dsonar.organization=fraunhofer-aisec \ -Dsonar.host.url=https://sonarcloud.io \ -Dsonar.login=$SONAR_TOKEN else - ./gradlew --no-daemon --parallel -Pversion=$VERSION -Pexperimental -PenableJavaFrontend=true -PenableCXXFrontend=true -PenableGoFrontend=true -PenablePythonFrontend=true -PenableLLVMFrontend=true -PenableTypeScriptFrontend=true -Pintegration spotlessCheck -x spotlessApply build -x distZip -x distTar + ./gradlew --no-daemon --parallel -Pversion=$VERSION -Pintegration spotlessCheck -x spotlessApply build -x distZip -x distTar fi id: build env: VERSION: ${{ env.version }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Prepare test and coverage reports + if: ${{ always() }} + run: | + zip reports.zip **/build/reports/** - name: Archive test and coverage reports if: ${{ always() }} uses: actions/upload-artifact@v3 with: name: reports - path: "**/build/reports" + path: reports.zip + - name: Download old dokka versions + run: | + # make sure the previousDocs folder exists + mkdir previousDocs && cd previousDocs + # retrieve the previous documentation folders for each published version (this also includes "main") + wget -O - https://github.com/Fraunhofer-AISEC/cpg/archive/gh-pages.tar.gz | tar -xz --strip=2 cpg-gh-pages/dokka + # in order to avoid duplicate mains, remove the "main" version from the previous versions + rm -rf main - name: Publish if: startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, 'beta') && !contains(github.ref, 'alpha') run: | @@ -155,7 +168,7 @@ jobs: - name: Build JavaDoc if: startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, 'beta') && !contains(github.ref, 'alpha') || github.ref == 'refs/heads/main' run: | - ./gradlew --no-daemon --parallel -Pversion=$VERSION -PenableJavaFrontend=true -PenableCXXFrontend=true -PenableGoFrontend=true -PenablePythonFrontend=true -PenableLLVMFrontend=true -PenableTypeScriptFrontend=true dokkaHtmlMultiModule + ./gradlew --no-daemon -Pversion=$VERSION dokkaHtmlMultiModule - name: Publish JavaDoc (version) if: startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, 'beta') && !contains(github.ref, 'alpha') uses: JamesIves/github-pages-deploy-action@v4 @@ -168,7 +181,7 @@ jobs: with: folder: build/dokkaCustomMultiModuleOutput/main target-folder: dokka/main - - name: "Create Release" + - name: Create Release if: startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, 'beta') && !contains(github.ref, 'alpha') id: create_release uses: softprops/action-gh-release@v1 diff --git a/buildSrc/src/main/kotlin/cpg.formatting-conventions.gradle.kts b/buildSrc/src/main/kotlin/cpg.formatting-conventions.gradle.kts index 4c739ba254..813189c6c8 100644 --- a/buildSrc/src/main/kotlin/cpg.formatting-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/cpg.formatting-conventions.gradle.kts @@ -34,9 +34,6 @@ plugins { tasks.withType { dependsOn("spotlessApply") } -tasks.withType { - dependsOn("spotlessApply") -} val headerWithStars = """/* * Copyright (c) ${"$"}YEAR, Fraunhofer AISEC. All rights reserved. @@ -92,16 +89,6 @@ val headerWithHashes = """# """ spotless { - java { - targetExclude( - fileTree(project.projectDir) { - include("build/generated-src/**") - } - ) - googleJavaFormat("1.15.0") - licenseHeader(headerWithStars).yearSeparator(" - ") - } - kotlin { ktfmt().kotlinlangStyle() licenseHeader(headerWithStars).yearSeparator(" - ") diff --git a/cpg-language-typescript/build.gradle.kts b/cpg-language-typescript/build.gradle.kts index f717365daf..e76d770b12 100644 --- a/cpg-language-typescript/build.gradle.kts +++ b/cpg-language-typescript/build.gradle.kts @@ -23,7 +23,7 @@ * \______/ \__| \______/ * */ -import com.github.gradle.node.yarn.task.YarnTask +import com.github.gradle.node.npm.task.NpmTask plugins { id("cpg.frontend-conventions") @@ -44,32 +44,22 @@ publishing { node { download.set(findProperty("nodeDownload")?.toString()?.toBoolean() ?: false) - version.set("16.4.2") + version.set("18") + nodeProjectDir.set(file("${project.projectDir.resolve("src/main/nodejs")}")) } -val yarnInstall by tasks.registering(YarnTask::class) { +val npmBuild by tasks.registering(NpmTask::class) { inputs.file("src/main/nodejs/package.json").withPathSensitivity(PathSensitivity.RELATIVE) - inputs.file("src/main/nodejs/yarn.lock").withPathSensitivity(PathSensitivity.RELATIVE) - outputs.dir("src/main/nodejs/node_modules") - outputs.cacheIf { true } - - workingDir.set(file("src/main/nodejs")) - yarnCommand.set(listOf("install", "--ignore-optional")) -} - -val yarnBuild by tasks.registering(YarnTask::class) { - inputs.file("src/main/nodejs/package.json").withPathSensitivity(PathSensitivity.RELATIVE) - inputs.file("src/main/nodejs/yarn.lock").withPathSensitivity(PathSensitivity.RELATIVE) + inputs.file("src/main/nodejs/package-lock.json").withPathSensitivity(PathSensitivity.RELATIVE) inputs.dir("src/main/nodejs/src").withPathSensitivity(PathSensitivity.RELATIVE) outputs.dir("build/resources/main/nodejs") outputs.cacheIf { true } workingDir.set(file("src/main/nodejs")) - yarnCommand.set(listOf("bundle")) - - dependsOn(yarnInstall) + npmCommand.set(listOf("run", "bundle")) + dependsOn(tasks.getByName("npmInstall")) } tasks.processResources { - dependsOn(yarnBuild) + dependsOn(npmBuild) } diff --git a/cpg-language-typescript/src/main/nodejs/package-lock.json b/cpg-language-typescript/src/main/nodejs/package-lock.json new file mode 100644 index 0000000000..ed81c93958 --- /dev/null +++ b/cpg-language-typescript/src/main/nodejs/package-lock.json @@ -0,0 +1,425 @@ +{ + "name": "nodejs", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "license": "Apache-2.0", + "dependencies": { + "@types/node": "18.16.5", + "typescript": "5.1.3" + }, + "devDependencies": { + "@rollup/plugin-commonjs": "^25.0.3", + "@rollup/plugin-node-resolve": "^15.1.0", + "@rollup/plugin-typescript": "^11.1.2", + "rollup": "^3.26.3", + "tslib": "^2.6.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true + }, + "node_modules/@rollup/plugin-commonjs": { + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-25.0.3.tgz", + "integrity": "sha512-uBdtWr/H3BVcgm97MUdq2oJmqBR23ny1hOrWe2PKo9FTbjsGqg32jfasJUKYAI5ouqacjRnj65mBB/S79F+GQA==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "commondir": "^1.0.1", + "estree-walker": "^2.0.2", + "glob": "^8.0.3", + "is-reference": "1.2.1", + "magic-string": "^0.27.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.68.0||^3.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-node-resolve": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.1.0.tgz", + "integrity": "sha512-xeZHCgsiZ9pzYVgAo9580eCGqwh/XCEUM9q6iQfGNocjgkufHAqC3exA+45URvhiYV8sBF9RlBai650eNs7AsA==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "@types/resolve": "1.20.2", + "deepmerge": "^4.2.2", + "is-builtin-module": "^3.2.1", + "is-module": "^1.0.0", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.78.0||^3.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-typescript": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-11.1.2.tgz", + "integrity": "sha512-0ghSOCMcA7fl1JM+0gYRf+Q/HWyg+zg7/gDSc+fRLmlJWcW5K1I+CLRzaRhXf4Y3DRyPnnDo4M2ktw+a6JcDEg==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.14.0||^3.0.0", + "tslib": "*", + "typescript": ">=3.7.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + }, + "tslib": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.0.2.tgz", + "integrity": "sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@types/estree": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz", + "integrity": "sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==", + "dev": true + }, + "node_modules/@types/node": { + "version": "18.16.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.5.tgz", + "integrity": "sha512-seOA34WMo9KB+UA78qaJoCO20RJzZGVXQ5Sh6FWu0g/hfT44nKXnej3/tCQl7FL97idFpBhisLYCTB50S0EirA==" + }, + "node_modules/@types/resolve": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", + "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", + "dev": true + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/builtin-modules": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", + "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/is-builtin-module": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz", + "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==", + "dev": true, + "dependencies": { + "builtin-modules": "^3.3.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-core-module": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", + "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", + "dev": true + }, + "node_modules/is-reference": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", + "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", + "dev": true, + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/magic-string": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz", + "integrity": "sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.13" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/rollup": { + "version": "3.26.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.26.3.tgz", + "integrity": "sha512-7Tin0C8l86TkpcMtXvQu6saWH93nhG3dGQ1/+l5V2TDMceTxO7kDiK6GzbfLWNNxqJXm591PcEZUozZm51ogwQ==", + "dev": true, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=14.18.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tslib": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", + "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "dev": true + }, + "node_modules/typescript": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.3.tgz", + "integrity": "sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw==", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + } + } +} diff --git a/cpg-language-typescript/src/main/nodejs/package.json b/cpg-language-typescript/src/main/nodejs/package.json index b3a9f1d911..e77bc119db 100644 --- a/cpg-language-typescript/src/main/nodejs/package.json +++ b/cpg-language-typescript/src/main/nodejs/package.json @@ -1,8 +1,7 @@ { "scripts": { "build": "tsc --build tsconfig.json", - "prebundle": "tsc --build tsconfig.json", - "bundle": "webpack --mode=production", + "bundle": "rollup -c", "start": "node src/parser.js" }, "dependencies": { @@ -11,7 +10,10 @@ }, "license": "Apache-2.0", "devDependencies": { - "webpack": "5.88.0", - "webpack-cli": "5.1.0" + "@rollup/plugin-commonjs": "^25.0.3", + "@rollup/plugin-node-resolve": "^15.1.0", + "@rollup/plugin-typescript": "^11.1.2", + "rollup": "^3.26.3", + "tslib": "^2.6.0" } } \ No newline at end of file diff --git a/cpg-language-typescript/src/main/nodejs/rollup.config.mjs b/cpg-language-typescript/src/main/nodejs/rollup.config.mjs new file mode 100644 index 0000000000..8c8c166efd --- /dev/null +++ b/cpg-language-typescript/src/main/nodejs/rollup.config.mjs @@ -0,0 +1,19 @@ +import typescript from "@rollup/plugin-typescript"; +import resolve from "@rollup/plugin-node-resolve"; +import commonjs from "@rollup/plugin-commonjs"; + +export default { + input: "src/parser.ts", + output: { + dir: "../../../build/resources/main/nodejs", + format: "commonjs", + }, + plugins: [ + commonjs({ + include: ["./src/parser.js", "node_modules/**"], + }), + typescript(), resolve({ + typescript: true, + path: true + })], +}; diff --git a/cpg-language-typescript/src/main/nodejs/src/parser.ts b/cpg-language-typescript/src/main/nodejs/src/parser.ts index 555802929c..a9b0e7ac15 100644 --- a/cpg-language-typescript/src/main/nodejs/src/parser.ts +++ b/cpg-language-typescript/src/main/nodejs/src/parser.ts @@ -1,9 +1,9 @@ -import * as ts from 'typescript'; -import path = require('path'); +import { SyntaxKind, SourceFile, Node, createProgram, forEachChild } from 'typescript'; +import * as path from 'node:path'; const file = path.normalize(process.argv[2]); -const program = ts.createProgram([file], { +const program = createProgram([file], { allowJs: true, }); @@ -14,8 +14,8 @@ sources.filter(sf => sf.fileName.endsWith(file)).forEach(sf => { console.log(printTree(sf, sf, false)); }) -function printTree(sf: ts.SourceFile, node: ts.Node, needsComma: boolean): string { - var output = " ".repeat(indent) + `{ "type": "${ts.SyntaxKind[node.kind]}"` +function printTree(sf: SourceFile, node: Node, needsComma: boolean): string { + var output = " ".repeat(indent) + `{ "type": "${SyntaxKind[node.kind]}"` //output += `, "code": "${node.getText(sf).replace(/"/g, "\\\"").replace(/\n/g, "\\n")}"` output += `, "code": ${JSON.stringify(node.getText(sf))}` @@ -24,13 +24,13 @@ function printTree(sf: ts.SourceFile, node: ts.Node, needsComma: boolean): strin // need to use forEachChild, otherwise, we will get additional syntax nodes, that we do not want var numChildren = 0; - ts.forEachChild(node, x => { + forEachChild(node, x => { numChildren++; }) if (numChildren == 1) { output += `, "children": [`; - ts.forEachChild(node, x => { + forEachChild(node, x => { output += printTree(sf, x, false); }); output += "]"; @@ -38,7 +38,7 @@ function printTree(sf: ts.SourceFile, node: ts.Node, needsComma: boolean): strin output += `, "children": [\n`; var i = 0; - ts.forEachChild(node, x => { + forEachChild(node, x => { //console.log(`${i} == ${numChildren}`) output += printTree(sf, x, i < numChildren - 1) i++; diff --git a/cpg-language-typescript/src/main/nodejs/tsconfig.json b/cpg-language-typescript/src/main/nodejs/tsconfig.json index aca6fa6818..4396959045 100644 --- a/cpg-language-typescript/src/main/nodejs/tsconfig.json +++ b/cpg-language-typescript/src/main/nodejs/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "target": "es6", - "module": "CommonJS", + "module": "esnext", "moduleResolution": "Node" } } \ No newline at end of file diff --git a/cpg-language-typescript/src/main/nodejs/webpack.config.js b/cpg-language-typescript/src/main/nodejs/webpack.config.js deleted file mode 100644 index 13ffc9f37b..0000000000 --- a/cpg-language-typescript/src/main/nodejs/webpack.config.js +++ /dev/null @@ -1,10 +0,0 @@ -const path = require('path'); - -module.exports = { - entry: './src/parser.js', - target: 'node', - output: { - path: path.resolve(__dirname, '../../../build/resources/main/nodejs'), - filename: 'parser.js', - } -}; diff --git a/cpg-language-typescript/src/main/nodejs/yarn.lock b/cpg-language-typescript/src/main/nodejs/yarn.lock deleted file mode 100644 index eb31cf8fa1..0000000000 --- a/cpg-language-typescript/src/main/nodejs/yarn.lock +++ /dev/null @@ -1,815 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@discoveryjs/json-ext@^0.5.0": - version "0.5.3" - resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.3.tgz#90420f9f9c6d3987f176a19a7d8e764271a2f55d" - integrity sha512-Fxt+AfXgjMoin2maPIYzFZnQjAXjAL0PHscM5pRTtatFqB+vZxAM9tLp2Optnuw3QOQC40jTNeGYFOMvyf7v9g== - -"@jridgewell/gen-mapping@^0.3.0": - version "0.3.2" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9" - integrity sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A== - dependencies: - "@jridgewell/set-array" "^1.0.1" - "@jridgewell/sourcemap-codec" "^1.4.10" - "@jridgewell/trace-mapping" "^0.3.9" - -"@jridgewell/resolve-uri@3.1.0", "@jridgewell/resolve-uri@^3.0.3": - version "3.1.0" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" - integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== - -"@jridgewell/set-array@^1.0.1": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" - integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== - -"@jridgewell/source-map@^0.3.2": - version "0.3.2" - resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.2.tgz#f45351aaed4527a298512ec72f81040c998580fb" - integrity sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw== - dependencies: - "@jridgewell/gen-mapping" "^0.3.0" - "@jridgewell/trace-mapping" "^0.3.9" - -"@jridgewell/sourcemap-codec@1.4.14", "@jridgewell/sourcemap-codec@^1.4.10": - version "1.4.14" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" - integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== - -"@jridgewell/trace-mapping@^0.3.17": - version "0.3.18" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz#25783b2086daf6ff1dcb53c9249ae480e4dd4cd6" - integrity sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA== - dependencies: - "@jridgewell/resolve-uri" "3.1.0" - "@jridgewell/sourcemap-codec" "1.4.14" - -"@jridgewell/trace-mapping@^0.3.9": - version "0.3.14" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz#b231a081d8f66796e475ad588a1ef473112701ed" - integrity sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ== - dependencies: - "@jridgewell/resolve-uri" "^3.0.3" - "@jridgewell/sourcemap-codec" "^1.4.10" - -"@types/eslint-scope@^3.7.3": - version "3.7.3" - resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.3.tgz#125b88504b61e3c8bc6f870882003253005c3224" - integrity sha512-PB3ldyrcnAicT35TWPs5IcwKD8S333HMaa2VVv4+wdvebJkjWuW/xESoB8IwRcog8HYVYamb1g/R31Qv5Bx03g== - dependencies: - "@types/eslint" "*" - "@types/estree" "*" - -"@types/eslint@*": - version "7.2.14" - resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-7.2.14.tgz#088661518db0c3c23089ab45900b99dd9214b92a" - integrity sha512-pESyhSbUOskqrGcaN+bCXIQDyT5zTaRWfj5ZjjSlMatgGjIn3QQPfocAu4WSabUR7CGyLZ2CQaZyISOEX7/saw== - dependencies: - "@types/estree" "*" - "@types/json-schema" "*" - -"@types/estree@*": - version "0.0.51" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.51.tgz#cfd70924a25a3fd32b218e5e420e6897e1ac4f40" - integrity sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ== - -"@types/estree@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.0.tgz#5fb2e536c1ae9bf35366eed879e827fa59ca41c2" - integrity sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ== - -"@types/json-schema@*", "@types/json-schema@^7.0.8": - version "7.0.8" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.8.tgz#edf1bf1dbf4e04413ca8e5b17b3b7d7d54b59818" - integrity sha512-YSBPTLTVm2e2OoQIDYx8HaeWJ5tTToLH67kXR7zYNGupXMEHa2++G8k+DczX2cFVgalypqtyZIcU19AFcmOpmg== - -"@types/node@*": - version "18.11.0" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.0.tgz#f38c7139247a1d619f6cc6f27b072606af7c289d" - integrity sha512-IOXCvVRToe7e0ny7HpT/X9Rb2RYtElG1a+VshjwT00HxrM2dWBApHQoqsI6WiY7Q03vdf2bCrIGzVrkF/5t10w== - -"@types/node@18.16.5": - version "18.16.5" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.16.5.tgz#bf64e42719dc2e74da24709a2e1c0b50a966120a" - integrity sha512-seOA34WMo9KB+UA78qaJoCO20RJzZGVXQ5Sh6FWu0g/hfT44nKXnej3/tCQl7FL97idFpBhisLYCTB50S0EirA== - -"@webassemblyjs/ast@1.11.5", "@webassemblyjs/ast@^1.11.5": - version "1.11.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.5.tgz#6e818036b94548c1fb53b754b5cae3c9b208281c" - integrity sha512-LHY/GSAZZRpsNQH+/oHqhRQ5FT7eoULcBqgfyTB5nQHogFnK3/7QoN7dLnwSE/JkUAF0SrRuclT7ODqMFtWxxQ== - dependencies: - "@webassemblyjs/helper-numbers" "1.11.5" - "@webassemblyjs/helper-wasm-bytecode" "1.11.5" - -"@webassemblyjs/floating-point-hex-parser@1.11.5": - version "1.11.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.5.tgz#e85dfdb01cad16b812ff166b96806c050555f1b4" - integrity sha512-1j1zTIC5EZOtCplMBG/IEwLtUojtwFVwdyVMbL/hwWqbzlQoJsWCOavrdnLkemwNoC/EOwtUFch3fuo+cbcXYQ== - -"@webassemblyjs/helper-api-error@1.11.5": - version "1.11.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.5.tgz#1e82fa7958c681ddcf4eabef756ce09d49d442d1" - integrity sha512-L65bDPmfpY0+yFrsgz8b6LhXmbbs38OnwDCf6NpnMUYqa+ENfE5Dq9E42ny0qz/PdR0LJyq/T5YijPnU8AXEpA== - -"@webassemblyjs/helper-buffer@1.11.5": - version "1.11.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.5.tgz#91381652ea95bb38bbfd270702351c0c89d69fba" - integrity sha512-fDKo1gstwFFSfacIeH5KfwzjykIE6ldh1iH9Y/8YkAZrhmu4TctqYjSh7t0K2VyDSXOZJ1MLhht/k9IvYGcIxg== - -"@webassemblyjs/helper-numbers@1.11.5": - version "1.11.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.5.tgz#23380c910d56764957292839006fecbe05e135a9" - integrity sha512-DhykHXM0ZABqfIGYNv93A5KKDw/+ywBFnuWybZZWcuzWHfbp21wUfRkbtz7dMGwGgT4iXjWuhRMA2Mzod6W4WA== - dependencies: - "@webassemblyjs/floating-point-hex-parser" "1.11.5" - "@webassemblyjs/helper-api-error" "1.11.5" - "@xtuc/long" "4.2.2" - -"@webassemblyjs/helper-wasm-bytecode@1.11.5": - version "1.11.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.5.tgz#e258a25251bc69a52ef817da3001863cc1c24b9f" - integrity sha512-oC4Qa0bNcqnjAowFn7MPCETQgDYytpsfvz4ujZz63Zu/a/v71HeCAAmZsgZ3YVKec3zSPYytG3/PrRCqbtcAvA== - -"@webassemblyjs/helper-wasm-section@1.11.5": - version "1.11.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.5.tgz#966e855a6fae04d5570ad4ec87fbcf29b42ba78e" - integrity sha512-uEoThA1LN2NA+K3B9wDo3yKlBfVtC6rh0i4/6hvbz071E8gTNZD/pT0MsBf7MeD6KbApMSkaAK0XeKyOZC7CIA== - dependencies: - "@webassemblyjs/ast" "1.11.5" - "@webassemblyjs/helper-buffer" "1.11.5" - "@webassemblyjs/helper-wasm-bytecode" "1.11.5" - "@webassemblyjs/wasm-gen" "1.11.5" - -"@webassemblyjs/ieee754@1.11.5": - version "1.11.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.11.5.tgz#b2db1b33ce9c91e34236194c2b5cba9b25ca9d60" - integrity sha512-37aGq6qVL8A8oPbPrSGMBcp38YZFXcHfiROflJn9jxSdSMMM5dS5P/9e2/TpaJuhE+wFrbukN2WI6Hw9MH5acg== - dependencies: - "@xtuc/ieee754" "^1.2.0" - -"@webassemblyjs/leb128@1.11.5": - version "1.11.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.11.5.tgz#482e44d26b6b949edf042a8525a66c649e38935a" - integrity sha512-ajqrRSXaTJoPW+xmkfYN6l8VIeNnR4vBOTQO9HzR7IygoCcKWkICbKFbVTNMjMgMREqXEr0+2M6zukzM47ZUfQ== - dependencies: - "@xtuc/long" "4.2.2" - -"@webassemblyjs/utf8@1.11.5": - version "1.11.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.11.5.tgz#83bef94856e399f3740e8df9f63bc47a987eae1a" - integrity sha512-WiOhulHKTZU5UPlRl53gHR8OxdGsSOxqfpqWeA2FmcwBMaoEdz6b2x2si3IwC9/fSPLfe8pBMRTHVMk5nlwnFQ== - -"@webassemblyjs/wasm-edit@^1.11.5": - version "1.11.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.5.tgz#93ee10a08037657e21c70de31c47fdad6b522b2d" - integrity sha512-C0p9D2fAu3Twwqvygvf42iGCQ4av8MFBLiTb+08SZ4cEdwzWx9QeAHDo1E2k+9s/0w1DM40oflJOpkZ8jW4HCQ== - dependencies: - "@webassemblyjs/ast" "1.11.5" - "@webassemblyjs/helper-buffer" "1.11.5" - "@webassemblyjs/helper-wasm-bytecode" "1.11.5" - "@webassemblyjs/helper-wasm-section" "1.11.5" - "@webassemblyjs/wasm-gen" "1.11.5" - "@webassemblyjs/wasm-opt" "1.11.5" - "@webassemblyjs/wasm-parser" "1.11.5" - "@webassemblyjs/wast-printer" "1.11.5" - -"@webassemblyjs/wasm-gen@1.11.5": - version "1.11.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.5.tgz#ceb1c82b40bf0cf67a492c53381916756ef7f0b1" - integrity sha512-14vteRlRjxLK9eSyYFvw1K8Vv+iPdZU0Aebk3j6oB8TQiQYuO6hj9s4d7qf6f2HJr2khzvNldAFG13CgdkAIfA== - dependencies: - "@webassemblyjs/ast" "1.11.5" - "@webassemblyjs/helper-wasm-bytecode" "1.11.5" - "@webassemblyjs/ieee754" "1.11.5" - "@webassemblyjs/leb128" "1.11.5" - "@webassemblyjs/utf8" "1.11.5" - -"@webassemblyjs/wasm-opt@1.11.5": - version "1.11.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.5.tgz#b52bac29681fa62487e16d3bb7f0633d5e62ca0a" - integrity sha512-tcKwlIXstBQgbKy1MlbDMlXaxpucn42eb17H29rawYLxm5+MsEmgPzeCP8B1Cl69hCice8LeKgZpRUAPtqYPgw== - dependencies: - "@webassemblyjs/ast" "1.11.5" - "@webassemblyjs/helper-buffer" "1.11.5" - "@webassemblyjs/wasm-gen" "1.11.5" - "@webassemblyjs/wasm-parser" "1.11.5" - -"@webassemblyjs/wasm-parser@1.11.5", "@webassemblyjs/wasm-parser@^1.11.5": - version "1.11.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.5.tgz#7ba0697ca74c860ea13e3ba226b29617046982e2" - integrity sha512-SVXUIwsLQlc8srSD7jejsfTU83g7pIGr2YYNb9oHdtldSxaOhvA5xwvIiWIfcX8PlSakgqMXsLpLfbbJ4cBYew== - dependencies: - "@webassemblyjs/ast" "1.11.5" - "@webassemblyjs/helper-api-error" "1.11.5" - "@webassemblyjs/helper-wasm-bytecode" "1.11.5" - "@webassemblyjs/ieee754" "1.11.5" - "@webassemblyjs/leb128" "1.11.5" - "@webassemblyjs/utf8" "1.11.5" - -"@webassemblyjs/wast-printer@1.11.5": - version "1.11.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.11.5.tgz#7a5e9689043f3eca82d544d7be7a8e6373a6fa98" - integrity sha512-f7Pq3wvg3GSPUPzR0F6bmI89Hdb+u9WXrSKc4v+N0aV0q6r42WoF92Jp2jEorBEBRoRNXgjp53nBniDXcqZYPA== - dependencies: - "@webassemblyjs/ast" "1.11.5" - "@xtuc/long" "4.2.2" - -"@webpack-cli/configtest@^2.1.0": - version "2.1.0" - resolved "https://registry.yarnpkg.com/@webpack-cli/configtest/-/configtest-2.1.0.tgz#b59b33377b1b896a9a7357cfc643b39c1524b1e6" - integrity sha512-K/vuv72vpfSEZoo5KIU0a2FsEoYdW0DUMtMpB5X3LlUwshetMZRZRxB7sCsVji/lFaSxtQQ3aM9O4eMolXkU9w== - -"@webpack-cli/info@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@webpack-cli/info/-/info-2.0.1.tgz#eed745799c910d20081e06e5177c2b2569f166c0" - integrity sha512-fE1UEWTwsAxRhrJNikE7v4EotYflkEhBL7EbajfkPlf6E37/2QshOy/D48Mw8G5XMFlQtS6YV42vtbG9zBpIQA== - -"@webpack-cli/serve@^2.0.3": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-2.0.3.tgz#c00c48d19340224242842e38b8f7b76c308bbd3f" - integrity sha512-Bwxd73pHuYc0cyl7vulPp2I6kAYtmJPkfUivbts7by6wDAVyFdKzGX3AksbvCRyNVFUJu7o2ZTcWXdT90T3qbg== - -"@xtuc/ieee754@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" - integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA== - -"@xtuc/long@4.2.2": - version "4.2.2" - resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" - integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== - -acorn-import-assertions@^1.9.0: - version "1.9.0" - resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz#507276249d684797c84e0734ef84860334cfb1ac" - integrity sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA== - -acorn@^8.5.0, acorn@^8.7.1: - version "8.8.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.0.tgz#88c0187620435c7f6015803f5539dae05a9dbea8" - integrity sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w== - -ajv-keywords@^3.5.2: - version "3.5.2" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" - integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== - -ajv@^6.12.5: - version "6.12.6" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" - integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== - dependencies: - fast-deep-equal "^3.1.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" - -browserslist@^4.14.5: - version "4.16.6" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.16.6.tgz#d7901277a5a88e554ed305b183ec9b0c08f66fa2" - integrity sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ== - dependencies: - caniuse-lite "^1.0.30001219" - colorette "^1.2.2" - electron-to-chromium "^1.3.723" - escalade "^3.1.1" - node-releases "^1.1.71" - -buffer-from@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" - integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== - -caniuse-lite@^1.0.30001219: - version "1.0.30001243" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001243.tgz#d9250155c91e872186671c523f3ae50cfc94a3aa" - integrity sha512-vNxw9mkTBtkmLFnJRv/2rhs1yufpDfCkBZexG3Y0xdOH2Z/eE/85E4Dl5j1YUN34nZVsSp6vVRFQRrez9wJMRA== - -chrome-trace-event@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac" - integrity sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg== - -clone-deep@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" - integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ== - dependencies: - is-plain-object "^2.0.4" - kind-of "^6.0.2" - shallow-clone "^3.0.0" - -colorette@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.2.tgz#cbcc79d5e99caea2dbf10eb3a26fd8b3e6acfa94" - integrity sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w== - -colorette@^2.0.14: - version "2.0.16" - resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.16.tgz#713b9af84fdb000139f04546bd4a93f62a5085da" - integrity sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g== - -commander@^10.0.1: - version "10.0.1" - resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06" - integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug== - -commander@^2.20.0: - version "2.20.3" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" - integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== - -cross-spawn@^7.0.3: - version "7.0.3" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" - integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== - dependencies: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" - -electron-to-chromium@^1.3.723: - version "1.3.772" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.772.tgz#fd1ed39f9f3149f62f581734e4f026e600369479" - integrity sha512-X/6VRCXWALzdX+RjCtBU6cyg8WZgoxm9YA02COmDOiNJEZ59WkQggDbWZ4t/giHi/3GS+cvdrP6gbLISANAGYA== - -enhanced-resolve@^5.15.0: - version "5.15.0" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz#1af946c7d93603eb88e9896cee4904dc012e9c35" - integrity sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg== - dependencies: - graceful-fs "^4.2.4" - tapable "^2.2.0" - -envinfo@^7.7.3: - version "7.8.1" - resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.8.1.tgz#06377e3e5f4d379fea7ac592d5ad8927e0c4d475" - integrity sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw== - -es-module-lexer@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.2.1.tgz#ba303831f63e6a394983fde2f97ad77b22324527" - integrity sha512-9978wrXM50Y4rTMmW5kXIC09ZdXQZqkE4mxhwkd8VbzsGkXGPgV4zWuqQJgCEzYngdo2dYDa0l8xhX4fkSwJSg== - -escalade@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" - integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== - -eslint-scope@5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" - integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== - dependencies: - esrecurse "^4.3.0" - estraverse "^4.1.1" - -esrecurse@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" - integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== - dependencies: - estraverse "^5.2.0" - -estraverse@^4.1.1: - version "4.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" - integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== - -estraverse@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.2.0.tgz#307df42547e6cc7324d3cf03c155d5cdb8c53880" - integrity sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ== - -events@^3.2.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" - integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== - -fast-deep-equal@^3.1.1: - version "3.1.3" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" - integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== - -fast-json-stable-stringify@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" - integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== - -fastest-levenshtein@^1.0.12: - version "1.0.12" - resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz#9990f7d3a88cc5a9ffd1f1745745251700d497e2" - integrity sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow== - -find-up@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" - integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== - dependencies: - locate-path "^5.0.0" - path-exists "^4.0.0" - -function-bind@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" - integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== - -glob-to-regexp@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" - integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== - -graceful-fs@^4.1.2, graceful-fs@^4.2.4, graceful-fs@^4.2.9: - version "4.2.9" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.9.tgz#041b05df45755e587a24942279b9d113146e1c96" - integrity sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ== - -has-flag@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" - integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== - -has@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" - integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== - dependencies: - function-bind "^1.1.1" - -import-local@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.0.2.tgz#a8cfd0431d1de4a2199703d003e3e62364fa6db6" - integrity sha512-vjL3+w0oulAVZ0hBHnxa/Nm5TAurf9YLQJDhqRZyqb+VKGOB6LU8t9H1Nr5CIo16vh9XfJTOoHwU0B71S557gA== - dependencies: - pkg-dir "^4.2.0" - resolve-cwd "^3.0.0" - -interpret@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/interpret/-/interpret-3.1.1.tgz#5be0ceed67ca79c6c4bc5cf0d7ee843dcea110c4" - integrity sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ== - -is-core-module@^2.9.0: - version "2.11.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.11.0.tgz#ad4cb3e3863e814523c96f3f58d26cc570ff0144" - integrity sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw== - dependencies: - has "^1.0.3" - -is-plain-object@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" - integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== - dependencies: - isobject "^3.0.1" - -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= - -isobject@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" - integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= - -jest-worker@^27.4.5: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.5.1.tgz#8d146f0900e8973b106b6f73cc1e9a8cb86f8db0" - integrity sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg== - dependencies: - "@types/node" "*" - merge-stream "^2.0.0" - supports-color "^8.0.0" - -json-parse-even-better-errors@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" - integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== - -json-schema-traverse@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" - integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== - -kind-of@^6.0.2: - version "6.0.3" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" - integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== - -loader-runner@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.2.0.tgz#d7022380d66d14c5fb1d496b89864ebcfd478384" - integrity sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw== - -locate-path@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" - integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== - dependencies: - p-locate "^4.1.0" - -merge-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" - integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== - -mime-db@1.48.0: - version "1.48.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.48.0.tgz#e35b31045dd7eada3aaad537ed88a33afbef2d1d" - integrity sha512-FM3QwxV+TnZYQ2aRqhlKBMHxk10lTbMt3bBkMAp54ddrNeVSfcQYOOKuGuy3Ddrm38I04If834fOUSq1yzslJQ== - -mime-types@^2.1.27: - version "2.1.31" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.31.tgz#a00d76b74317c61f9c2db2218b8e9f8e9c5c9e6b" - integrity sha512-XGZnNzm3QvgKxa8dpzyhFTHmpP3l5YNusmne07VUOXxou9CqUqYa/HBy124RqtVh/O2pECas/MOcsDgpilPOPg== - dependencies: - mime-db "1.48.0" - -neo-async@^2.6.2: - version "2.6.2" - resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" - integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== - -node-releases@^1.1.71: - version "1.1.73" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.73.tgz#dd4e81ddd5277ff846b80b52bb40c49edf7a7b20" - integrity sha512-uW7fodD6pyW2FZNZnp/Z3hvWKeEW1Y8R1+1CnErE8cXFXzl5blBOoVB41CvMer6P6Q0S5FXDwcHgFd1Wj0U9zg== - -p-limit@^2.2.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" - integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== - dependencies: - p-try "^2.0.0" - -p-locate@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" - integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== - dependencies: - p-limit "^2.2.0" - -p-try@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" - integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== - -path-exists@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" - integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== - -path-key@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" - integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== - -path-parse@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" - integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== - -pkg-dir@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" - integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== - dependencies: - find-up "^4.0.0" - -punycode@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" - integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== - -randombytes@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" - integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== - dependencies: - safe-buffer "^5.1.0" - -rechoir@^0.8.0: - version "0.8.0" - resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.8.0.tgz#49f866e0d32146142da3ad8f0eff352b3215ff22" - integrity sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ== - dependencies: - resolve "^1.20.0" - -resolve-cwd@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" - integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== - dependencies: - resolve-from "^5.0.0" - -resolve-from@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" - integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== - -resolve@^1.20.0: - version "1.22.1" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177" - integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== - dependencies: - is-core-module "^2.9.0" - path-parse "^1.0.7" - supports-preserve-symlinks-flag "^1.0.0" - -safe-buffer@^5.1.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" - integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== - -schema-utils@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.1.1.tgz#bc74c4b6b6995c1d88f76a8b77bea7219e0c8281" - integrity sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw== - dependencies: - "@types/json-schema" "^7.0.8" - ajv "^6.12.5" - ajv-keywords "^3.5.2" - -schema-utils@^3.2.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.3.0.tgz#f50a88877c3c01652a15b622ae9e9795df7a60fe" - integrity sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg== - dependencies: - "@types/json-schema" "^7.0.8" - ajv "^6.12.5" - ajv-keywords "^3.5.2" - -serialize-javascript@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.1.tgz#b206efb27c3da0b0ab6b52f48d170b7996458e5c" - integrity sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w== - dependencies: - randombytes "^2.1.0" - -shallow-clone@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3" - integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA== - dependencies: - kind-of "^6.0.2" - -shebang-command@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" - integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== - dependencies: - shebang-regex "^3.0.0" - -shebang-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" - integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== - -source-map-support@~0.5.20: - version "0.5.21" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" - integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== - dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" - -source-map@^0.6.0: - version "0.6.1" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" - integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== - -supports-color@^8.0.0: - version "8.1.1" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" - integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== - dependencies: - has-flag "^4.0.0" - -supports-preserve-symlinks-flag@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" - integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== - -tapable@^2.1.1, tapable@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.0.tgz#5c373d281d9c672848213d0e037d1c4165ab426b" - integrity sha512-FBk4IesMV1rBxX2tfiK8RAmogtWn53puLOQlvO8XuwlgxcYbP4mVPS9Ph4aeamSyyVjOl24aYWAuc8U5kCVwMw== - -terser-webpack-plugin@^5.3.7: - version "5.3.7" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.7.tgz#ef760632d24991760f339fe9290deb936ad1ffc7" - integrity sha512-AfKwIktyP7Cu50xNjXF/6Qb5lBNzYaWpU6YfoX3uZicTx0zTy0stDDCsvjDapKsSDvOeWo5MEq4TmdBy2cNoHw== - dependencies: - "@jridgewell/trace-mapping" "^0.3.17" - jest-worker "^27.4.5" - schema-utils "^3.1.1" - serialize-javascript "^6.0.1" - terser "^5.16.5" - -terser@^5.16.5: - version "5.16.9" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.16.9.tgz#7a28cb178e330c484369886f2afd623d9847495f" - integrity sha512-HPa/FdTB9XGI2H1/keLFZHxl6WNvAI4YalHGtDQTlMnJcoqSab1UwL4l1hGEhs6/GmLHBZIg/YgB++jcbzoOEg== - dependencies: - "@jridgewell/source-map" "^0.3.2" - acorn "^8.5.0" - commander "^2.20.0" - source-map-support "~0.5.20" - -typescript@5.1.3: - version "5.1.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.1.3.tgz#8d84219244a6b40b6fb2b33cc1c062f715b9e826" - integrity sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw== - -uri-js@^4.2.2: - version "4.4.1" - resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" - integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== - dependencies: - punycode "^2.1.0" - -watchpack@^2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.0.tgz#fa33032374962c78113f93c7f2fb4c54c9862a5d" - integrity sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg== - dependencies: - glob-to-regexp "^0.4.1" - graceful-fs "^4.1.2" - -webpack-cli@5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-5.1.0.tgz#abc4b1f44b50250f2632d8b8b536cfe2f6257891" - integrity sha512-a7KRJnCxejFoDpYTOwzm5o21ZXMaNqtRlvS183XzGDUPRdVEzJNImcQokqYZ8BNTnk9DkKiuWxw75+DCCoZ26w== - dependencies: - "@discoveryjs/json-ext" "^0.5.0" - "@webpack-cli/configtest" "^2.1.0" - "@webpack-cli/info" "^2.0.1" - "@webpack-cli/serve" "^2.0.3" - colorette "^2.0.14" - commander "^10.0.1" - cross-spawn "^7.0.3" - envinfo "^7.7.3" - fastest-levenshtein "^1.0.12" - import-local "^3.0.2" - interpret "^3.1.1" - rechoir "^0.8.0" - webpack-merge "^5.7.3" - -webpack-merge@^5.7.3: - version "5.8.0" - resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-5.8.0.tgz#2b39dbf22af87776ad744c390223731d30a68f61" - integrity sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q== - dependencies: - clone-deep "^4.0.1" - wildcard "^2.0.0" - -webpack-sources@^3.2.3: - version "3.2.3" - resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" - integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== - -webpack@5.88.0: - version "5.88.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.88.0.tgz#a07aa2f8e7a64a8f1cec0c6c2e180e3cb34440c8" - integrity sha512-O3jDhG5e44qIBSi/P6KpcCcH7HD+nYIHVBhdWFxcLOcIGN8zGo5nqF3BjyNCxIh4p1vFdNnreZv2h2KkoAw3lw== - dependencies: - "@types/eslint-scope" "^3.7.3" - "@types/estree" "^1.0.0" - "@webassemblyjs/ast" "^1.11.5" - "@webassemblyjs/wasm-edit" "^1.11.5" - "@webassemblyjs/wasm-parser" "^1.11.5" - acorn "^8.7.1" - acorn-import-assertions "^1.9.0" - browserslist "^4.14.5" - chrome-trace-event "^1.0.2" - enhanced-resolve "^5.15.0" - es-module-lexer "^1.2.1" - eslint-scope "5.1.1" - events "^3.2.0" - glob-to-regexp "^0.4.1" - graceful-fs "^4.2.9" - json-parse-even-better-errors "^2.3.1" - loader-runner "^4.2.0" - mime-types "^2.1.27" - neo-async "^2.6.2" - schema-utils "^3.2.0" - tapable "^2.1.1" - terser-webpack-plugin "^5.3.7" - watchpack "^2.4.0" - webpack-sources "^3.2.3" - -which@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" - integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== - dependencies: - isexe "^2.0.0" - -wildcard@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.0.tgz#a77d20e5200c6faaac979e4b3aadc7b3dd7f8fec" - integrity sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw== diff --git a/docs/docs/dokka/main/.gitkeep b/docs/docs/dokka/main/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 From 9e6503a6712c796713107b2d0b2d7f26d0d99e95 Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Thu, 20 Jul 2023 09:06:35 +0200 Subject: [PATCH 095/143] Delete docs/docs/dokka/main directory --- docs/docs/dokka/main/.gitkeep | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 docs/docs/dokka/main/.gitkeep diff --git a/docs/docs/dokka/main/.gitkeep b/docs/docs/dokka/main/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 From 39767ff1f0de0579d0973090a26d09eebd2f68c0 Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Fri, 21 Jul 2023 14:01:26 +0200 Subject: [PATCH 096/143] Trivial change in README.md to rebuild dokka folder --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 5f2719894d..82e20bfb88 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,6 @@ A code property graph (CPG) is a representation of source code in form of a labe This library uses [Eclipse CDT](https://www.eclipse.org/cdt/) for parsing C/C++ source code [JavaParser](https://javaparser.org/) for parsing Java. In contrast to compiler AST generators, both are "forgiving" parsers that can cope with incomplete or even semantically incorrect source code. That makes it possible to analyze source code even without being able to compile it (due to missing dependencies or minor syntax errors). Furthermore, it uses [LLVM](https://llvm.org) through the [javacpp](https://github.com/bytedeco/javacpp) project to parse LLVM IR. Note that the LLVM IR parser is *not* forgiving, i.e., the LLVM IR code needs to be at least considered valid by LLVM. The necessary native libraries are shipped by the javacpp project for most platforms. - ## Specifications In order to improve some formal aspects of our library, we created several specifications of our core concepts. Currently, the following specifications exist: From f58a34e6b84c1fb841e9e6d969b9c1725505b821 Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Fri, 21 Jul 2023 14:03:49 +0200 Subject: [PATCH 097/143] Added -p --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c058df4d6e..ed908fc6a7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -115,7 +115,7 @@ jobs: - name: Download old dokka versions run: | # make sure the previousDocs folder exists - mkdir previousDocs && cd previousDocs + mkdir -p previousDocs && cd previousDocs # retrieve the previous documentation folders for each published version (this also includes "main") wget -O - https://github.com/Fraunhofer-AISEC/cpg/archive/gh-pages.tar.gz | tar -xz --strip=2 cpg-gh-pages/dokka || echo "No dokka directory present. Will continue as if nothing happened" # in order to avoid duplicate mains, remove the "main" version from the previous versions From 5f92f77c12e8e26d48ad08bd62f2a347568e4b5f Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Fri, 21 Jul 2023 14:11:29 +0200 Subject: [PATCH 098/143] Removed duplicate dokka workflow --- .github/workflows/build.yml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ed908fc6a7..d73d035555 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -147,14 +147,6 @@ jobs: with: name: reports path: reports.zip - - name: Download old dokka versions - run: | - # make sure the previousDocs folder exists - mkdir previousDocs && cd previousDocs - # retrieve the previous documentation folders for each published version (this also includes "main") - wget -O - https://github.com/Fraunhofer-AISEC/cpg/archive/gh-pages.tar.gz | tar -xz --strip=2 cpg-gh-pages/dokka - # in order to avoid duplicate mains, remove the "main" version from the previous versions - rm -rf main - name: Publish if: startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, 'beta') && !contains(github.ref, 'alpha') run: | From 5fbfa801f0943d654e670497d1498aab532414f6 Mon Sep 17 00:00:00 2001 From: Tobias Specht Date: Fri, 21 Jul 2023 15:34:11 +0200 Subject: [PATCH 099/143] Convert Neo4j pre-save hooks to cpg pass (#1224) --- .../aisec/cpg/passes/PrepareSerialization.kt | 61 +++++++++++++++++++ .../aisec/cpg_vis_neo4j/Application.kt | 33 +--------- .../aisec/cpg_vis_neo4j/ApplicationTest.kt | 32 +++------- 3 files changed, 73 insertions(+), 53 deletions(-) create mode 100644 cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/PrepareSerialization.kt diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/PrepareSerialization.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/PrepareSerialization.kt new file mode 100644 index 0000000000..6896475360 --- /dev/null +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/PrepareSerialization.kt @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2023, 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.allChildren +import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration +import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression +import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker +import de.fraunhofer.aisec.cpg.passes.order.ExecuteBefore +import kotlin.reflect.full.memberProperties +import kotlin.reflect.jvm.javaField + +/** Pass with some graph transformations useful when doing serialization. */ +@ExecuteBefore(FilenameMapper::class) +class PrepareSerialization(ctx: TranslationContext) : TranslationUnitPass(ctx) { + private val nodeNameField = + Node::class + .memberProperties + .first() { it.name == "name" } + .javaField + .also { it?.isAccessible = true } + + override fun cleanup() { + // nothing to do + } + + override fun accept(tr: TranslationUnitDeclaration) { + tr.allChildren().map { node -> + // Add explicit AST edge + node.astChildren = SubgraphWalker.getAstChildren(node) + // CallExpression overwrites name property and must be copied to JvmField + // to be visible by Neo4jOGM + if (node is CallExpression) nodeNameField?.set(node, node.name) + } + } +} diff --git a/cpg-neo4j/src/main/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/Application.kt b/cpg-neo4j/src/main/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/Application.kt index 185fd06ac0..5b55601c45 100644 --- a/cpg-neo4j/src/main/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/Application.kt +++ b/cpg-neo4j/src/main/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/Application.kt @@ -27,28 +27,20 @@ package de.fraunhofer.aisec.cpg_vis_neo4j import de.fraunhofer.aisec.cpg.* import de.fraunhofer.aisec.cpg.frontends.CompilationDatabase.Companion.fromFile -import de.fraunhofer.aisec.cpg.graph.Node -import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression import de.fraunhofer.aisec.cpg.helpers.Benchmark -import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker import de.fraunhofer.aisec.cpg.passes.* -import de.fraunhofer.aisec.cpg.passes.order.* import java.io.File import java.lang.Class import java.net.ConnectException import java.nio.file.Paths import java.util.concurrent.Callable import kotlin.reflect.KClass -import kotlin.reflect.full.memberProperties -import kotlin.reflect.jvm.javaField import kotlin.system.exitProcess import org.neo4j.driver.exceptions.AuthenticationException import org.neo4j.ogm.config.Configuration import org.neo4j.ogm.exception.ConnectionException import org.neo4j.ogm.session.Session import org.neo4j.ogm.session.SessionFactory -import org.neo4j.ogm.session.event.Event -import org.neo4j.ogm.session.event.EventListenerAdapter import org.slf4j.Logger import org.slf4j.LoggerFactory import picocli.CommandLine @@ -303,7 +295,6 @@ class Application : Callable { "de.fraunhofer.aisec.cpg.graph", "de.fraunhofer.aisec.cpg.frontends" ) - sessionFactory.register(AstChildrenEventListener()) session = sessionFactory.openSession() } catch (ex: ConnectionException) { @@ -351,7 +342,7 @@ class Application : Callable { * point to a file, is a directory or point to a hidden file or the paths does not have the * same top level path. */ - private fun setupTranslationConfiguration(): TranslationConfiguration { + fun setupTranslationConfiguration(): TranslationConfiguration { val translationConfiguration = TranslationConfiguration.builder() .topLevel(topLevel) @@ -394,6 +385,7 @@ class Application : Callable { } } } + translationConfiguration.registerPass(PrepareSerialization::class) mutuallyExclusiveParameters.jsonCompilationDatabase?.let { val db = fromFile(it) @@ -477,27 +469,6 @@ class Application : Callable { } } -class AstChildrenEventListener : EventListenerAdapter() { - private val nodeNameField = - Node::class - .memberProperties - .first() { it.name == "name" } - .javaField - .also { it?.isAccessible = true } - - override fun onPreSave(event: Event?) { - val node = event?.`object` as? Node ?: return - node.astChildren = SubgraphWalker.getAstChildren(node) - if (node is CallExpression) fixBackingFields(node) - } - - private fun fixBackingFields(node: CallExpression) { - // CallExpression overwrites name property and must be copied to JvmField - // to be visible by Neo4jOGM - nodeNameField?.set(node, node.name) - } -} - /** * Starts a command line application of the cpg-vis-neo4j. * diff --git a/cpg-neo4j/src/test/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/ApplicationTest.kt b/cpg-neo4j/src/test/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/ApplicationTest.kt index 2d1d442867..33f38fe2cb 100644 --- a/cpg-neo4j/src/test/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/ApplicationTest.kt +++ b/cpg-neo4j/src/test/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/ApplicationTest.kt @@ -25,47 +25,35 @@ */ package de.fraunhofer.aisec.cpg_vis_neo4j -import de.fraunhofer.aisec.cpg.TranslationConfiguration import de.fraunhofer.aisec.cpg.TranslationManager -import de.fraunhofer.aisec.cpg.TranslationResult import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration import de.fraunhofer.aisec.cpg.graph.functions -import java.io.File import java.nio.file.Paths import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertNotNull import org.junit.jupiter.api.Tag +import picocli.CommandLine @Tag("integration") class ApplicationTest { - - private var translationResult: TranslationResult? = null - @Test @Throws(InterruptedException::class) fun testPush() { val topLevel = Paths.get("src").resolve("test").resolve("resources").toAbsolutePath() val path = topLevel.resolve("client.cpp").toAbsolutePath() - val file = File(path.toString()) - assert(file.exists() && !file.isDirectory && !file.isHidden) - val translationConfiguration = - TranslationConfiguration.builder() - .sourceLocations(file) - .topLevel(topLevel.toFile()) - .defaultPasses() - .defaultLanguages() - .debugParser(true) - .build() - val translationManager = - TranslationManager.builder().config(translationConfiguration).build() - translationResult = translationManager.analyze().get() - assertEquals(31, translationResult.functions.size) + val cmd = CommandLine(Application::class.java) + cmd.parseArgs(path.toString()) + val application = cmd.getCommand() - val application = Application() + val translationConfiguration = application.setupTranslationConfiguration() + val translationResult = + TranslationManager.builder().config(translationConfiguration).build().analyze().get() + + assertEquals(31, translationResult.functions.size) - application.pushToNeo4j(translationResult!!) + application.pushToNeo4j(translationResult) val sessionAndSessionFactoryPair = application.connect() From e109b3a18ff9fac3c47df07a0d85c3281a435381 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 23 Jul 2023 12:27:43 +0200 Subject: [PATCH 100/143] Update dependency @types/node to v18.17.0 (#1247) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- cpg-language-typescript/src/main/nodejs/package-lock.json | 8 ++++---- cpg-language-typescript/src/main/nodejs/package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cpg-language-typescript/src/main/nodejs/package-lock.json b/cpg-language-typescript/src/main/nodejs/package-lock.json index ed81c93958..4833fcaaf2 100644 --- a/cpg-language-typescript/src/main/nodejs/package-lock.json +++ b/cpg-language-typescript/src/main/nodejs/package-lock.json @@ -6,7 +6,7 @@ "": { "license": "Apache-2.0", "dependencies": { - "@types/node": "18.16.5", + "@types/node": "18.17.0", "typescript": "5.1.3" }, "devDependencies": { @@ -128,9 +128,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "18.16.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.5.tgz", - "integrity": "sha512-seOA34WMo9KB+UA78qaJoCO20RJzZGVXQ5Sh6FWu0g/hfT44nKXnej3/tCQl7FL97idFpBhisLYCTB50S0EirA==" + "version": "18.17.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.17.0.tgz", + "integrity": "sha512-GXZxEtOxYGFchyUzxvKI14iff9KZ2DI+A6a37o6EQevtg6uO9t+aUZKcaC1Te5Ng1OnLM7K9NVVj+FbecD9cJg==" }, "node_modules/@types/resolve": { "version": "1.20.2", diff --git a/cpg-language-typescript/src/main/nodejs/package.json b/cpg-language-typescript/src/main/nodejs/package.json index e77bc119db..6d0c8f6269 100644 --- a/cpg-language-typescript/src/main/nodejs/package.json +++ b/cpg-language-typescript/src/main/nodejs/package.json @@ -5,7 +5,7 @@ "start": "node src/parser.js" }, "dependencies": { - "@types/node": "18.16.5", + "@types/node": "18.17.0", "typescript": "5.1.3" }, "license": "Apache-2.0", From cc3bf45ecb01a47b30d1be4c18e700afa16592dc Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 24 Jul 2023 16:03:39 +0200 Subject: [PATCH 101/143] Update dependency org.junit.jupiter:junit-jupiter-params to v5.10.0 (#1248) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index bc83195f9f..da4146d626 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -35,7 +35,7 @@ jep = { module = "black.ninia:jep", version = "4.1.1" } # build.yml uses grep t llvm = { module = "org.bytedeco:llvm-platform", version = "16.0.4-1.5.9"} # test -junit-params = { module = "org.junit.jupiter:junit-jupiter-params", version = "5.9.1"} +junit-params = { module = "org.junit.jupiter:junit-jupiter-params", version = "5.10.0"} mockito = { module = "org.mockito:mockito-core", version = "5.4.0"} # plugins needed for build.gradle.kts in buildSrc From 5f18c1fb23958aeda45a72f6ba29cad054ccf5c5 Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Tue, 25 Jul 2023 13:52:28 +0200 Subject: [PATCH 102/143] Fixed missing version environment variable in dokka build step (#1250) --- .github/workflows/build.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d73d035555..2daaf5ac04 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -161,6 +161,8 @@ jobs: if: startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, 'beta') && !contains(github.ref, 'alpha') || github.ref == 'refs/heads/main' run: | ./gradlew --no-daemon -Pversion=$VERSION dokkaHtmlMultiModule + env: + VERSION: ${{ env.version }} - name: Publish JavaDoc (version) if: startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, 'beta') && !contains(github.ref, 'alpha') uses: JamesIves/github-pages-deploy-action@v4 From 8cd7a885740ff8992a071be6fc0956433777721a Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Tue, 25 Jul 2023 14:34:13 +0200 Subject: [PATCH 103/143] Upgraded transitive dependencies (#1251) This PR upgrades transitive dependencies because of known security issues. --- cpg-language-typescript/build.gradle.kts | 2 +- gradle/libs.versions.toml | 4 ++-- jitpack.yml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cpg-language-typescript/build.gradle.kts b/cpg-language-typescript/build.gradle.kts index e76d770b12..dfc4cbfd8d 100644 --- a/cpg-language-typescript/build.gradle.kts +++ b/cpg-language-typescript/build.gradle.kts @@ -44,7 +44,7 @@ publishing { node { download.set(findProperty("nodeDownload")?.toString()?.toBoolean() ?: false) - version.set("18") + version.set("18.17.0") nodeProjectDir.set(file("${project.projectDir.resolve("src/main/nodejs")}")) } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index da4146d626..7c08d8bc98 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] kotlin = "1.9.0" -neo4j = "4.0.2" +neo4j = "4.0.6" log4j = "2.20.0" sonarqube = "4.2.0.3129" spotless = "6.20.0" @@ -21,7 +21,7 @@ neo4j-ogm-core = { module = "org.neo4j:neo4j-ogm-core", version.ref = "neo4j"} neo4j-ogm = { module = "org.neo4j:neo4j-ogm", version.ref = "neo4j"} neo4j-ogm-bolt = { module = "org.neo4j:neo4j-ogm-bolt-driver", version.ref = "neo4j"} -javaparser = { module = "com.github.javaparser:javaparser-symbol-solver-core", version = "3.25.0"} +javaparser = { module = "com.github.javaparser:javaparser-symbol-solver-core", version = "3.25.4"} jackson = { module = "com.fasterxml.jackson.module:jackson-module-kotlin", version = "2.15.0"} eclipse-runtime = { module = "org.eclipse.platform:org.eclipse.core.runtime", version = "3.27.0"} osgi-service = { module = "org.osgi:org.osgi.service.prefs", version = "1.1.2"} diff --git a/jitpack.yml b/jitpack.yml index 0299ff9ce2..87055334f5 100644 --- a/jitpack.yml +++ b/jitpack.yml @@ -2,7 +2,7 @@ jdk: - openjdk17 before_install: - mkdir -p ~/go - - wget -q https://go.dev/dl/go1.18.3.linux-amd64.tar.gz && tar -C ~ -xzf go1.18.3.linux-amd64.tar.gz + - wget -q https://go.dev/dl/go1.20.4.linux-amd64.tar.gz && tar -C ~ -xzf go1.20.4.linux-amd64.tar.gz - ls -l ~/go install: - export PATH="$PATH:$HOME/go/bin" From 69fdca47f754fe5c201318869d0e562c682934f4 Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Tue, 25 Jul 2023 14:59:04 +0200 Subject: [PATCH 104/143] Added discussion template for dev meetings (#1252) --- .github/DISCUSSION_TEMPLATE/dev-meetings.yml | 33 ++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 .github/DISCUSSION_TEMPLATE/dev-meetings.yml diff --git a/.github/DISCUSSION_TEMPLATE/dev-meetings.yml b/.github/DISCUSSION_TEMPLATE/dev-meetings.yml new file mode 100644 index 0000000000..5e1da8e265 --- /dev/null +++ b/.github/DISCUSSION_TEMPLATE/dev-meetings.yml @@ -0,0 +1,33 @@ +title: "Meeting minutes for XX.XX.2023" +labels: ["minutes"] +body: + - type: markdown + attributes: + value: | + This discussion contains the public minutes of the dev meeting conducted on the date in the title. + It can be created ahead of time (ideally at least a week before) to fill the initial agenda and + list of PRs to discuss. + - type: textarea + id: agenda + attributes: + label: Agenda + description: "What is/was the general of the meeting?" + value: | + 1. Open PRs + 2. + 3. + validations: + required: true + - type: textarea + id: prs + attributes: + label: Discussed PRs + description: "Which PRs should be discussed / where discussed in the meeting?" + value: | + - [ ] # + - [ ] # + - [ ] # + ... + validations: + required: true + \ No newline at end of file From f285022e239d245b42fc545186bded76af64385c Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Tue, 25 Jul 2023 20:13:31 +0200 Subject: [PATCH 105/143] Fix publishing issues (#1254) --- .github/workflows/build.yml | 34 +++++++++++++------ build.gradle.kts | 15 ++++++++ buildSrc/build.gradle.kts | 1 + .../kotlin/cpg.common-conventions.gradle.kts | 18 ++-------- cpg-language-typescript/build.gradle.kts | 2 +- .../src/main/nodejs/package-lock.json | 8 ++--- .../src/main/nodejs/package.json | 2 +- gradle/libs.versions.toml | 2 ++ jitpack.yml | 1 - 9 files changed, 50 insertions(+), 33 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2daaf5ac04..a1739e55ef 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -112,14 +112,6 @@ jobs: with: name: libcpgo-amd64.dylib path: cpg-language-go/src/main/resources/ - - name: Download old dokka versions - run: | - # make sure the previousDocs folder exists - mkdir -p previousDocs && cd previousDocs - # retrieve the previous documentation folders for each published version (this also includes "main") - wget -O - https://github.com/Fraunhofer-AISEC/cpg/archive/gh-pages.tar.gz | tar -xz --strip=2 cpg-gh-pages/dokka || echo "No dokka directory present. Will continue as if nothing happened" - # in order to avoid duplicate mains, remove the "main" version from the previous versions - rm -rf main - name: Build ${{ env.version }} run: | if [ "$SONAR_TOKEN" != "" ] @@ -147,16 +139,32 @@ jobs: with: name: reports path: reports.zip - - name: Publish + - name: Publish to Maven Central if: startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, 'beta') && !contains(github.ref, 'alpha') run: | export ORG_GRADLE_PROJECT_signingKey=`echo ${{ secrets.GPG_PRIVATE_KEY }} | base64 -d` - ./gradlew --no-daemon -Dorg.gradle.internal.publish.checksums.insecure=true --parallel -Pversion=$VERSION -PenableJavaFrontend=true -PenableCXXFrontend=true -PenableGoFrontend=true -PenablePythonFrontend=true -PenableLLVMFrontend=true -PenableTypeScriptFrontend=true publish + ./gradlew --no-daemon -Dorg.gradle.internal.publish.checksums.insecure=true --parallel -Pversion=$VERSION -PenableJavaFrontend=true -PenableCXXFrontend=true -PenableGoFrontend=true -PenablePythonFrontend=true -PenableLLVMFrontend=true -PenableTypeScriptFrontend=true publishToSonatype closeSonatypeStagingRepository env: VERSION: ${{ env.version }} ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.GPG_PASSWORD }} ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }} ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} + - name: Download old dokka versions (version) + if: startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, 'beta') && !contains(github.ref, 'alpha') + run: | + # make sure the previousDocs folder exists + mkdir -p previousDocs && cd previousDocs + # retrieve the previous documentation folders for each published version (this also includes "main") + wget -O - https://github.com/Fraunhofer-AISEC/cpg/archive/gh-pages.tar.gz | tar -xz --strip=2 cpg-gh-pages/dokka || echo "No dokka directory present. Will continue as if nothing happened" + - name: Download old dokka versions (main) + if: github.ref == 'refs/heads/main' + run: | + # make sure the previousDocs folder exists + mkdir -p previousDocs && cd previousDocs + # retrieve the previous documentation folders for each published version (this also includes "main") + wget -O - https://github.com/Fraunhofer-AISEC/cpg/archive/gh-pages.tar.gz | tar -xz --strip=2 cpg-gh-pages/dokka || echo "No dokka directory present. Will continue as if nothing happened" + # in order to avoid duplicate mains, remove the "main" version from the previous versions + rm -rf main - name: Build JavaDoc if: startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, 'beta') && !contains(github.ref, 'alpha') || github.ref == 'refs/heads/main' run: | @@ -169,6 +177,12 @@ jobs: with: folder: build/dokkaCustomMultiModuleOutput/${{ env.version }} target-folder: dokka/${{ env.version }} + - name: Publish JavaDoc (version as main) + if: startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, 'beta') && !contains(github.ref, 'alpha') + uses: JamesIves/github-pages-deploy-action@v4 + with: + folder: build/dokkaCustomMultiModuleOutput/${{ env.version }} + target-folder: dokka/main - name: Publish JavaDoc (main) if: github.ref == 'refs/heads/main' uses: JamesIves/github-pages-deploy-action@v4 diff --git a/build.gradle.kts b/build.gradle.kts index 725ff39e52..6d0ebd5155 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -34,6 +34,7 @@ import java.io.ByteArrayOutputStream plugins { id("org.jetbrains.dokka") id("org.sonarqube") + id("io.github.gradle-nexus.publish-plugin") } // this is needed for the plugins block @@ -90,6 +91,20 @@ sonarqube { } } +/** + * Publishing to maven central + */ +nexusPublishing { + repositories { + sonatype() { + val mavenCentralUsername: String? by project + val mavenCentralPassword: String? by project + + username.set(mavenCentralUsername) + password.set(mavenCentralPassword) + } + } +} // diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 8ccc359397..234798349e 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -11,5 +11,6 @@ dependencies { implementation(libs.dokka.gradle) implementation(libs.sonarqube.gradle) implementation(libs.spotless.gradle) + implementation(libs.nexus.publish.gradle) implementation(files(libs.javaClass.superclass.protectionDomain.codeSource.location)) // this is only there to be able to import 'LibrariesForLibs' in the convention plugins to access the version catalog in buildSrc } diff --git a/buildSrc/src/main/kotlin/cpg.common-conventions.gradle.kts b/buildSrc/src/main/kotlin/cpg.common-conventions.gradle.kts index 1983d0fc7d..e882ee8b6a 100644 --- a/buildSrc/src/main/kotlin/cpg.common-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/cpg.common-conventions.gradle.kts @@ -6,10 +6,10 @@ plugins { `java-library` jacoco - kotlin("jvm") - id("org.jetbrains.dokka") signing `maven-publish` + kotlin("jvm") + id("org.jetbrains.dokka") } group = "de.fraunhofer.aisec" @@ -79,20 +79,6 @@ publishing { } } } - - repositories { - maven { - url = uri("https://oss.sonatype.org/service/local/staging/deploy/maven2") - - credentials { - val mavenCentralUsername: String? by project - val mavenCentralPassword: String? by project - - username = mavenCentralUsername - password = mavenCentralPassword - } - } - } } signing { diff --git a/cpg-language-typescript/build.gradle.kts b/cpg-language-typescript/build.gradle.kts index dfc4cbfd8d..aaa38f32c4 100644 --- a/cpg-language-typescript/build.gradle.kts +++ b/cpg-language-typescript/build.gradle.kts @@ -44,7 +44,7 @@ publishing { node { download.set(findProperty("nodeDownload")?.toString()?.toBoolean() ?: false) - version.set("18.17.0") + version.set("16.20.1") nodeProjectDir.set(file("${project.projectDir.resolve("src/main/nodejs")}")) } diff --git a/cpg-language-typescript/src/main/nodejs/package-lock.json b/cpg-language-typescript/src/main/nodejs/package-lock.json index 4833fcaaf2..3f999ba2d3 100644 --- a/cpg-language-typescript/src/main/nodejs/package-lock.json +++ b/cpg-language-typescript/src/main/nodejs/package-lock.json @@ -6,7 +6,7 @@ "": { "license": "Apache-2.0", "dependencies": { - "@types/node": "18.17.0", + "@types/node": "^16", "typescript": "5.1.3" }, "devDependencies": { @@ -128,9 +128,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "18.17.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.17.0.tgz", - "integrity": "sha512-GXZxEtOxYGFchyUzxvKI14iff9KZ2DI+A6a37o6EQevtg6uO9t+aUZKcaC1Te5Ng1OnLM7K9NVVj+FbecD9cJg==" + "version": "16.18.39", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.39.tgz", + "integrity": "sha512-8q9ZexmdYYyc5/cfujaXb4YOucpQxAV4RMG0himLyDUOEr8Mr79VrqsFI+cQ2M2h89YIuy95lbxuYjxT4Hk4kQ==" }, "node_modules/@types/resolve": { "version": "1.20.2", diff --git a/cpg-language-typescript/src/main/nodejs/package.json b/cpg-language-typescript/src/main/nodejs/package.json index 6d0c8f6269..7b2d8d4113 100644 --- a/cpg-language-typescript/src/main/nodejs/package.json +++ b/cpg-language-typescript/src/main/nodejs/package.json @@ -5,7 +5,7 @@ "start": "node src/parser.js" }, "dependencies": { - "@types/node": "18.17.0", + "@types/node": "^16", "typescript": "5.1.3" }, "license": "Apache-2.0", diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7c08d8bc98..8cd0897f14 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -4,6 +4,7 @@ neo4j = "4.0.6" log4j = "2.20.0" sonarqube = "4.2.0.3129" spotless = "6.20.0" +nexus-publish = "1.3.0" [libraries] kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin"} @@ -44,6 +45,7 @@ dokka-gradle = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version = " dokka-versioning= {module = "org.jetbrains.dokka:versioning-plugin", version = "1.8.10"} sonarqube-gradle = { module = "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin", version.ref = "sonarqube" } spotless-gradle = { module = "com.diffplug.spotless:spotless-plugin-gradle", version.ref = "spotless" } +nexus-publish-gradle = { module = "io.github.gradle-nexus:publish-plugin", version.ref = "nexus-publish" } [bundles] log4j = ["log4j-impl", "log4j-core"] diff --git a/jitpack.yml b/jitpack.yml index 87055334f5..8f25a3cc8c 100644 --- a/jitpack.yml +++ b/jitpack.yml @@ -3,7 +3,6 @@ jdk: before_install: - mkdir -p ~/go - wget -q https://go.dev/dl/go1.20.4.linux-amd64.tar.gz && tar -C ~ -xzf go1.20.4.linux-amd64.tar.gz - - ls -l ~/go install: - export PATH="$PATH:$HOME/go/bin" - cp gradle.properties.example gradle.properties From 95364c7248b2077727f8d26c0d28c11d8f6c399c Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Tue, 25 Jul 2023 20:37:13 +0200 Subject: [PATCH 106/143] Correctly applying group to all projects (including root) --- build.gradle.kts | 2 ++ buildSrc/src/main/kotlin/cpg.common-conventions.gradle.kts | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 6d0ebd5155..4284914adc 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -45,6 +45,8 @@ repositories { allprojects { plugins.apply("org.jetbrains.dokka") + group = "de.fraunhofer.aisec" + val dokkaPlugin by configurations dependencies { dokkaPlugin("org.jetbrains.dokka:versioning-plugin:1.8.10") diff --git a/buildSrc/src/main/kotlin/cpg.common-conventions.gradle.kts b/buildSrc/src/main/kotlin/cpg.common-conventions.gradle.kts index e882ee8b6a..37c53ea283 100644 --- a/buildSrc/src/main/kotlin/cpg.common-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/cpg.common-conventions.gradle.kts @@ -12,8 +12,6 @@ plugins { id("org.jetbrains.dokka") } -group = "de.fraunhofer.aisec" - java { withSourcesJar() } From 066abcf7c9e64d8523b2f28a593cc69da3eb05ec Mon Sep 17 00:00:00 2001 From: KuechA <31155350+KuechA@users.noreply.github.com> Date: Wed, 26 Jul 2023 09:50:06 +0200 Subject: [PATCH 107/143] Improvements to EOG iteration and more applications (#1135) --- .../aisec/cpg/analysis/ValueEvaluator.kt | 4 + .../aisec/cpg/passes/UnreachableEOGPass.kt | 207 +++++-- .../cpg/analysis/MultiValueEvaluatorTest.kt | 4 + .../cpg/passes/UnreachableEOGPassTest.kt | 47 +- .../aisec/cpg/frontends/LanguageTraits.kt | 18 +- .../aisec/cpg/graph/BranchingNode.kt | 32 + .../aisec/cpg/graph/ExpressionBuilder.kt | 23 +- .../de/fraunhofer/aisec/cpg/graph/Node.kt | 34 +- .../aisec/cpg/graph/builder/Fluent.kt | 31 + .../aisec/cpg/graph/statements/CatchClause.kt | 7 +- .../cpg/graph/statements/ForEachStatement.kt | 47 +- .../cpg/graph/statements/ForStatement.kt | 7 +- .../aisec/cpg/graph/statements/IfStatement.kt | 7 +- .../cpg/graph/statements/SwitchStatement.kt | 7 +- .../cpg/graph/statements/WhileStatement.kt | 7 +- .../statements/expressions/BinaryOperator.kt | 2 +- .../expressions/ConditionalExpression.kt | 5 +- .../expressions/ShortCircuitOperator.kt | 42 ++ .../aisec/cpg/helpers/EOGWorklist.kt | 282 +++++++++ .../cpg/passes/ControlDependenceGraphPass.kt | 266 +++++++++ .../cpg/passes/ControlFlowSensitiveDFGPass.kt | 556 +++++++----------- .../de/fraunhofer/aisec/cpg/GraphExamples.kt | 117 +++- .../passes/ControlDependenceGraphPassTest.kt | 194 ++++++ .../de/fraunhofer/aisec/cpg/passes/DFGTest.kt | 10 + .../aisec/cpg/frontends/TestLanguage.kt | 6 +- .../cpg/frontends/cxx/ExpressionHandler.kt | 1 + .../src/test/resources/cfg/if.cpp | 1 + 27 files changed, 1523 insertions(+), 441 deletions(-) create mode 100644 cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/BranchingNode.kt create mode 100644 cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ShortCircuitOperator.kt create mode 100644 cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/EOGWorklist.kt create mode 100644 cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPass.kt create mode 100644 cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPassTest.kt diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluator.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluator.kt index 5836fb7fc0..5e1d27d582 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluator.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluator.kt @@ -76,6 +76,10 @@ open class ValueEvaluator( return evaluateInternal(node as? Node, 0) } + fun clearPath() { + path.clear() + } + /** Tries to evaluate this node. Anything can happen. */ protected open fun evaluateInternal(node: Node?, depth: Int): Any? { // Add the expression to the current path diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGPass.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGPass.kt index 6631cdfb00..c867b09ed6 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGPass.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGPass.kt @@ -28,13 +28,14 @@ package de.fraunhofer.aisec.cpg.passes import de.fraunhofer.aisec.cpg.TranslationContext import de.fraunhofer.aisec.cpg.analysis.ValueEvaluator import de.fraunhofer.aisec.cpg.graph.Node +import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration import de.fraunhofer.aisec.cpg.graph.edge.Properties +import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge import de.fraunhofer.aisec.cpg.graph.statements.IfStatement import de.fraunhofer.aisec.cpg.graph.statements.WhileStatement +import de.fraunhofer.aisec.cpg.helpers.* import de.fraunhofer.aisec.cpg.passes.order.DependsOn -import de.fraunhofer.aisec.cpg.processing.IVisitor -import de.fraunhofer.aisec.cpg.processing.strategy.Strategy /** * A [Pass] which uses a simple logic to determine constant values and mark unreachable code regions @@ -42,57 +43,183 @@ import de.fraunhofer.aisec.cpg.processing.strategy.Strategy */ @DependsOn(ControlFlowSensitiveDFGPass::class) class UnreachableEOGPass(ctx: TranslationContext) : TranslationUnitPass(ctx) { + override fun cleanup() { + // Nothing to do + } + override fun accept(tu: TranslationUnitDeclaration) { - tu.accept( - Strategy::AST_FORWARD, - object : IVisitor() { - override fun visit(t: Node) { - when (t) { - is IfStatement -> handleIfStatement(t) - is WhileStatement -> handleWhileStatement(t) - } - - super.visit(t) + val walker = SubgraphWalker.IterativeGraphWalker() + walker.registerOnNodeVisit(::handle) + walker.iterate(tu) + } + + /** + * We perform the actions for each [FunctionDeclaration]. + * + * @param node every node in the TranslationResult + */ + protected fun handle(node: Node) { + if (node is FunctionDeclaration) { + val startState = UnreachabilityState() + for (firstEdge in node.nextEOGEdges) { + startState.push(firstEdge, ReachabilityLattice(Reachability.REACHABLE)) + } + val finalState = iterateEOG(node.nextEOGEdges, startState, ::transfer) ?: return + + for ((key, value) in finalState) { + if (value.elements == Reachability.UNREACHABLE) { + key.addProperty(Properties.UNREACHABLE, true) } } - ) + } } +} - private fun handleIfStatement(n: IfStatement) { - val evalResult = ValueEvaluator().evaluate(n.condition) +/** + * This method is executed for each EOG edge which is in the worklist. [currentEdge] is the edge to + * process, [currentState] contains the state which was observed before arriving here. + * + * This method modifies the state for the next eog edge as follows: + * - If the next node in the eog is an [IfStatement], the condition is evaluated and if it is either + * always true or false, the else or then branch receives set to [Reachability.UNREACHABLE]. + * - If the next node in the eog is a [WhileStatement], the condition is evaluated and if it's + * always true or false, either the EOG edge to the loop body or out of the loop body is set to + * [Reachability.UNREACHABLE]. + * - For all other nodes, we simply propagate the state which led us here. + * + * Returns the updated state and true because we always expect an update of the state. + */ +fun transfer( + currentEdge: PropertyEdge, + currentState: State, Reachability>, + currentWorklist: Worklist, PropertyEdge, Reachability> +): State, Reachability> { + val currentNode = currentEdge.end + if (currentNode is IfStatement) { + handleIfStatement(currentEdge, currentNode, currentState) + } else if (currentNode is WhileStatement) { + handleWhileStatement(currentEdge, currentNode, currentState) + } else { + // For all other edges, we simply propagate the reachability property of the edge + // which made us come here. + currentNode.nextEOGEdges.forEach { currentState.push(it, currentState[currentEdge]) } + } + + return currentState +} + +/** + * Evaluates the condition of the [IfStatement] [n] (which is the end node of [enteringEdge]). If it + * is always true, then the else-branch receives the [state] [Reachability.UNREACHABLE]. If the + * condition is always false, then the then-branch receives the [state] [Reachability.UNREACHABLE]. + * All other cases simply copy the state which led us here. + */ +private fun handleIfStatement( + enteringEdge: PropertyEdge, + n: IfStatement, + state: State, Reachability> +) { + val evalResult = ValueEvaluator().evaluate(n.condition) + + val (unreachableEdge, remainingEdges) = if (evalResult is Boolean && evalResult == true) { - n.nextEOGEdges - .firstOrNull { e -> e.getProperty(Properties.INDEX) == 1 } - ?.addProperty(Properties.UNREACHABLE, true) + Pair( + n.nextEOGEdges.firstOrNull { e -> e.getProperty(Properties.INDEX) == 1 }, + n.nextEOGEdges.filter { e -> e.getProperty(Properties.INDEX) != 1 } + ) } else if (evalResult is Boolean && evalResult == false) { - n.nextEOGEdges - .firstOrNull { e -> e.getProperty(Properties.INDEX) == 0 } - ?.addProperty(Properties.UNREACHABLE, true) + Pair( + n.nextEOGEdges.firstOrNull { e -> e.getProperty(Properties.INDEX) == 0 }, + n.nextEOGEdges.filter { e -> e.getProperty(Properties.INDEX) != 0 } + ) + } else { + Pair(null, n.nextEOGEdges) } + + if (unreachableEdge != null) { + // This edge is definitely unreachable + state.push(unreachableEdge, ReachabilityLattice(Reachability.UNREACHABLE)) } - private fun handleWhileStatement(n: WhileStatement) { - /* - * Note: It does not understand that code like - * x = true; while(x) {...; x = false;} - * makes the loop execute at least once. - * Apparently, the CPG does not offer the required functionality to - * differentiate between the first and subsequent evaluations of the - * condition. - */ - val evalResult = ValueEvaluator().evaluate(n.condition) + // For all other edges, we simply propagate the reachability property of the edge which + // made us come here. + remainingEdges.forEach { state.push(it, state[enteringEdge]) } +} + +/** + * Evaluates the condition of the [WhileStatement] [n] (which is the end node of [enteringEdge]). If + * it is always true, then the edge to the code after the loop receives the [state] + * [Reachability.UNREACHABLE]. If the condition is always false, then the edge to the loop body + * receives the [state] [Reachability.UNREACHABLE]. All other cases simply copy the state which led + * us here. + */ +private fun handleWhileStatement( + enteringEdge: PropertyEdge, + n: WhileStatement, + state: State, Reachability> +) { + /* + * Note: It does not understand that code like + * x = true; while(x) {...; x = false;} + * makes the loop execute at least once. + * Apparently, the CPG does not offer the required functionality to + * differentiate between the first and subsequent evaluations of the + * condition. + */ + val evalResult = ValueEvaluator().evaluate(n.condition) + + val (unreachableEdge, remainingEdges) = if (evalResult is Boolean && evalResult == true) { - n.nextEOGEdges - .firstOrNull { e -> e.getProperty(Properties.INDEX) == 1 } - ?.addProperty(Properties.UNREACHABLE, true) + Pair( + n.nextEOGEdges.firstOrNull { e -> e.getProperty(Properties.INDEX) == 1 }, + n.nextEOGEdges.filter { e -> e.getProperty(Properties.INDEX) != 1 } + ) } else if (evalResult is Boolean && evalResult == false) { - n.nextEOGEdges - .firstOrNull { e -> e.getProperty(Properties.INDEX) == 0 } - ?.addProperty(Properties.UNREACHABLE, true) + Pair( + n.nextEOGEdges.firstOrNull { e -> e.getProperty(Properties.INDEX) == 0 }, + n.nextEOGEdges.filter { e -> e.getProperty(Properties.INDEX) != 0 } + ) + } else { + Pair(null, n.nextEOGEdges) } - } - override fun cleanup() { - // nothing to do + if (unreachableEdge != null) { + // This edge is definitely unreachable + state.push(unreachableEdge, ReachabilityLattice(Reachability.UNREACHABLE)) } + + // For all other edges, we simply propagate the reachability property of the edge which + // made us come here. + remainingEdges.forEach { state.push(it, state[enteringEdge]) } } + +/** + * Implements the [LatticeElement] over reachability properties: TOP | REACHABLE | UNREACHABLE | + * BOTTOM + */ +class ReachabilityLattice(override val elements: Reachability) : + LatticeElement(elements) { + override fun lub(other: LatticeElement) = + ReachabilityLattice(maxOf(this.elements, other.elements)) + + override fun duplicate() = ReachabilityLattice(this.elements) + + override fun compareTo(other: LatticeElement) = + this.elements.compareTo(other.elements) +} + +/** + * The ordering will be as follows: BOTTOM (no information) < UNREACHABLE < REACHABLE (= Top of the + * lattice) + */ +enum class Reachability { + BOTTOM, + UNREACHABLE, + REACHABLE +} + +/** + * A state which actually holds a state for all [PropertyEdge]s, one only for declarations and one + * for ReturnStatements. + */ +class UnreachabilityState : State, Reachability>() diff --git a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluatorTest.kt b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluatorTest.kt index b29549de7c..f401058bde 100644 --- a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluatorTest.kt +++ b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluatorTest.kt @@ -167,21 +167,25 @@ class MultiValueEvaluatorTest { printB = main.bodyOrNull(1) assertNotNull(printB) + evaluator.clearPath() value = evaluator.evaluate(printB.arguments.firstOrNull()) as ConcreteNumberSet assertEquals(setOf(0, 1, 2), value.values) printB = main.bodyOrNull(2) assertNotNull(printB) + evaluator.clearPath() value = evaluator.evaluate(printB.arguments.firstOrNull()) as ConcreteNumberSet assertEquals(setOf(0, 1, 2, 4), value.values) printB = main.bodyOrNull(3) assertNotNull(printB) + evaluator.clearPath() value = evaluator.evaluate(printB.arguments.firstOrNull()) as ConcreteNumberSet assertEquals(setOf(-4, -2, -1, 0, 1, 2, 4), value.values) printB = main.bodyOrNull(4) assertNotNull(printB) + evaluator.clearPath() value = evaluator.evaluate(printB.arguments.firstOrNull()) as ConcreteNumberSet assertEquals(setOf(3, 6), value.values) } diff --git a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGPassTest.kt b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGPassTest.kt index 5f0b701a30..7d26b62d7e 100644 --- a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGPassTest.kt +++ b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGPassTest.kt @@ -34,10 +34,7 @@ import de.fraunhofer.aisec.cpg.graph.edge.Properties import de.fraunhofer.aisec.cpg.graph.statements.IfStatement import de.fraunhofer.aisec.cpg.graph.statements.WhileStatement import java.nio.file.Path -import kotlin.test.Test -import kotlin.test.assertFalse -import kotlin.test.assertNotNull -import kotlin.test.assertTrue +import kotlin.test.* import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.TestInstance @@ -80,8 +77,46 @@ class UnreachableEOGPassTest { val ifStatement = method.bodyOrNull() assertNotNull(ifStatement) - assertFalse(ifStatement.nextEOGEdges[0].getProperty(Properties.UNREACHABLE) as Boolean) - assertTrue(ifStatement.nextEOGEdges[1].getProperty(Properties.UNREACHABLE) as Boolean) + // Check if the then-branch is set as reachable including all the edges until reaching the + // print + val thenDecl = ifStatement.nextEOGEdges[0] + assertFalse(thenDecl.getProperty(Properties.UNREACHABLE) as Boolean) + assertEquals(1, thenDecl.end.nextEOGEdges.size) + // The "++" + val incOp = thenDecl.end.nextEOGEdges[0] + assertFalse(incOp.getProperty(Properties.UNREACHABLE) as Boolean) + assertEquals(1, incOp.end.nextEOGEdges.size) + // The compoundStmt + val thenCompound = incOp.end.nextEOGEdges[0] + assertFalse(thenCompound.getProperty(Properties.UNREACHABLE) as Boolean) + assertEquals(1, thenCompound.end.nextEOGEdges.size) + // There's the outgoing EOG edge to the statement after the branching + val thenExit = thenCompound.end.nextEOGEdges[0] + assertFalse(thenExit.getProperty(Properties.UNREACHABLE) as Boolean) + + // Check if the else-branch is set as unreachable including all the edges until reaching the + // print + val elseDecl = ifStatement.nextEOGEdges[1] + assertTrue(elseDecl.getProperty(Properties.UNREACHABLE) as Boolean) + assertEquals(1, elseDecl.end.nextEOGEdges.size) + // The "--" + val decOp = elseDecl.end.nextEOGEdges[0] + assertTrue(decOp.getProperty(Properties.UNREACHABLE) as Boolean) + assertEquals(1, decOp.end.nextEOGEdges.size) + // The compoundStmt + val elseCompound = decOp.end.nextEOGEdges[0] + assertTrue(elseCompound.getProperty(Properties.UNREACHABLE) as Boolean) + assertEquals(1, elseCompound.end.nextEOGEdges.size) + // There's the outgoing EOG edge to the statement after the branching + val elseExit = elseCompound.end.nextEOGEdges[0] + assertTrue(elseExit.getProperty(Properties.UNREACHABLE) as Boolean) + + // After the branching, it's reachable again. Check that we found the merge node and that we + // continue with reachable edges. + assertEquals(thenExit.end, elseExit.end) + val mergeNode = thenExit.end + assertEquals(1, mergeNode.nextEOGEdges.size) + assertFalse(mergeNode.nextEOGEdges[0].getProperty(Properties.UNREACHABLE) as Boolean) } @Test diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/LanguageTraits.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/LanguageTraits.kt index 956dbb8a81..cb7278c706 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/LanguageTraits.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/LanguageTraits.kt @@ -204,8 +204,22 @@ interface HasUnknownType : LanguageTrait { * evaluation if the logical result is already known: '&&', '||' in Java or 'and','or' in Python */ interface HasShortCircuitOperators : LanguageTrait { - // '&&', 'and', '^' + /** + * Operations which only execute the rhs of a binary operation if the lhs is `true`. Typically, + * these are `&&`, `and` or `^` + */ val conjunctiveOperators: List - // '||', 'or', 'v' + + /** + * Operations which only execute the rhs of a binary operation if the lhs is `false`. Typically, + * these are `||`, `or` or `v` + */ val disjunctiveOperators: List + + /** + * The union of [conjunctiveOperators] and [disjunctiveOperators], i.e., all binary operators of + * this language which result in some kind of branching behavior. + */ + val operatorCodes: Set + get() = conjunctiveOperators.union(disjunctiveOperators) } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/BranchingNode.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/BranchingNode.kt new file mode 100644 index 0000000000..9360beeb27 --- /dev/null +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/BranchingNode.kt @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2023, 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.graph + +/** A node triggering a conditional execution of other code. */ +interface BranchingNode { + /** The node which affects the next EOG edge. Typically, this is a condition or similar. */ + val branchedBy: Node? +} diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/ExpressionBuilder.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/ExpressionBuilder.kt index d072d2359a..230524e9f7 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/ExpressionBuilder.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/ExpressionBuilder.kt @@ -26,6 +26,7 @@ package de.fraunhofer.aisec.cpg.graph import de.fraunhofer.aisec.cpg.frontends.Handler +import de.fraunhofer.aisec.cpg.frontends.HasShortCircuitOperators import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend import de.fraunhofer.aisec.cpg.graph.Node.Companion.EMPTY_NAME import de.fraunhofer.aisec.cpg.graph.NodeBuilder.log @@ -57,10 +58,12 @@ fun MetadataProvider.newLiteral( } /** - * Creates a new [BinaryOperator]. The [MetadataProvider] receiver will be used to fill different - * meta-data using [Node.applyMetadata]. Calling this extension function outside of Kotlin requires - * an appropriate [MetadataProvider], such as a [LanguageFrontend] as an additional prepended - * argument. + * Creates a new [BinaryOperator] or a [ShortCircuitOperator] if the language implements + * [HasShortCircuitOperators] and if the [operatorCode] is contained in + * [HasShortCircuitOperators.operatorCodes]. The [MetadataProvider] receiver will be used to fill + * different meta-data using [Node.applyMetadata]. Calling this extension function outside of Kotlin + * requires an appropriate [MetadataProvider], such as a [LanguageFrontend] as an additional + * prepended argument. */ @JvmOverloads fun MetadataProvider.newBinaryOperator( @@ -68,7 +71,17 @@ fun MetadataProvider.newBinaryOperator( code: String? = null, rawNode: Any? = null ): BinaryOperator { - val node = BinaryOperator() + val node = + if ( + this is LanguageProvider && + (this.language as? HasShortCircuitOperators) + ?.operatorCodes + ?.contains(operatorCode) == true + ) { + ShortCircuitOperator() + } else { + BinaryOperator() + } node.applyMetadata(this, operatorCode, rawNode, code, true) node.operatorCode = operatorCode diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Node.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Node.kt index 788468fbb0..2b35842570 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Node.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Node.kt @@ -39,12 +39,14 @@ import de.fraunhofer.aisec.cpg.graph.declarations.TypedefDeclaration import de.fraunhofer.aisec.cpg.graph.edge.Properties import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.unwrap +import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdgeDelegate import de.fraunhofer.aisec.cpg.graph.scopes.GlobalScope import de.fraunhofer.aisec.cpg.graph.scopes.RecordScope import de.fraunhofer.aisec.cpg.graph.scopes.Scope import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker import de.fraunhofer.aisec.cpg.helpers.neo4j.LocationConverter import de.fraunhofer.aisec.cpg.helpers.neo4j.NameConverter +import de.fraunhofer.aisec.cpg.passes.ControlDependenceGraphPass import de.fraunhofer.aisec.cpg.passes.ControlFlowSensitiveDFGPass import de.fraunhofer.aisec.cpg.passes.DFGPass import de.fraunhofer.aisec.cpg.passes.EvaluationOrderGraphPass @@ -115,17 +117,39 @@ open class Node : IVisitable, Persistable, LanguageProvider, ScopeProvider @PopulatedByPass(FilenameMapper::class) var file: String? = null /** Incoming control flow edges. */ - @PopulatedByPass(EvaluationOrderGraphPass::class) @Relationship(value = "EOG", direction = Relationship.Direction.INCOMING) + @PopulatedByPass(EvaluationOrderGraphPass::class) var prevEOGEdges: MutableList> = ArrayList() protected set /** Outgoing control flow edges. */ - @PopulatedByPass(EvaluationOrderGraphPass::class) @Relationship(value = "EOG", direction = Relationship.Direction.OUTGOING) + @PopulatedByPass(EvaluationOrderGraphPass::class) var nextEOGEdges: MutableList> = ArrayList() protected set + /** + * The nodes which are control-flow dominated, i.e., the children of the Control Dependence + * Graph (CDG). + */ + @PopulatedByPass(ControlDependenceGraphPass::class) + @Relationship(value = "CDG", direction = Relationship.Direction.OUTGOING) + var nextCDGEdges: MutableList> = ArrayList() + protected set + + var nextCDG by PropertyEdgeDelegate(Node::nextCDGEdges, true) + + /** + * The nodes which dominate this node via the control-flow, i.e., the parents of the Control + * Dependence Graph (CDG). + */ + @PopulatedByPass(ControlDependenceGraphPass::class) + @Relationship(value = "CDG", direction = Relationship.Direction.INCOMING) + var prevCDGEdges: MutableList> = ArrayList() + protected set + + var prevCDG by PropertyEdgeDelegate(Node::prevCDGEdges, false) + /** * Virtual property to return a list of the node's children. Uses the [SubgraphWalker] to * retrieve the appropriate nodes. @@ -238,6 +262,12 @@ open class Node : IVisitable, Persistable, LanguageProvider, ScopeProvider prev.nextDFG.add(this) } + fun addPrevCDG(prev: Node) { + val edge = PropertyEdge(prev, this) + prevCDGEdges.add(edge) + prev.nextCDGEdges.add(edge) + } + fun addAllPrevDFG(prev: Collection) { prevDFG.addAll(prev) prev.forEach { it.nextDFG.add(this) } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt index 1ac97cad94..631d5319ad 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt @@ -459,6 +459,23 @@ fun LanguageFrontend.ifStmt(init: IfStatement.() -> Unit): IfStatement { return node } +/** + * Creates a new [ForEachStatement] in the Fluent Node DSL and adds it to the + * [StatementHolder.statements] of the nearest enclosing [StatementHolder]. The [init] block can be + * used to create further sub-nodes as well as configuring the created node itself. + */ +context(StatementHolder) + +fun LanguageFrontend.forEachStmt(init: ForEachStatement.() -> Unit): ForEachStatement { + val node = newForEachStatement() + + init(node) + + (this@StatementHolder) += node + + return node +} + /** * Creates a new [SwitchStatement] in the Fluent Node DSL and adds it to the * [StatementHolder.statements] of the nearest enclosing [StatementHolder]. The [init] block can be @@ -567,6 +584,20 @@ fun LanguageFrontend.elseIf(init: IfStatement.() -> Unit): IfStatement { */ context(WhileStatement) +fun LanguageFrontend.loopBody(init: CompoundStatement.() -> Unit): CompoundStatement { + val node = newCompoundStatement() + init(node) + statement = node + + return node +} +/** + * Creates a new [CompoundStatement] in the Fluent Node DSL and sets it to the + * [WhileStatement.statement] of the nearest enclosing [WhileStatement]. The [init] block can be + * used to create further sub-nodes as well as configuring the created node itself. + */ +context(ForEachStatement) + fun LanguageFrontend.loopBody(init: CompoundStatement.() -> Unit): CompoundStatement { val node = newCompoundStatement() init(node) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/CatchClause.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/CatchClause.kt index fd67cd5ba2..a07ab7525e 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/CatchClause.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/CatchClause.kt @@ -26,14 +26,19 @@ package de.fraunhofer.aisec.cpg.graph.statements import de.fraunhofer.aisec.cpg.graph.AST +import de.fraunhofer.aisec.cpg.graph.BranchingNode +import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration import java.util.Objects -class CatchClause : Statement() { +class CatchClause : Statement(), BranchingNode { @AST var parameter: VariableDeclaration? = null @AST var body: CompoundStatement? = null + override val branchedBy: Node? + get() = parameter + override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is CatchClause) return false diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ForEachStatement.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ForEachStatement.kt index 702547223d..05cce46a10 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ForEachStatement.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ForEachStatement.kt @@ -25,15 +25,24 @@ */ package de.fraunhofer.aisec.cpg.graph.statements -import de.fraunhofer.aisec.cpg.graph.AST +import de.fraunhofer.aisec.cpg.graph.* +import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge +import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression import java.util.Objects -class ForEachStatement : Statement() { +class ForEachStatement : Statement(), BranchingNode, StatementHolder { /** * This field contains the iteration variable of the loop. It can be either a new variable * declaration or a reference to an existing variable. */ - @AST var variable: Statement? = null + @AST + var variable: Statement? = null + set(value) { + if (value is DeclaredReferenceExpression) { + value.access = AccessValues.WRITE + } + field = value + } /** This field contains the iteration subject of the loop. */ @AST var iterable: Statement? = null @@ -41,6 +50,38 @@ class ForEachStatement : Statement() { /** This field contains the body of the loop. */ @AST var statement: Statement? = null + override val branchedBy: Node? + get() = iterable + + override var statementEdges: MutableList> + get() { + val statements = mutableListOf>() + variable?.let { statements.add(PropertyEdge(this, it)) } + iterable?.let { statements.add(PropertyEdge(this, it)) } + statement?.let { statements.add(PropertyEdge(this, it)) } + return statements + } + set(value) { + // Nothing to do here + } + + override fun addStatement(s: Statement) { + if (variable == null) { + variable = s + } else if (iterable == null) { + iterable = s + } else if (statement == null) { + statement = s + } else if (statement !is CompoundStatement) { + val newStmt = newCompoundStatement() + statement?.let { newStmt.addStatement(it) } + newStmt.addStatement(s) + statement = newStmt + } else { + (statement as? CompoundStatement)?.addStatement(s) + } + } + override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is ForEachStatement) return false diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ForStatement.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ForStatement.kt index 939d8b0238..24a3a1af34 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ForStatement.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ForStatement.kt @@ -26,11 +26,13 @@ package de.fraunhofer.aisec.cpg.graph.statements import de.fraunhofer.aisec.cpg.graph.AST +import de.fraunhofer.aisec.cpg.graph.BranchingNode +import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.declarations.Declaration import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression import java.util.* -class ForStatement : Statement() { +class ForStatement : Statement(), BranchingNode { @AST var statement: Statement? = null @AST var initializerStatement: Statement? = null @@ -41,6 +43,9 @@ class ForStatement : Statement() { @AST var iterationStatement: Statement? = null + override val branchedBy: Node? + get() = condition ?: conditionDeclaration + override fun equals(other: Any?): Boolean { if (this === other) { return true diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/IfStatement.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/IfStatement.kt index 53dcc68045..602348d241 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/IfStatement.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/IfStatement.kt @@ -27,13 +27,15 @@ package de.fraunhofer.aisec.cpg.graph.statements import de.fraunhofer.aisec.cpg.graph.AST import de.fraunhofer.aisec.cpg.graph.ArgumentHolder +import de.fraunhofer.aisec.cpg.graph.BranchingNode +import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.declarations.Declaration import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression import java.util.* import org.apache.commons.lang3.builder.ToStringBuilder /** Represents a condition control flow statement, usually indicating by `If`. */ -class IfStatement : Statement(), ArgumentHolder { +class IfStatement : Statement(), BranchingNode, ArgumentHolder { /** C++ initializer statement. */ @AST var initializerStatement: Statement? = null @@ -43,6 +45,9 @@ class IfStatement : Statement(), ArgumentHolder { /** The condition to be evaluated. */ @AST var condition: Expression? = null + override val branchedBy: Node? + get() = condition ?: conditionDeclaration + /** C++ constexpr construct. */ var isConstExpression = false diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/SwitchStatement.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/SwitchStatement.kt index 5699a85358..f9e39ca7e4 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/SwitchStatement.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/SwitchStatement.kt @@ -26,6 +26,8 @@ package de.fraunhofer.aisec.cpg.graph.statements import de.fraunhofer.aisec.cpg.graph.AST +import de.fraunhofer.aisec.cpg.graph.BranchingNode +import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.declarations.Declaration import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression import java.util.Objects @@ -35,7 +37,7 @@ import java.util.Objects * and default statements. Break statements break out of the switch and labeled breaks in JAva are * handled properly. */ -class SwitchStatement : Statement() { +class SwitchStatement : Statement(), BranchingNode { /** Selector that determines the case/default statement of the subsequent execution */ @AST var selector: Expression? = null @@ -51,6 +53,9 @@ class SwitchStatement : Statement() { */ @AST var statement: Statement? = null + override val branchedBy: Node? + get() = selector + override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is SwitchStatement) return false diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/WhileStatement.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/WhileStatement.kt index faf23cf27a..14b08f2180 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/WhileStatement.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/WhileStatement.kt @@ -27,13 +27,15 @@ package de.fraunhofer.aisec.cpg.graph.statements import de.fraunhofer.aisec.cpg.graph.AST import de.fraunhofer.aisec.cpg.graph.ArgumentHolder +import de.fraunhofer.aisec.cpg.graph.BranchingNode +import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.declarations.Declaration import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression import java.util.* import org.apache.commons.lang3.builder.ToStringBuilder /** Represents a conditional loop statement of the form: `while(...){...}`. */ -class WhileStatement : Statement(), ArgumentHolder { +class WhileStatement : Statement(), BranchingNode, ArgumentHolder { /** C++ allows defining a declaration instead of a pure logical expression as condition */ @AST var conditionDeclaration: Declaration? = null @@ -46,6 +48,9 @@ class WhileStatement : Statement(), ArgumentHolder { */ @AST var statement: Statement? = null + override val branchedBy: Node? + get() = condition ?: conditionDeclaration + override fun toString(): String { return ToStringBuilder(this, TO_STRING_STYLE) .appendSuper(super.toString()) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/BinaryOperator.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/BinaryOperator.kt index c3185f0954..1b121af01b 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/BinaryOperator.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/BinaryOperator.kt @@ -37,7 +37,7 @@ import org.apache.commons.lang3.builder.ToStringBuilder * A binary operation expression, such as "a + b". It consists of a left hand expression (lhs), a * right hand expression (rhs) and an operatorCode. */ -class BinaryOperator : +open class BinaryOperator : Expression(), HasType.TypeListener, AssignmentHolder, HasBase, ArgumentHolder { /** The left-hand expression. */ @AST diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ConditionalExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ConditionalExpression.kt index e53e5539aa..bd06015e53 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ConditionalExpression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ConditionalExpression.kt @@ -35,7 +35,7 @@ import org.apache.commons.lang3.builder.ToStringBuilder * Represents an expression containing a ternary operator: `var x = condition ? valueIfTrue : * valueIfFalse`; */ -class ConditionalExpression : Expression(), HasType.TypeListener, ArgumentHolder { +class ConditionalExpression : Expression(), HasType.TypeListener, ArgumentHolder, BranchingNode { @AST var condition: Expression = ProblemExpression("could not parse condition expression") @AST @@ -93,6 +93,9 @@ class ConditionalExpression : Expression(), HasType.TypeListener, ArgumentHolder .build() } + override val branchedBy: Node + get() = condition + override fun addArgument(expression: Expression) { // Do nothing } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ShortCircuitOperator.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ShortCircuitOperator.kt new file mode 100644 index 0000000000..1829a2d993 --- /dev/null +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ShortCircuitOperator.kt @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2023, 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.graph.statements.expressions + +import de.fraunhofer.aisec.cpg.graph.BranchingNode +import de.fraunhofer.aisec.cpg.graph.Node + +/** + * A [BinaryOperator] which only evaluates [BinaryOperator.rhs] if [BinaryOperator.lhs] fulfils some + * condition. For the operators in + * [de.fraunhofer.aisec.cpg.frontends.HasShortCircuitOperators.conjunctiveOperators], the rhs has to + * evaluate to "true" or so to continue on the lhs, whereas for the operators in + * [de.fraunhofer.aisec.cpg.frontends.HasShortCircuitOperators.disjunctiveOperators], the lhs has to + * evaluate to "false" (or similar). + */ +class ShortCircuitOperator : BinaryOperator(), BranchingNode { + override val branchedBy: Node + get() = lhs +} diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/EOGWorklist.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/EOGWorklist.kt new file mode 100644 index 0000000000..d7238dc397 --- /dev/null +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/EOGWorklist.kt @@ -0,0 +1,282 @@ +/* + * Copyright (c) 2023, 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.helpers + +import de.fraunhofer.aisec.cpg.graph.Node +import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge +import java.util.IdentityHashMap + +/** + * A complete lattice is an ordered structure of values of type [T]. [T] could be anything, e.g., a + * set, a new data structure (like a range), or anything else. [T] depends on the analysis and + * typically has to abstract the value for the specific purpose. + * + * This class is actually used to hold individual instances of the lattice's elements and to compute + * bigger elements depending on these two elements. + * + * Implementations of this class have to implement the comparator, the least upper bound of two + * lattices. + */ +abstract class LatticeElement(open val elements: T) : Comparable> { + /** + * Computes the least upper bound of this lattice and [other]. It returns a new object and does + * not modify either of the objects. + */ + abstract fun lub(other: LatticeElement): LatticeElement + + /** Duplicates the object, i.e., makes a deep copy. */ + abstract fun duplicate(): LatticeElement +} + +/** + * Implements the [LatticeElement] for a lattice over a set of nodes. The lattice itself is + * constructed by the powerset. + */ +class PowersetLattice(override val elements: Set) : LatticeElement>(elements) { + override fun lub(other: LatticeElement>) = + PowersetLattice((other.elements).union(this.elements)) + + override fun duplicate() = PowersetLattice(this.elements.toSet()) + + override fun compareTo(other: LatticeElement>): Int { + return if (this.elements.containsAll(other.elements)) { + if (this.elements.size > (other.elements.size)) 1 else 0 + } else { + -1 + } + } +} + +/** + * Stores the current state. I.e., it maps [K] (e.g. a [Node] or [PropertyEdge]) to a + * [LatticeElement]. It provides some useful functions e.g. to check if the mapping has to be + * updated (e.g. because there are new nodes or because a new lattice element is bigger than the old + * one). + */ +open class State : IdentityHashMap>() { + + /** + * It updates this state by adding all new nodes in [other] to `this` and by computing the least + * upper bound for each entry. + * + * Returns this and a flag which states if there was any update necessary (or if `this` is equal + * before and after running the method). + */ + open fun lub(other: State): Pair, Boolean> { + var update = false + for ((node, newLattice) in other) { + update = push(node, newLattice) || update + } + return Pair(this, update) + } + + /** + * Checks if an update is necessary, i.e., if [other] contains nodes which are not present in + * `this` and if the lattice element of a node in [other] "is bigger" than the respective + * lattice element in `this`. It does not modify anything. + */ + open fun needsUpdate(other: State): Boolean { + var update = false + for ((node, newLattice) in other) { + val current = this[node] + update = update || current == null || newLattice > current + } + return update + } + + /** Deep copies this object. */ + open fun duplicate(): State { + val clone = State() + for ((key, value) in this) { + clone[key] = value.duplicate() + } + return clone + } + + /** + * Adds a new mapping from [newNode] to (a copy of) [newLatticeElement] to this object if + * [newNode] does not exist in this state yet. If it already exists, it computes the least upper + * bound of [newLatticeElement] and the current one for [newNode]. It returns if the state has + * changed. + */ + open fun push(newNode: K, newLatticeElement: LatticeElement?): Boolean { + if (newLatticeElement == null) { + return false + } + val current = this[newNode] + if (current != null && current >= newLatticeElement) { + // newLattice is "smaller" than the currently stored one. We don't add it anything. + return false + } else if (current != null) { + // newLattice is "bigger" than the currently stored one. We update it to the least + // upper bound + this[newNode] = newLatticeElement.lub(current) + } else { + this[newNode] = newLatticeElement.duplicate() + } + return true + } +} + +/** + * A worklist. Essentially, it stores mappings of nodes to the states which are available there and + * determines which nodes have to be analyzed. + */ +class Worklist() { + /** A mapping of nodes to the state which is currently available there. */ + var globalState: MutableMap> = mutableMapOf() + private set + + /** A list of all nodes which have already been visited. */ + private val alreadySeen = IdentitySet() + + constructor(globalState: MutableMap> = mutableMapOf()) : this() { + this.globalState = globalState + } + + /** + * The actual worklist, i.e., elements which still have to be analyzed and the state which + * should be considered there. + */ + private val nodeOrder: MutableList>> = mutableListOf() + + /** + * Adds [newNode] and the [state] to the [globalState] (i.e., computes the [State.lub] of the + * current state there and [state]). Returns true if there was an update. + */ + fun update(newNode: K, state: State): Boolean { + val (newGlobalState, update) = globalState[newNode]?.lub(state) ?: Pair(state, true) + if (update) { + globalState[newNode] = newGlobalState + } + return update + } + + /** + * Pushes [newNode] and the [state] to the worklist or updates the currently available entry for + * the node. Returns `true` if there was a change which means that the node has to be analyzed. + * If it returns `false`, the [newNode] wasn't added to the worklist as the state didn't change. + */ + fun push(newNode: K, state: State): Boolean { + val currentEntry = nodeOrder.find { it.first == newNode } + val update: Boolean + val newEntry = + if (currentEntry != null) { + val (newState, update2) = currentEntry.second.lub(state) + update = update2 + if (update) { + nodeOrder.remove(currentEntry) + } + Pair(currentEntry.first, newState) + } else { + update = true + Pair(newNode, state) + } + if (update) nodeOrder.add(newEntry) + return update + } + + /** Determines if there are still elements to analyze */ + fun isNotEmpty() = nodeOrder.isNotEmpty() + + /** Determines if there are no more elements to analyze */ + fun isEmpty() = nodeOrder.isEmpty() + + /** Removes a [Node] from the worklist and returns the [Node] together with its [State] */ + fun pop(): Pair> { + val node = nodeOrder.removeFirst() + alreadySeen.add(node.first) + return node + } + + /** Checks if [currentNode] has already been visited before. */ + fun hasAlreadySeen(currentNode: K) = currentNode in alreadySeen + + /** Computes the meet over paths for all the states in [globalState]. */ + fun mop(): State? { + val firstKey = globalState.keys.firstOrNull() + val state = globalState[firstKey] + for ((_, v) in globalState) { + state?.lub(v) + } + + return state + } +} + +/** + * Iterates through the worklist of the Evaluation Order Graph starting at [startNode] and with the + * [State] [startState]. For each node, the [transformation] is applied which should update the + * state. + * + * [transformation] receives the current [Node] popped from the worklist, the [State] at this node + * which is considered for this analysis and even the current [Worklist]. The worklist is given if + * we have to add more elements out-of-order e.g. because the EOG is traversed in an order which is + * not useful for this analysis. The [transformation] has to return the updated [State]. + */ +inline fun iterateEOG( + startNode: K, + startState: State, + transformation: (K, State, Worklist) -> State +): State? { + val worklist = Worklist(mutableMapOf(Pair(startNode, startState))) + worklist.push(startNode, startState) + + while (worklist.isNotEmpty()) { + val (nextNode, state) = worklist.pop() + + val newState = transformation(nextNode, state.duplicate(), worklist) + if (worklist.update(nextNode, newState)) { + nextNode.nextEOG.forEach { if (it is K) worklist.push(it, newState.duplicate()) } + } + } + return worklist.mop() +} + +inline fun , N : Any, V> iterateEOG( + startEdges: List, + startState: State, + transformation: (K, State, Worklist) -> State +): State? { + val globalState = mutableMapOf>() + for (startEdge in startEdges) { + globalState[startEdge] = startState + } + val worklist = Worklist(globalState) + startEdges.forEach { worklist.push(it, startState) } + + while (worklist.isNotEmpty()) { + val (nextEdge, state) = worklist.pop() + + val newState = transformation(nextEdge, state.duplicate(), worklist) + if (worklist.update(nextEdge, newState)) { + nextEdge.end.nextEOGEdges.forEach { + if (it is K) worklist.push(it, newState.duplicate()) + } + } + } + return worklist.mop() +} diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPass.kt new file mode 100644 index 0000000000..6ef8833b2d --- /dev/null +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPass.kt @@ -0,0 +1,266 @@ +/* + * Copyright (c) 2023, 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.BranchingNode +import de.fraunhofer.aisec.cpg.graph.Node +import de.fraunhofer.aisec.cpg.graph.allChildren +import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration +import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration +import de.fraunhofer.aisec.cpg.graph.edge.Properties +import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge +import de.fraunhofer.aisec.cpg.graph.functions +import de.fraunhofer.aisec.cpg.graph.statements.IfStatement +import de.fraunhofer.aisec.cpg.graph.statements.expressions.ConditionalExpression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.ShortCircuitOperator +import de.fraunhofer.aisec.cpg.helpers.* +import de.fraunhofer.aisec.cpg.passes.order.DependsOn + +/** This pass builds the Control Dependence Graph (CDG) by iterating through the EOG. */ +@DependsOn(EvaluationOrderGraphPass::class) +open class ControlDependenceGraphPass(ctx: TranslationContext) : TranslationUnitPass(ctx) { + override fun cleanup() { + // Nothing to do + } + + override fun accept(tu: TranslationUnitDeclaration) { + tu.functions.forEach(::handle) + } + + /** + * Computes the CDG for the given [functionDecl]. It performs the following steps: + * 1) Compute the "parent branching node" for each node and through which path the node is + * reached + * 2) Find out which branch of a [BranchingNode] is actually conditional. The other ones aren't. + * 3) For each node: 3.a) Check if the node is reachable through an unconditional path of its + * parent [BranchingNode] or through all the conditional paths. 3.b) Move the node "one layer + * up" by finding the parent node of the current [BranchingNode] and changing it to this + * parent node and the path(s) through which the [BranchingNode] node is reachable. 3.c) + * Repeat step 3) until you cannot move the node upwards in the CDG anymore. + */ + private fun handle(functionDecl: FunctionDeclaration) { + // Maps nodes to their "cdg parent" (i.e. the dominator) and also has the information + // through which path it is reached. If all outgoing paths of the node's dominator result in + // the node, we use the dominator's state instead (i.e., we move the node one layer upwards) + val startState = PrevEOGState() + startState.push( + functionDecl, + PrevEOGLattice(mapOf(Pair(functionDecl, setOf(functionDecl)))) + ) + val finalState = iterateEOG(functionDecl.nextEOGEdges, startState, ::handleEdge) ?: return + + val branchingNodeConditionals = getBranchingNodeConditions(functionDecl) + + // Collect the information, identify merge points, etc. This is not really efficient yet :( + for ((node, dominatorPaths) in finalState) { + val dominatorsList = + dominatorPaths.elements.entries + .map { (k, v) -> Pair(k, v.toMutableSet()) } + .toMutableList() + val finalDominators = mutableListOf>>() + while (dominatorsList.isNotEmpty()) { + val (k, v) = dominatorsList.removeFirst() + if (k != functionDecl && v.containsAll(branchingNodeConditionals[k] ?: setOf())) { + // We are reachable from all the branches of branch. Add this parent to the + // worklist or update an existing entry. Also consider already existing entries + // in finalDominators list and update it (if necessary) + val newDominatorMap = finalState[k]?.elements + newDominatorMap?.forEach { (newK, newV) -> + if (dominatorsList.any { it.first == newK }) { + // Entry exists => update it + dominatorsList.first { it.first == newK }.second.addAll(newV) + } else if (finalDominators.any { it.first == newK }) { + // Entry in final dominators => Delete it and add it to the worklist + // (but only if something changed) + val entry = finalDominators.first { it.first == newK } + finalDominators.remove(entry) + val update = entry.second.addAll(newV) + if (update) dominatorsList.add(entry) else finalDominators.add(entry) + } else { + // We don't have an entry yet => add a new one + dominatorsList.add(Pair(newK, newV.toMutableSet())) + } + } + } else { + // Node is not reachable from all branches => k dominates node. Add to + // finalDominators. + finalDominators.add(Pair(k, v)) + } + } + // We have all the dominators of this node and potentially traversed the graph + // "upwards". Add the CDG edges + finalDominators.filter { (k, _) -> k != node }.forEach { (k, _) -> node.addPrevCDG(k) } + } + } + + /* + * For a branching node, we identify which path(s) have to be found to be in a "merging point". + * There are two options: + * 1) There's a path which is executed independent of the branch (e.g. this is the case for an if-statement without an else-branch). + * 2) A node can be reached from all conditional branches. + * + * This method collects the merging points. It also includes the function declaration itself. + */ + private fun getBranchingNodeConditions(functionDecl: FunctionDeclaration) = + mapOf( + // For the function declaration, there's only the path through the function declaration + // itself. + Pair(functionDecl, setOf(functionDecl)), + *functionDecl + .allChildren() + .map { branchingNode -> + val mergingPoints = + if ( + (branchingNode as? Node)?.nextEOGEdges?.any { + !it.isConditionalBranch() + } == true + ) { + // There's an unconditional path (case 1), so when reaching this branch, + // we're done. Collect all (=1) unconditional branches. + (branchingNode as? Node) + ?.nextEOGEdges + ?.filter { !it.isConditionalBranch() } + ?.map { it.end } + ?.toSet() + } else { + // All branches are executed based on some condition (case 2), so we + // collect all these branches. + (branchingNode as Node).nextEOGEdges.map { it.end }.toSet() + } + // Map this branching node to its merging points + Pair(branchingNode as Node, mergingPoints) + } + .toTypedArray() + ) +} + +/** + * This method is executed for each EOG edge which is in the worklist. [currentEdge] is the edge to + * process, [currentState] contains the state which was observed before arriving here. + * + * This method modifies the state for the next eog edge as follows: + * - If [currentEdge] starts in a [BranchingNode], the end node depends on the start node. We modify + * the state to express that "the end node depends on the start node and is reachable through the + * path starting at the end node". + * - For all other starting nodes, we copy the state of the start node to the end node. + * + * Returns the updated state and true because we always expect an update of the state. + */ +fun handleEdge( + currentEdge: PropertyEdge, + currentState: State>>, + currentWorklist: Worklist, Node, Map>> +): State>> { + // Check if we start in a branching node and if this edge leads to the conditional + // branch. In this case, the next node will move "one layer downwards" in the CDG. + if (currentEdge.start is BranchingNode) { // && currentEdge.isConditionalBranch()) { + // We start in a branching node and end in one of the branches, so we have the + // following state: + // for the branching node "start", we have a path through "end". + currentState.push( + currentEdge.end, + PrevEOGLattice(mapOf(Pair(currentEdge.start, setOf(currentEdge.end)))) + ) + } else { + // We did not start in a branching node, so for the next node, we have the same path + // (last branching + first end node) as for the start node of this edge. + // If there is no state for the start node (most likely, this is the case for the + // first edge in a function), we generate a new state where we start in "start" end + // have "end" as the first node in the "branch". + val state = + PrevEOGLattice( + currentState[currentEdge.start]?.elements + ?: mapOf(Pair(currentEdge.start, setOf(currentEdge.end))) + ) + currentState.push(currentEdge.end, state) + } + return currentState +} + +/** + * For all types I've seen so far, the "true" branch is executed conditionally. + * + * For if-statements, the BRANCH property is set to "false" for the "else" branch (which is also + * executed conditionally) and is not set in the code after an if-statement if there's no else + * branch (which is also always executed). For all other nodes, the "false" branch is the code after + * the loop or so (i.e., the unconditionally executed path). + * + * Note: This method does not account for return statements in the conditional part or endless loops + * where the other branch is actually also conditionally executed (or not). It should be easy to + * change this if we do not want this behavior (just remove the condition on the start node of the + * "false" branch). + */ +private fun PropertyEdge.isConditionalBranch(): Boolean { + return if (this.getProperty(Properties.BRANCH) == true) { + true + } else + (this.start is IfStatement || + this.start is ConditionalExpression || + this.start is ShortCircuitOperator) && this.getProperty(Properties.BRANCH) == false +} + +/** + * Implements the [LatticeElement] over a set of nodes and their set of "nextEOG" nodes which reach + * this node. + */ +class PrevEOGLattice(override val elements: Map>) : + LatticeElement>>(elements) { + + override fun lub( + other: LatticeElement>> + ): LatticeElement>> { + val newMap = (other.elements).mapValues { (_, v) -> v.toMutableSet() }.toMutableMap() + for ((key, value) in this.elements) { + newMap.computeIfAbsent(key, ::mutableSetOf).addAll(value) + } + return PrevEOGLattice(newMap) + } + + override fun duplicate() = PrevEOGLattice(this.elements.toMap()) + + override fun compareTo(other: LatticeElement>>): Int { + return if ( + this.elements.keys.containsAll(other.elements.keys) && + this.elements.all { (k, v) -> v.containsAll(other.elements[k] ?: setOf()) } + ) { + if ( + this.elements.keys.size > (other.elements.keys.size) || + this.elements.any { (k, v) -> v.size > (other.elements[k] ?: setOf()).size } + ) + 1 + else 0 + } else { + -1 + } + } +} + +/** + * A state which actually holds a state for all [PropertyEdge]s. It maps the node to its + * [BranchingNode]-parent and the path through which it is reached. + */ +class PrevEOGState : State>>() diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt index 0eca2ac509..9182f2c19e 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt @@ -29,12 +29,10 @@ import de.fraunhofer.aisec.cpg.TranslationContext import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.declarations.* import de.fraunhofer.aisec.cpg.graph.edge.Properties +import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge import de.fraunhofer.aisec.cpg.graph.statements.* -import de.fraunhofer.aisec.cpg.graph.statements.expressions.BinaryOperator -import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression -import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression -import de.fraunhofer.aisec.cpg.graph.statements.expressions.UnaryOperator -import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker.IterativeGraphWalker +import de.fraunhofer.aisec.cpg.graph.statements.expressions.* +import de.fraunhofer.aisec.cpg.helpers.* import de.fraunhofer.aisec.cpg.passes.order.DependsOn /** @@ -52,9 +50,7 @@ open class ControlFlowSensitiveDFGPass(ctx: TranslationContext) : TranslationUni } override fun accept(tu: TranslationUnitDeclaration) { - val walker = IterativeGraphWalker() - walker.registerOnNodeVisit(::handle) - walker.iterate(tu) + tu.functions.forEach(::handle) } /** @@ -65,7 +61,23 @@ open class ControlFlowSensitiveDFGPass(ctx: TranslationContext) : TranslationUni protected fun handle(node: Node) { if (node is FunctionDeclaration) { clearFlowsOfVariableDeclarations(node) - handleStatementHolder(node) + val startState = DFGPassState>() + startState.declarationsState.push(node, PowersetLattice(setOf())) + val finalState = + iterateEOG(node.nextEOGEdges, startState, ::transfer) as? DFGPassState ?: return + + removeUnreachableImplicitReturnStatement( + node, + finalState.returnStatements.values.flatMap { + it.elements.filterIsInstance() + } + ) + + for ((key, value) in finalState.generalState) { + key.addAllPrevDFG( + value.elements.filterNot { it is VariableDeclaration && key == it } + ) + } } } @@ -81,291 +93,152 @@ open class ControlFlowSensitiveDFGPass(ctx: TranslationContext) : TranslationUni } /** - * Performs a forward analysis through the EOG to collect all possible writes to a variable and - * adds them to the DFG edges to the read operations of that variable. We differentiate between - * the flows based on the following types of statements/expressions: - * - VariableDeclaration with an initializer - * - Unary operators ++ and -- - * - Assignments of the form "variable = rhs" - * - Assignments with an operation e.g. of the form "variable += rhs" - * - Read operations on a variable + * Computes the previous write access of [currentEdge].end if it is a + * [DeclaredReferenceExpression] or [ValueDeclaration] based on the given [state] (which maps + * all variables to its last write instruction). It also updates the [state] if + * [currentEdge].end performs a write-operation to a variable. + * + * It further determines unnecessary implicit return statement which are added by some frontends + * even if every path reaching this point already contains a return statement. */ - protected fun handleStatementHolder(node: Node) { - // The list of nodes that we have to consider and the last write operations to the different - // variables. - val worklist = - mutableListOf>>>( - Pair(node, mutableMapOf()) - ) - - val alreadyProcessed = mutableSetOf>>() - - // Different points which could be the cause of a loop (in a non-broken program). We - // consider ForStatements, WhileStatements, ForEachStatements, DoStatements and - // GotoStatements - val loopPoints = mutableMapOf>>() - - val returnStatements = mutableSetOf() - - // Iterate through the worklist - while (worklist.isNotEmpty()) { - // The node we will analyze now and the map of the last write statements to a variable. - val (currentNode, previousWrites) = worklist.removeFirst() - if ( - !alreadyProcessed.add( - Pair(currentNode, previousWrites.mapValues { (_, v) -> v.last() }) - ) - ) { - // The entry did already exist. This means that the changes won't have any effects - // and we don't have to run the loop. - continue - } - // We will set this if we write to a variable - var writtenDecl: Declaration? = null - var currentWritten = currentNode - - val initializer = (currentNode as? VariableDeclaration)?.initializer - if (initializer != null) { - // A variable declaration with an initializer => The initializer flows to the - // declaration. - currentNode.addPrevDFG(initializer) - - // We wrote something to this variable declaration - writtenDecl = currentNode - - // Add the node to the list of previous write nodes in this path - previousWrites[currentNode] = mutableListOf(currentNode) - } else if (isIncOrDec(currentNode)) { - // Increment or decrement => Add the prevWrite of the input to the input. After the - // operation, the prevWrite of the input's variable is this node. - val input = (currentNode as UnaryOperator).input as DeclaredReferenceExpression - // We write to the variable in the input - writtenDecl = input.refersTo - - if (writtenDecl != null) { - previousWrites[writtenDecl]?.lastOrNull()?.let { input.addPrevDFG(it) } - - // TODO: Do we want to have a flow from the input back to the input? This can - // cause problems if the DFG is not iterated through appropriately. The - // following line would remove it: - // currentNode.removeNextDFG(input) - - // Add the whole node to the list of previous write nodes in this path. This - // prevents some weird circular dependencies. - previousWrites - .computeIfAbsent(writtenDecl, ::mutableListOf) - .add(currentNode.input) - currentWritten = currentNode.input - } - } else if (isSimpleAssignment(currentNode)) { - // We write to the target => the rhs flows to the lhs - (currentNode as BinaryOperator).rhs.let { currentNode.lhs.addPrevDFG(it) } - - // Only the lhs is the last write statement here and the variable which is written - // to. - writtenDecl = (currentNode.lhs as DeclaredReferenceExpression).refersTo - - if (writtenDecl != null) { - previousWrites - .computeIfAbsent(writtenDecl, ::mutableListOf) - .add(currentNode.lhs as DeclaredReferenceExpression) - currentWritten = currentNode.lhs as DeclaredReferenceExpression - } - } else if (isCompoundAssignment(currentNode)) { - // We write to the lhs, but it also serves as an input => We first get all previous - // writes to the lhs and then add the flow from lhs and rhs to the current node. - - // The write operation goes to the variable in the lhs - writtenDecl = - ((currentNode as BinaryOperator).lhs as? DeclaredReferenceExpression)?.refersTo - - if (writtenDecl != null) { - // Data flows from the last writes to the lhs variable to this node - previousWrites[writtenDecl]?.lastOrNull()?.let { - currentNode.lhs.addPrevDFG(it) + protected fun transfer( + currentEdge: PropertyEdge, + state: State>, + worklist: Worklist, Node, Set> + ): State> { + // We will set this if we write to a variable + val writtenDecl: Declaration? + val currentNode = currentEdge.end + + val doubleState = state as DFGPassState + + val initializer = (currentNode as? VariableDeclaration)?.initializer + if (initializer != null) { + // A variable declaration with an initializer => The initializer flows to the + // declaration. + // We also wrote something to this variable declaration + state.push(currentNode, PowersetLattice(setOf(initializer))) + + doubleState.pushToDeclarationsState(currentNode, PowersetLattice(setOf(currentNode))) + } else if (currentNode is AssignExpression) { + // It's an assignment which can have one or multiple things on the lhs and on the + // rhs. The lhs could be a declaration or a reference (or multiple of these things). + // The rhs can be anything. The rhs flows to the respective lhs. To identify the + // correct mapping, we use the "assignments" property which already searches for us. + currentNode.assignments.forEach { assignment -> + // This was the last write to the respective declaration. + (assignment.target as? Declaration + ?: (assignment.target as? DeclaredReferenceExpression)?.refersTo) + ?.let { + doubleState.declarationsState[it] = + PowersetLattice(setOf(assignment.target as Node)) } - currentNode.addPrevDFG(currentNode.lhs) + } + } else if (isIncOrDec(currentNode)) { + // Increment or decrement => Add the prevWrite of the input to the input. After the + // operation, the prevWrite of the input's variable is this node. + val input = (currentNode as UnaryOperator).input as DeclaredReferenceExpression + // We write to the variable in the input + writtenDecl = input.refersTo + + if (writtenDecl != null) { + state.push(input, doubleState.declarationsState[writtenDecl]) + doubleState.declarationsState[writtenDecl] = PowersetLattice(setOf(input)) + } + } else if (isSimpleAssignment(currentNode)) { + // Only the lhs is the last write statement here and the variable which is written + // to. + writtenDecl = + ((currentNode as BinaryOperator).lhs as DeclaredReferenceExpression).refersTo + + if (writtenDecl != null) { + doubleState.declarationsState[writtenDecl] = PowersetLattice(setOf(currentNode.lhs)) + } + } else if (isCompoundAssignment(currentNode)) { + // We write to the lhs, but it also serves as an input => We first get all previous + // writes to the lhs and then add the flow from lhs and rhs to the current node. - // Data flows from whatever is the rhs to this node - currentNode.rhs.let { currentNode.addPrevDFG(it) } + // The write operation goes to the variable in the lhs + writtenDecl = + ((currentNode as BinaryOperator).lhs as? DeclaredReferenceExpression)?.refersTo - // TODO: Similar to the ++ case: Should the DFG edge go back to the reference? - // If it shouldn't, remove the following statement: - currentNode.lhs.addPrevDFG(currentNode) + if (writtenDecl != null) { + // Data flows from the last writes to the lhs variable to this node + state.push(currentNode.lhs, doubleState.declarationsState[writtenDecl]) - // The whole current node is the place of the last update, not (only) the lhs! - previousWrites - .computeIfAbsent(writtenDecl, ::mutableListOf) - .add(currentNode.lhs) - currentWritten = currentNode.lhs - } - } else if ((currentNode as? DeclaredReferenceExpression)?.access == AccessValues.READ) { - // We only read the variable => Get previous write which have been collected in the - // other steps - previousWrites[currentNode.refersTo]?.lastOrNull()?.let { - currentNode.addPrevDFG(it) + // The whole current node is the place of the last update, not (only) the lhs! + doubleState.declarationsState[writtenDecl] = PowersetLattice(setOf(currentNode.lhs)) + } + } else if ( + (currentNode as? DeclaredReferenceExpression)?.access == AccessValues.READ && + currentNode.refersTo is VariableDeclaration + ) { + // We can only find a change if there's a state for the variable + doubleState.declarationsState[currentNode.refersTo]?.let { + // We only read the variable => Get previous write which have been collected in + // the other steps + state.push(currentNode, it) + } + } else if (currentNode is ForEachStatement && currentNode.variable != null) { + // The VariableDeclaration in the ForEachStatement doesn't have an initializer, so + // the "normal" case won't work. We handle this case separately here... + // This is what we write to the declaration + val iterable = currentNode.iterable as? Expression + + val writtenTo = + when (currentNode.variable) { + is DeclarationStatement -> + (currentNode.variable as DeclarationStatement).singleDeclaration + else -> currentNode.variable } - } else if (currentNode is ForEachStatement && currentNode.variable != null) { - // The VariableDeclaration in the ForEachStatement doesn't have an initializer, so - // the "normal" case won't work. We handle this case separately here... - // This is what we write to the declaration - val iterable = currentNode.iterable as? Expression - val writtenTo = - when (currentNode.variable) { - is DeclarationStatement -> - (currentNode.variable as DeclarationStatement).singleDeclaration - else -> currentNode.variable + // We wrote something to this variable declaration + writtenDecl = + when (writtenTo) { + is Declaration -> writtenTo + is DeclaredReferenceExpression -> writtenTo.refersTo + else -> { + log.error( + "The variable of type ${writtenTo?.javaClass} is not yet supported in the foreach loop" + ) + null } - - // We wrote something to this variable declaration - writtenDecl = - when (writtenTo) { - is Declaration -> writtenTo - is DeclaredReferenceExpression -> writtenTo.refersTo - else -> { - log.error( - "The variable of type ${writtenTo?.javaClass} is not yet supported in the foreach loop" - ) - null - } - } - - currentNode.variable?.let { currentWritten = it } - - if (writtenTo is DeclaredReferenceExpression) { - if (currentNode !in loopPoints) { - // We haven't been here before, so there's no chance we added something - // already. However, as the variable is processed before, we have already - // added the DFG edge from the VariableDeclaration to the - // DeclaredReferenceExpression. This doesn't make any sense, so we have to - // remove it again. - writtenTo.removePrevDFG(writtenDecl) - } - - // This is a special case: We add the nextEOGEdge which goes out of the loop but - // with the old previousWrites map. - val nodesOutsideTheLoop = - currentNode.nextEOGEdges.filter { - it.getProperty(Properties.UNREACHABLE) != true && - it.end != currentNode.statement && - it.end !in currentNode.statement.allChildren() - } - nodesOutsideTheLoop - .map { it.end } - .forEach { worklist.add(Pair(it, copyMap(previousWrites, it))) } } - iterable?.let { writtenTo?.addPrevDFG(it) } - - if (writtenDecl != null && writtenTo != null) { - // Add the variable declaration (or the reference) to the list of previous write - // nodes in this path - previousWrites.computeIfAbsent(writtenDecl, ::mutableListOf).add(writtenTo) - } - } else if (currentNode is ReturnStatement) { - returnStatements.add(currentNode) - } - - // Check for loops: No loop statement with the same state as before and no write which - // is already in the current chain of writes too often (=twice). - if ( - !loopDetection(currentNode, writtenDecl, currentWritten, previousWrites, loopPoints) - ) { - // We add all the next steps in the eog to the worklist unless the exact same thing - // is already included in the list. - currentNode.nextEOGEdges - .filter { it.getProperty(Properties.UNREACHABLE) != true } - .map { it.end } - .forEach { - val newPair = Pair(it, copyMap(previousWrites, it)) - if (!worklistHasSimilarPair(worklist, newPair)) worklist.add(newPair) + if (writtenTo is DeclaredReferenceExpression) { + // This is a special case: We add the nextEOGEdge which goes out of the loop but + // with the old previousWrites map. + val nodesOutsideTheLoop = + currentNode.nextEOGEdges.filter { + it.getProperty(Properties.UNREACHABLE) != true && + it.end != currentNode.statement && + it.end !in currentNode.statement.allChildren() } + nodesOutsideTheLoop.forEach { worklist.push(it, state.duplicate()) } } - } - - removeUnreachableImplicitReturnStatement(node, returnStatements) - } - - /** - * Removes the DFG edges for a potential implicit return statement if it is not in - * [reachableReturnStatements]. - */ - protected fun removeUnreachableImplicitReturnStatement( - node: Node, - reachableReturnStatements: MutableSet - ) { - val lastStatement = - ((node as? FunctionDeclaration)?.body as? CompoundStatement)?.statements?.lastOrNull() - if ( - lastStatement is ReturnStatement && - lastStatement.isImplicit && - lastStatement !in reachableReturnStatements - ) - lastStatement.removeNextDFG(node) - } - /** - * Determines if there's an item in the [worklist] which has the same last write for each - * declaration in the [newPair]. If this is the case, we can ignore it because all that changed - * was the path through the EOG to reach this state but apparently, all the writes in the - * different branches are obsoleted by one common write access which happens afterwards. - */ - protected fun worklistHasSimilarPair( - worklist: MutableList>>>, - newPair: Pair>> - ): Boolean { - // We collect all states in the worklist which are only a subset of the new pair. We will - // remove them to avoid unnecessary computations. - val subsets = mutableSetOf>>>() - val newPairLastMap = newPair.second.mapValues { (_, v) -> v.last() } - for (existingPair in worklist) { - if (existingPair.first == newPair.first) { - // The next nodes match. Now check the last writes for each declaration. - var allWritesMatch = true - var allExistingWritesMatch = true - for ((lastWriteDecl, lastWrite) in newPairLastMap) { - - // We ignore FieldDeclarations because we cannot be sure how interprocedural - // data flows affect the field. Handling them in the state would only blow up - // the number of paths unnecessarily. - if (lastWriteDecl is FieldDeclaration) continue - - // Will we generate the same "prev DFG" with the item that is already in the - // list? - allWritesMatch = - allWritesMatch && existingPair.second[lastWriteDecl]?.last() == lastWrite - // All last writes which exist in the "existing pair" match but we have new - // declarations in the current one - allExistingWritesMatch = - allExistingWritesMatch && - (lastWriteDecl !in existingPair.second || - existingPair.second[lastWriteDecl]?.last() == lastWrite) - } - // We found a matching pair in the worklist? Done. Otherwise, maybe there's another - // pair... - if (allWritesMatch) return true - // The new state is a superset of the old one? We will add the missing pieces to the - // old one. - if (allExistingWritesMatch) { - subsets.add(existingPair) + iterable?.let { + writtenTo?.let { + state.push(writtenTo, PowersetLattice(setOf(iterable))) + // Add the variable declaration (or the reference) to the list of previous + // write nodes in this path + state.declarationsState[writtenDecl] = PowersetLattice(setOf(writtenTo)) } } - } - - // Check the "subsets" again, and add the missing declarations - if (subsets.isNotEmpty()) { - for (s in subsets) { - for ((k, v) in newPair.second) { - if (k !in s.second) { - s.second[k] = v - } - } + } else if (currentNode is FunctionDeclaration) { + // We have to add the parameters + currentNode.parameters.forEach { + doubleState.pushToDeclarationsState(it, PowersetLattice(setOf(it))) } - return true // We cover it in the respective subsets, so do not add this state again. + } else if (currentNode is ReturnStatement) { + doubleState.returnStatements.push(currentNode, PowersetLattice(setOf(currentNode))) + } else { + doubleState.declarationsState.push( + currentNode, + doubleState.declarationsState[currentEdge.start] + ) } - - return false + return state } /** @@ -391,79 +264,82 @@ open class ControlFlowSensitiveDFGPass(ctx: TranslationContext) : TranslationUni (currentNode.input as? DeclaredReferenceExpression)?.refersTo != null /** - * Determines if the [currentNode] is a loop point has already been visited with the exact same - * state before. Changes the state saved in the [loopPoints] by adding the current - * [previousWrites]. - * - * @return true if a loop was detected, false otherwise + * Removes the DFG edges for a potential implicit return statement if it is not in + * [reachableReturnStatements]. */ - protected fun loopDetection( - currentNode: Node, - writtenDecl: Declaration?, - currentWritten: Node, - previousWrites: MutableMap>, - loopPoints: MutableMap>> - ): Boolean { + protected fun removeUnreachableImplicitReturnStatement( + node: Node, + reachableReturnStatements: Collection + ) { + val lastStatement = + ((node as? FunctionDeclaration)?.body as? CompoundStatement)?.statements?.lastOrNull() if ( - currentNode is ForStatement || - currentNode is WhileStatement || - currentNode is ForEachStatement || - currentNode is DoStatement || - currentNode is GotoStatement || - currentNode is ContinueStatement - ) { - // Loop detection: This is a point which could serve as a loop, so we check all - // states which we have seen before in this place. - val state = loopPoints.computeIfAbsent(currentNode) { mutableMapOf() } - if ( - previousWrites.all { (decl, prevs) -> - (state[decl]?.contains(prevs.last())) == true - } - ) { - // The current state of last write operations has already been seen before => - // Nothing new => Do not add the next eog steps! - return true - } - // Add the current state for future loop detections. - previousWrites.forEach { (decl, prevs) -> - state.computeIfAbsent(decl, ::mutableSetOf).add(prevs.last()) - } - } - return writtenDecl != null && - ((previousWrites[writtenDecl]?.filter { it == currentWritten }?.size ?: 0) >= 2) + lastStatement is ReturnStatement && + lastStatement.isImplicit && + lastStatement !in reachableReturnStatements + ) + lastStatement.removeNextDFG(node) } /** - * Copies the map. We remove all the declarations which are no longer relevant because they are - * in a child scope of the next hop. + * A state which actually holds a state for all nodes, one only for declarations and one for + * ReturnStatements. */ - protected fun copyMap( - map: Map>, - nextNode: Node - ): MutableMap> { - val result = mutableMapOf>() - for ((k, v) in map) { - if ( - nextNode.scope == k.scope || - !nextNode.hasOuterScopeOf(k) || - ((nextNode is ForStatement || nextNode is ForEachStatement) && - k.scope?.parent == nextNode.scope) - ) { - result[k] = mutableListOf() - result[k]?.addAll(v) + protected class DFGPassState( + /** + * A mapping of a [Node] to its [LatticeElement]. The keys of this state will later get the + * DFG edges from the value! + */ + var generalState: State = State(), + /** + * It's main purpose is to store the most recent mapping of a [Declaration] to its + * [LatticeElement]. However, it is also used to figure out if we have to continue with the + * iteration (something in the declarationState has changed) which is why we store all nodes + * here. However, since we never use them except from determining if we changed something, + * it won't affect the result. + */ + var declarationsState: State = State(), + /** The [returnStatements] which are reachable. */ + var returnStatements: State = State() + ) : State() { + override fun duplicate(): DFGPassState { + return DFGPassState(generalState.duplicate(), declarationsState.duplicate()) + } + + override fun get(key: Node?): LatticeElement? { + return generalState[key] ?: declarationsState[key] + } + + override fun lub(other: State): Pair, Boolean> { + return if (other is DFGPassState) { + val (_, generalUpdate) = generalState.lub(other.generalState) + val (_, declUpdate) = declarationsState.lub(other.declarationsState) + Pair(this, generalUpdate || declUpdate) + } else { + val (_, generalUpdate) = generalState.lub(other) + Pair(this, generalUpdate) } } - return result - } - protected fun Node.hasOuterScopeOf(node: Node): Boolean { - var parentScope = node.scope?.parent - while (parentScope != null) { - if (this.scope == parentScope) { - return true + override fun needsUpdate(other: State): Boolean { + return if (other is DFGPassState) { + generalState.needsUpdate(other.generalState) || + declarationsState.needsUpdate(other.declarationsState) + } else { + generalState.needsUpdate(other) } - parentScope = parentScope.parent } - return false + + override fun push(newNode: Node, newLatticeElement: LatticeElement?): Boolean { + return generalState.push(newNode, newLatticeElement) + } + + /** Pushes the [newNode] and its [newLatticeElement] to the [declarationsState]. */ + fun pushToDeclarationsState( + newNode: Declaration, + newLatticeElement: LatticeElement? + ): Boolean { + return declarationsState.push(newNode, newLatticeElement) + } } } diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/GraphExamples.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/GraphExamples.kt index d1eb129f32..a72fba1d8a 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/GraphExamples.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/GraphExamples.kt @@ -30,6 +30,7 @@ import de.fraunhofer.aisec.cpg.frontends.TestLanguage import de.fraunhofer.aisec.cpg.frontends.TestLanguageFrontend import de.fraunhofer.aisec.cpg.graph.TypeManager import de.fraunhofer.aisec.cpg.graph.builder.* +import de.fraunhofer.aisec.cpg.graph.newInitializerListExpression import de.fraunhofer.aisec.cpg.graph.newVariableDeclaration import de.fraunhofer.aisec.cpg.sarif.PhysicalLocation import de.fraunhofer.aisec.cpg.sarif.Region @@ -37,6 +38,32 @@ import java.net.URI class GraphExamples { companion object { + fun getInitializerListExprDFG( + config: TranslationConfiguration = + TranslationConfiguration.builder() + .defaultPasses() + .registerLanguage(TestLanguage(".")) + .build() + ) = + testFrontend(config).build { + translationResult { + translationUnit("initializerListExprDFG.cpp") { + function("foo", t("int")) { body { returnStmt { literal(0, t("int")) } } } + function("main", t("int")) { + body { + declare { + variable("i", t("int")) { + val initList = newInitializerListExpression() + initList.initializers = listOf(call("foo")) + initializer = initList + } + } + returnStmt { ref("i") } + } + } + } + } + } fun testFrontend(config: TranslationConfiguration): TestLanguageFrontend { val ctx = TranslationContext(config, ScopeManager(), TypeManager()) @@ -109,41 +136,67 @@ class GraphExamples { ) = testFrontend(config).build { translationResult { - translationUnit("Variables.java") { - record("Variables") { - field("field", t("int")) { - literal(42, t("int")) - modifiers = listOf("private") - } - method("getField", t("int")) { - receiver = newVariableDeclaration("this", t("Variables")) - body { returnStmt { member("field") } } - } - method("getLocal", t("int")) { - receiver = newVariableDeclaration("this", t("Variables")) - body { - declare { - variable("local", t("int")) { literal(42, t("int")) } - } - returnStmt { ref("local") } - } - } - method("getShadow", t("int")) { - receiver = newVariableDeclaration("this", t("Variables")) - body { - declare { - variable("field", t("int")) { literal(43, t("int")) } + translationUnit("initializerListExprDFG.cpp") { + function("foo", t("int")) { body { returnStmt { literal(0, t("int")) } } } + function("main", t("int")) { + body { + declare { + variable("i", t("int")) { + val initList = newInitializerListExpression() + initList.initializers = listOf(call("foo")) + initializer = initList } - returnStmt { ref("field") } } - } - method("getNoShadow", t("int")) { - receiver = newVariableDeclaration("this", t("Variables")) - body { - declare { - variable("field", t("int")) { literal(43, t("int")) } + returnStmt { ref("i") } + + translationUnit("Variables.java") { + record("Variables") { + field("field", t("int")) { + literal(42, t("int")) + modifiers = listOf("private") + } + method("getField", t("int")) { + receiver = + newVariableDeclaration("this", t("Variables")) + body { returnStmt { member("field") } } + } + method("getLocal", t("int")) { + receiver = + newVariableDeclaration("this", t("Variables")) + body { + declare { + variable("local", t("int")) { + literal(42, t("int")) + } + } + returnStmt { ref("local") } + } + } + method("getShadow", t("int")) { + receiver = + newVariableDeclaration("this", t("Variables")) + body { + declare { + variable("field", t("int")) { + literal(43, t("int")) + } + } + returnStmt { ref("field") } + } + } + method("getNoShadow", t("int")) { + receiver = + newVariableDeclaration("this", t("Variables")) + body { + declare { + variable("field", t("int")) { + literal(43, t("int")) + } + } + returnStmt { member("field", ref("this")) } + } + } } - returnStmt { member("field", ref("this")) } } } } diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPassTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPassTest.kt new file mode 100644 index 0000000000..2d78e0de1e --- /dev/null +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPassTest.kt @@ -0,0 +1,194 @@ +/* + * Copyright (c) 2023, 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.ScopeManager +import de.fraunhofer.aisec.cpg.TranslationConfiguration +import de.fraunhofer.aisec.cpg.TranslationContext +import de.fraunhofer.aisec.cpg.frontends.TestLanguage +import de.fraunhofer.aisec.cpg.frontends.TestLanguageFrontend +import de.fraunhofer.aisec.cpg.graph.* +import de.fraunhofer.aisec.cpg.graph.builder.* +import de.fraunhofer.aisec.cpg.graph.statements.CompoundStatement +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Literal +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertTrue + +class ControlDependenceGraphPassTest { + + @Test + fun testIfStatements() { + val result = getIfTest() + assertNotNull(result) + val main = result.functions["main"] + assertNotNull(main) + val if0 = (main.body as CompoundStatement).statements[1] + assertNotNull(if0) + assertEquals(1, if0.prevCDG.size) + assertTrue(main in if0.prevCDG) + + val assignment1 = + result.assignments.firstOrNull { 1 == (it.value as? Literal<*>)?.value }?.start + assertNotNull(assignment1) + assertEquals(1, assignment1.prevCDG.size) + assertTrue(if0 in assignment1.prevCDG) + + val print0 = + result.calls("printf").first { + "0\n" == (it.arguments.firstOrNull() as? Literal<*>)?.value + } + assertNotNull(print0) + assertEquals(1, print0.prevCDG.size) + assertTrue(if0 in print0.prevCDG) + + val print1 = + result.calls("printf").first { + "1\n" == (it.arguments.firstOrNull() as? Literal<*>)?.value + } + assertNotNull(print1) + assertEquals(1, print1.prevCDG.size) + assertTrue(main in print1.prevCDG) + + val print2 = + result.calls("printf").first { + "2\n" == (it.arguments.firstOrNull() as? Literal<*>)?.value + } + assertNotNull(print2) + assertEquals(1, print2.prevCDG.size) + assertTrue(main in print2.prevCDG) + } + + @Test + fun testForEachLoop() { + val result = getForEachTest() + assertNotNull(result) + val main = result.functions["main"] + assertNotNull(main) + val forEachStmt = (main.body as CompoundStatement).statements[1] + assertNotNull(forEachStmt) + assertEquals(1, forEachStmt.prevCDG.size) + assertTrue(main in forEachStmt.prevCDG) + + val printInLoop = + result.calls("printf").first { + "loop: \${}\n" == (it.arguments.firstOrNull() as? Literal<*>)?.value + } + assertNotNull(printInLoop) + assertEquals(1, printInLoop.prevCDG.size) + assertTrue(forEachStmt in printInLoop.prevCDG) + + val printAfterLoop = + result.calls("printf").first { + "1\n" == (it.arguments.firstOrNull() as? Literal<*>)?.value + } + assertNotNull(printAfterLoop) + assertEquals(2, printAfterLoop.prevCDG.size) + assertTrue(main in printAfterLoop.prevCDG) + assertTrue( + forEachStmt in printAfterLoop.prevCDG + ) // TODO: Is this really correct or should it be filtered out in the pass? + } + + companion object { + fun testFrontend(config: TranslationConfiguration): TestLanguageFrontend { + val ctx = TranslationContext(config, ScopeManager(), TypeManager()) + val language = config.languages.filterIsInstance().first() + return TestLanguageFrontend(language.namespaceDelimiter, language, ctx) + } + + fun getIfTest() = + testFrontend( + TranslationConfiguration.builder() + .registerLanguage(TestLanguage("::")) + .defaultPasses() + .registerPass() + .build() + ) + .build { + translationResult { + translationUnit("if.cpp") { + // The main method + function("main", t("int")) { + body { + declare { variable("i", t("int")) { literal(0, t("int")) } } + ifStmt { + condition { ref("i") lt literal(1, t("int")) } + thenStmt { + ref("i") assign literal(1, t("int")) + call("printf") { literal("0\n", t("string")) } + } + } + call("printf") { literal("1\n", t("string")) } + ifStmt { + condition { ref("i") gt literal(0, t("int")) } + thenStmt { ref("i") assign literal(2, t("int")) } + elseStmt { ref("i") assign literal(3, t("int")) } + } + call("printf") { literal("2\n", t("string")) } + returnStmt { ref("i") } + } + } + } + } + } + + fun getForEachTest() = + testFrontend( + TranslationConfiguration.builder() + .registerLanguage(TestLanguage("::")) + .defaultPasses() + .registerPass() + .build() + ) + .build { + translationResult { + translationUnit("forEach.cpp") { + // The main method + function("main", t("int")) { + body { + declare { variable("i", t("int")) { literal(0, t("int")) } } + forEachStmt { + declare { variable("loopVar", t("string")) } + call("magicFunction") + loopBody { + call("printf") { + literal("loop: \${}\n", t("string")) + ref("loopVar") + } + } + } + call("printf") { literal("1\n", t("string")) } + + returnStmt { ref("i") } + } + } + } + } + } + } +} diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/DFGTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/DFGTest.kt index f96265da3e..ec7ff3098c 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/DFGTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/DFGTest.kt @@ -497,4 +497,14 @@ class DFGTest { assertEquals(6, varA.nextDFG.size) assertEquals(1, varA.prevDFG.size) // Only the initializer should flow there. } + + @Test + fun testInitializerListExpression() { + val result = GraphExamples.getInitializerListExprDFG() + val variable = result.variables["i"] + assertNotNull(variable) + assertEquals(1, variable.prevDFG.size) + val initializer = variable.prevDFG.first() + assertEquals(1, initializer.prevDFG.size) + } } diff --git a/cpg-core/src/testFixtures/kotlin/de/fraunhofer/aisec/cpg/frontends/TestLanguage.kt b/cpg-core/src/testFixtures/kotlin/de/fraunhofer/aisec/cpg/frontends/TestLanguage.kt index 86d8459301..8230d9c1dc 100644 --- a/cpg-core/src/testFixtures/kotlin/de/fraunhofer/aisec/cpg/frontends/TestLanguage.kt +++ b/cpg-core/src/testFixtures/kotlin/de/fraunhofer/aisec/cpg/frontends/TestLanguage.kt @@ -30,10 +30,7 @@ import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.TypeManager import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration import de.fraunhofer.aisec.cpg.graph.statements.expressions.ProblemExpression -import de.fraunhofer.aisec.cpg.graph.types.FloatingPointType -import de.fraunhofer.aisec.cpg.graph.types.IntegerType -import de.fraunhofer.aisec.cpg.graph.types.NumericType -import de.fraunhofer.aisec.cpg.graph.types.Type +import de.fraunhofer.aisec.cpg.graph.types.* import de.fraunhofer.aisec.cpg.sarif.PhysicalLocation import java.io.File import java.util.function.Supplier @@ -60,6 +57,7 @@ open class TestLanguage(namespaceDelimiter: String = "::") : Language Date: Wed, 26 Jul 2023 18:53:53 +0200 Subject: [PATCH 108/143] Add generic parameters for `LanguageFrontend` (#1236) This PR adds generic parameters representing AST node types and type node types of the original parser. --- .github/workflows/build.yml | 2 +- .../de/fraunhofer/aisec/cpg/ScopeManager.kt | 2 +- .../aisec/cpg/TranslationConfiguration.kt | 18 +-- .../aisec/cpg/TranslationManager.kt | 38 +++--- .../fraunhofer/aisec/cpg/frontends/Handler.kt | 66 +++------- .../aisec/cpg/frontends/Language.kt | 2 +- .../aisec/cpg/frontends/LanguageFrontend.kt | 49 +++++--- .../de/fraunhofer/aisec/cpg/graph/Name.kt | 2 +- .../de/fraunhofer/aisec/cpg/graph/Node.kt | 3 +- .../fraunhofer/aisec/cpg/graph/NodeBuilder.kt | 10 +- .../aisec/cpg/graph/builder/Fluent.kt | 114 +++++++++--------- .../aisec/cpg/graph/types/BooleanType.kt | 3 +- .../cpg/graph/types/FloatingPointType.kt | 3 +- .../cpg/graph/types/FunctionPointerType.kt | 5 +- .../aisec/cpg/graph/types/FunctionType.kt | 3 +- .../aisec/cpg/graph/types/IntegerType.kt | 3 +- .../aisec/cpg/graph/types/NumericType.kt | 3 +- .../aisec/cpg/graph/types/ObjectType.kt | 5 +- .../cpg/graph/types/ParameterizedType.kt | 3 +- .../aisec/cpg/graph/types/StringType.kt | 3 +- .../fraunhofer/aisec/cpg/graph/types/Type.kt | 5 +- .../aisec/cpg/graph/types/UnknownType.kt | 3 +- .../aisec/cpg/helpers/SubgraphWalker.kt | 2 +- .../de/fraunhofer/aisec/cpg/helpers/Util.kt | 16 +-- .../aisec/cpg/passes/CallResolver.kt | 2 +- .../de/fraunhofer/aisec/cpg/passes/Pass.kt | 6 +- .../aisec/cpg/passes/inference/Inference.kt | 3 +- .../cpg/passes/order/RequiredFrontend.kt | 2 +- .../aisec/cpg/frontends/TestLanguage.kt | 16 ++- .../aisec/cpg/frontends/cxx/CXXHandler.kt | 5 +- .../cpg/frontends/cxx/CXXLanguageFrontend.kt | 63 ++++------ .../cpg/frontends/cxx/DeclarationHandler.kt | 17 ++- .../cpg/frontends/cxx/DeclaratorHandler.kt | 46 +++---- .../cpg/frontends/cxx/ExpressionHandler.kt | 9 +- .../aisec/cpg/PerformanceRegressionTest.kt | 2 +- .../frontends/cxx/CXXLanguageFrontendTest.kt | 6 +- .../frontends/golang/GoLanguageFrontend.kt | 18 ++- .../cpg/frontends/java/DeclarationHandler.kt | 6 +- .../cpg/frontends/java/ExpressionHandler.kt | 15 +-- .../frontends/java/JavaLanguageFrontend.kt | 73 +++++------ .../cpg/frontends/java/StatementHandler.kt | 28 +++-- .../cpg/frontends/llvm/DeclarationHandler.kt | 27 +---- .../cpg/frontends/llvm/ExpressionHandler.kt | 44 +++---- .../frontends/llvm/LLVMIRLanguageFrontend.kt | 23 +++- .../cpg/frontends/llvm/StatementHandler.kt | 103 ++++++++-------- .../python/PythonLanguageFrontend.kt | 16 ++- .../typescript/DeclarationHandler.kt | 45 ++----- .../frontends/typescript/ExpressionHandler.kt | 65 ++++------ .../frontends/typescript/StatementHandler.kt | 8 +- .../typescript/TypeScriptLanguageFrontend.kt | 49 ++++---- 50 files changed, 491 insertions(+), 569 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a1739e55ef..b5031ebacb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -132,7 +132,7 @@ jobs: - name: Prepare test and coverage reports if: ${{ always() }} run: | - zip reports.zip **/build/reports/** + zip reports.zip **/build/reports/**/** || true - name: Archive test and coverage reports if: ${{ always() }} uses: actions/upload-artifact@v3 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 3507dd9f56..f1a6fed996 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 @@ -75,7 +75,7 @@ class ScopeManager : ScopeProvider { * The language frontend tied to the scope manager. Can be used to implement language specific * scope resolution or lookup. */ - var lang: LanguageFrontend? = null + var lang: LanguageFrontend<*, *>? = null /** True, if the scope manager is currently in a [BlockScope]. */ val isInBlock: Boolean diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationConfiguration.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationConfiguration.kt index a3f9f9135f..f421af0141 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationConfiguration.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationConfiguration.kt @@ -99,7 +99,7 @@ private constructor( */ val replacedPasses: Map>, KClass>>, KClass>>, - languages: List>, + languages: List>, codeInNodes: Boolean, processAnnotations: Boolean, disableCleanup: Boolean, @@ -112,7 +112,7 @@ private constructor( addIncludesToGraph: Boolean ) { /** This list contains all languages which we want to translate. */ - val languages: List> + val languages: List> /** * Switch off cleaning up TypeManager memory after analysis. @@ -215,7 +215,7 @@ private constructor( */ class Builder { private var softwareComponents: MutableMap> = HashMap() - private val languages = mutableListOf>() + private val languages = mutableListOf>() private var topLevel: File? = null private var debugParser = false private var failOnError = false @@ -405,7 +405,7 @@ private constructor( } /** Registers an additional [Language]. */ - fun registerLanguage(language: Language): Builder { + fun registerLanguage(language: Language<*>): Builder { languages.add(language) log.info( "Registered language frontend '${language::class.simpleName}' for following file types: ${language.fileExtensions}" @@ -414,7 +414,7 @@ private constructor( } /** Registers an additional [Language]. */ - inline fun > registerLanguage(): Builder { + inline fun > registerLanguage(): Builder { T::class.primaryConstructor?.call()?.let { registerLanguage(it) } return this } @@ -443,8 +443,8 @@ private constructor( } /** Unregisters a registered [de.fraunhofer.aisec.cpg.frontends.Language]. */ - fun unregisterLanguage(language: Class?>): Builder { - languages.removeIf { obj: Language? -> language.isInstance(obj) } + fun unregisterLanguage(language: Class?>): Builder { + languages.removeIf { obj: Language<*>? -> language.isInstance(obj) } return this } @@ -490,7 +490,7 @@ private constructor( return } - for (frontend in languages.map(Language::frontend)) { + for (frontend in languages.map(Language<*>::frontend)) { val extraPasses = frontend.findAnnotations() if (extraPasses.isNotEmpty()) { for (p in extraPasses) { @@ -505,7 +505,7 @@ private constructor( } private fun registerReplacedPasses() { - for (frontend in languages.map(Language::frontend)) { + for (frontend in languages.map(Language<*>::frontend)) { val replacedPasses = frontend.findAnnotations() if (replacedPasses.isNotEmpty()) { for (p in replacedPasses) { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationManager.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationManager.kt index bb5a52392a..da2eccc9cb 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationManager.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationManager.kt @@ -75,7 +75,7 @@ private constructor( } private fun analyzeNonAsync(): TranslationResult { - var executedFrontends = setOf() + var executedFrontends = setOf>() // Build a new global translation context val ctx = TranslationContext(config, ScopeManager(), TypeManager()) @@ -135,8 +135,8 @@ private constructor( private fun runFrontends( ctx: TranslationContext, result: TranslationResult - ): Set { - val usedFrontends = mutableSetOf() + ): Set> { + val usedFrontends = mutableSetOf>() for (sc in ctx.config.softwareComponents.keys) { val component = Component() component.name = Name(sc) @@ -252,14 +252,14 @@ private constructor( result: TranslationResult, globalCtx: TranslationContext, sourceLocations: Collection - ): Set { - val usedFrontends = mutableSetOf() + ): Set> { + val usedFrontends = mutableSetOf>() log.info("Parallel parsing started") - val futures = mutableListOf>>() + val futures = mutableListOf?>>() val parallelContexts = mutableListOf() - val futureToFile: MutableMap>, File> = + val futureToFile: MutableMap?>, File> = IdentityHashMap() for (sourceLocation in sourceLocations) { @@ -284,7 +284,8 @@ private constructor( for (future in futures) { try { - future.get().ifPresent { f: LanguageFrontend -> + val f = future.get() + if (f != null) { handleCompletion(result, usedFrontends, futureToFile[future], f) } } catch (e: InterruptedException) { @@ -310,13 +311,14 @@ private constructor( result: TranslationResult, ctx: TranslationContext, sourceLocations: Collection - ): Set { - val usedFrontends = mutableSetOf() + ): Set> { + val usedFrontends = mutableSetOf>() for (sourceLocation in sourceLocations) { log.info("Parsing {}", sourceLocation.absolutePath) - parse(component, ctx, sourceLocation).ifPresent { f: LanguageFrontend -> + var f = parse(component, ctx, sourceLocation) + if (f != null) { handleCompletion(result, usedFrontends, sourceLocation, f) } } @@ -326,9 +328,9 @@ private constructor( private fun handleCompletion( result: TranslationResult, - usedFrontends: MutableSet, + usedFrontends: MutableSet>, sourceLocation: File?, - f: LanguageFrontend + f: LanguageFrontend<*, *> ) { usedFrontends.add(f) @@ -345,8 +347,8 @@ private constructor( component: Component, ctx: TranslationContext, sourceLocation: File, - ): Optional { - var frontend: LanguageFrontend? = null + ): LanguageFrontend<*, *>? { + var frontend: LanguageFrontend<*, *>? = null try { frontend = getFrontend(sourceLocation, ctx) @@ -358,7 +360,7 @@ private constructor( "Found no parser frontend for ${sourceLocation.name}" ) } - return Optional.empty() + return null } component.translationUnits.add(frontend.parse(sourceLocation)) } catch (ex: TranslationException) { @@ -367,10 +369,10 @@ private constructor( throw ex } } - return Optional.ofNullable(frontend) + return frontend } - private fun getFrontend(file: File, ctx: TranslationContext): LanguageFrontend? { + private fun getFrontend(file: File, ctx: TranslationContext): LanguageFrontend<*, *>? { val language = file.language return if (language != null) { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/Handler.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/Handler.kt index c5857a75cc..ea90d57954 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/Handler.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/Handler.kt @@ -25,14 +25,11 @@ */ package de.fraunhofer.aisec.cpg.frontends -import de.fraunhofer.aisec.cpg.TranslationContext import de.fraunhofer.aisec.cpg.graph.* -import de.fraunhofer.aisec.cpg.graph.scopes.Scope import de.fraunhofer.aisec.cpg.helpers.Util.errorWithFileLocation import java.lang.reflect.ParameterizedType import java.lang.reflect.Type import java.util.function.Supplier -import org.eclipse.cdt.internal.core.dom.parser.ASTNode import org.slf4j.Logger import org.slf4j.LoggerFactory @@ -47,24 +44,29 @@ import org.slf4j.LoggerFactory * @param the raw ast node specific to the parser * @param the language frontend */ -abstract class Handler( - protected val configConstructor: Supplier, +abstract class Handler>( + protected val configConstructor: Supplier, /** Returns the frontend which used this handler. */ - var frontend: L -) : LanguageProvider, CodeAndLocationProvider, ScopeProvider, NamespaceProvider, ContextProvider { - protected val map = HashMap, HandlerInterface>() + val frontend: L +) : + LanguageProvider by frontend, + CodeAndLocationProvider by frontend, + ScopeProvider by frontend, + NamespaceProvider by frontend, + ContextProvider by frontend { + protected val map = HashMap, HandlerInterface>() private val typeOfT: Class<*>? /** - * Searches for a handler matching the most specific superclass of [T]. The created map should - * thus contain a handler for every semantically different AST node and can reuse handler code - * as long as the handled AST nodes have a common ancestor. + * Searches for a handler matching the most specific superclass of [HandlerNode]. The created + * map should thus contain a handler for every semantically different AST node and can reuse + * handler code as long as the handled AST nodes have a common ancestor. * * @param ctx The AST node, whose handler is matched with respect to the AST node class. * @return most specific handler. */ - open fun handle(ctx: T): S? { - var ret: S? + open fun handle(ctx: HandlerNode): ResultNode? { + var ret: ResultNode? if (ctx == null) { log.error( "ctx is NULL. This can happen when ast children are optional in ${this.javaClass}. Called by ${Thread.currentThread().stackTrace[2]}" @@ -72,17 +74,6 @@ abstract class Handler( return null } - // If we do not want to load includes into the CPG and the current fileLocation was included - if (!frontend.config.loadIncludes && ctx is ASTNode) { - val astNode = ctx as ASTNode - if ( - astNode.fileLocation != null && - astNode.fileLocation.contextInclusionStatement != null - ) { - log.debug("Skip parsing include file ${astNode.containingFilename}") - return null - } - } var toHandle: Class<*> = ctx.javaClass var handler = map[toHandle] while (handler == null) { @@ -92,7 +83,7 @@ abstract class Handler( handler != null && // always ok to handle as generic literal expr !ctx.javaClass.simpleName.contains("LiteralExpr") ) { - errorWithFileLocation( + errorWithFileLocation( frontend, ctx, log, @@ -110,13 +101,13 @@ abstract class Handler( // we will // set the location here. if (s.location == null) { - frontend.setCodeAndLocation(s, ctx) + frontend.setCodeAndLocation(s, ctx) } - frontend.setComment(s, ctx) + frontend.setComment(s, ctx) } ret = s } else { - errorWithFileLocation( + errorWithFileLocation( frontend, ctx, log, @@ -132,7 +123,7 @@ abstract class Handler( // In case the node is empty, we report a problem if (ret == null) { - errorWithFileLocation( + errorWithFileLocation( frontend, ctx, log, @@ -167,23 +158,6 @@ abstract class Handler( return null } - /** Returns the language which this handler is parsing. */ - override val language: Language - get() = frontend.language as Language - - override fun setCodeAndLocation(cpgNode: N, astNode: S?) { - frontend.setCodeAndLocation(cpgNode, astNode) - } - - override val scope: Scope? - get() = frontend.scope - - override val namespace: Name? - get() = frontend.namespace - - override val ctx: TranslationContext - get() = frontend.ctx - companion object { @JvmStatic protected val log: Logger = LoggerFactory.getLogger(Handler::class.java) } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/Language.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/Language.kt index c332229432..8fef40a6fc 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/Language.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/Language.kt @@ -48,7 +48,7 @@ import kotlin.reflect.full.primaryConstructor * persisted in the final graph (database) and each node links to its corresponding language using * the [Node.language] property. */ -abstract class Language : Node() { +abstract class Language> : Node() { /** The file extensions without the dot */ abstract val fileExtensions: List diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/LanguageFrontend.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/LanguageFrontend.kt index 383462388f..d1e8e12453 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/LanguageFrontend.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/LanguageFrontend.kt @@ -32,6 +32,7 @@ import de.fraunhofer.aisec.cpg.TranslationResult import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration import de.fraunhofer.aisec.cpg.graph.scopes.Scope +import de.fraunhofer.aisec.cpg.graph.types.Type import de.fraunhofer.aisec.cpg.sarif.PhysicalLocation import de.fraunhofer.aisec.cpg.sarif.Region import java.io.File @@ -47,9 +48,9 @@ import org.slf4j.LoggerFactory * More information can be found in the * [github wiki page](https://github.com/Fraunhofer-AISEC/cpg/wiki/Language-Frontends). */ -abstract class LanguageFrontend( +abstract class LanguageFrontend( /** The language this frontend works for. */ - override val language: Language, + override val language: Language>, /** * The translation context, which contains all necessary managers used in this frontend parsing @@ -59,7 +60,7 @@ abstract class LanguageFrontend( final override var ctx: TranslationContext, ) : ProcessedListener(), - CodeAndLocationProvider, + CodeAndLocationProvider, LanguageProvider, ScopeProvider, NamespaceProvider, @@ -91,10 +92,22 @@ abstract class LanguageFrontend( * This function returns a [TranslationResult], but rather than parsing source code, the * function [init] is used to build nodes in the Node Fluent DSL. */ - fun build(init: LanguageFrontend.() -> TranslationResult): TranslationResult { + fun build(init: LanguageFrontend<*, *>.() -> TranslationResult): TranslationResult { return init(this) } + /** + * This function serves as an entry-point to type parsing in the language frontend. It needs to + * return a [Type] object based on the ast type object used by the language frontend, e.g., the + * parser. + * + * A language frontend will usually de-construct the ast type object, e.g., in case of pointer + * or array types and then either recursively call this function or call other helper functions + * similar to this one. Ideally, they should share the [typeOf] name, but have different method + * signatures. + */ + abstract fun typeOf(type: TypeNode): Type + /** * Returns the raw code of the ast node, generic for java or c++ ast nodes. * @@ -102,7 +115,7 @@ abstract class LanguageFrontend( * @param astNode the ast node * @return the source code */ - abstract fun getCodeFromRawNode(astNode: T): String? + abstract fun codeOf(astNode: AstNode): String? /** * Returns the [Region] of the code with line and column, index starting at 1, generic for java @@ -112,21 +125,19 @@ abstract class LanguageFrontend( * @param astNode the ast node * @return the location */ - abstract fun getLocationFromRawNode(astNode: T): PhysicalLocation? - - override fun setCodeAndLocation(cpgNode: N, astNode: S?) { - if (cpgNode is Node && astNode != null) { - if (config.codeInNodes) { - // only set code, if it's not already set or empty - val code = getCodeFromRawNode(astNode) - if (code != null) { - (cpgNode as Node).code = code - } else { - log.warn("Unexpected: No code for node {}", astNode) - } + abstract fun locationOf(astNode: AstNode): PhysicalLocation? + + override fun setCodeAndLocation(cpgNode: Node, astNode: AstNode) { + if (config.codeInNodes) { + // only set code, if it's not already set or empty + val code = codeOf(astNode) + if (code != null) { + cpgNode.code = code + } else { + log.warn("Unexpected: No code for node {}", astNode) } - (cpgNode as Node).location = getLocationFromRawNode(astNode) } + cpgNode.location = locationOf(astNode) } /** @@ -225,7 +236,7 @@ abstract class LanguageFrontend( clearProcessed() } - abstract fun setComment(s: S, ctx: T) + abstract fun setComment(node: Node, astNode: AstNode) companion object { // Allow non-Java frontends to access the logger (i.e. jep) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Name.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Name.kt index cb8aa9717d..2e2d52b496 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Name.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Name.kt @@ -46,7 +46,7 @@ class Name( constructor( localName: String, parent: Name? = null, - language: Language? + language: Language<*>? ) : this(localName, parent, language?.namespaceDelimiter ?: ".") /** diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Node.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Node.kt index 2b35842570..7089f436fd 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Node.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Node.kt @@ -31,7 +31,6 @@ import de.fraunhofer.aisec.cpg.PopulatedByPass import de.fraunhofer.aisec.cpg.TranslationContext import de.fraunhofer.aisec.cpg.frontends.Handler import de.fraunhofer.aisec.cpg.frontends.Language -import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration @@ -89,7 +88,7 @@ open class Node : IVisitable, Persistable, LanguageProvider, ScopeProvider */ @Relationship(value = "LANGUAGE", direction = Relationship.Direction.OUTGOING) @JsonBackReference - override var language: Language? = null + override var language: Language<*>? = null /** * The scope this node "lives" in / in which it is defined. This property is set in diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/NodeBuilder.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/NodeBuilder.kt index 5d947622fa..6d05808cf4 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/NodeBuilder.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/NodeBuilder.kt @@ -56,15 +56,15 @@ interface MetadataProvider * each [Node], but also transformation steps, such as [Handler]. */ interface LanguageProvider : MetadataProvider { - val language: Language? + val language: Language<*>? } /** * This interface denotes that the class is able to provide source code and location information for * a specific node and set it using the [setCodeAndLocation] function. */ -interface CodeAndLocationProvider : MetadataProvider { - fun setCodeAndLocation(cpgNode: N, astNode: S?) +interface CodeAndLocationProvider : MetadataProvider { + fun setCodeAndLocation(cpgNode: Node, astNode: AstNode) } /** @@ -103,8 +103,8 @@ fun Node.applyMetadata( localNameOnly: Boolean = false, defaultNamespace: Name? = null, ) { - if (provider is CodeAndLocationProvider) { - provider.setCodeAndLocation(this, rawNode) + if (provider is CodeAndLocationProvider<*> && rawNode != null) { + (provider as CodeAndLocationProvider).setCodeAndLocation(this, rawNode) } if (provider is LanguageProvider) { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt index 631d5319ad..5f42bdb53b 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt @@ -35,7 +35,9 @@ import de.fraunhofer.aisec.cpg.graph.statements.expressions.* import de.fraunhofer.aisec.cpg.graph.types.Type import de.fraunhofer.aisec.cpg.passes.executePassSequential -fun LanguageFrontend.translationResult(init: TranslationResult.() -> Unit): TranslationResult { +fun LanguageFrontend<*, *>.translationResult( + init: TranslationResult.() -> Unit +): TranslationResult { val node = TranslationResult( TranslationManager.builder().config(ctx.config).build(), @@ -57,7 +59,7 @@ fun LanguageFrontend.translationResult(init: TranslationResult.() -> Unit): Tran */ context(TranslationResult) -fun LanguageFrontend.translationUnit( +fun LanguageFrontend<*, *>.translationUnit( name: CharSequence = Node.EMPTY_NAME, init: TranslationUnitDeclaration.() -> Unit ): TranslationUnitDeclaration { @@ -77,7 +79,7 @@ fun LanguageFrontend.translationUnit( */ context(DeclarationHolder) -fun LanguageFrontend.namespace( +fun LanguageFrontend<*, *>.namespace( name: CharSequence, init: NamespaceDeclaration.() -> Unit ): NamespaceDeclaration { @@ -97,7 +99,7 @@ fun LanguageFrontend.namespace( */ context(DeclarationHolder) -fun LanguageFrontend.record( +fun LanguageFrontend<*, *>.record( name: CharSequence, kind: String = "class", init: RecordDeclaration.() -> Unit @@ -119,7 +121,7 @@ fun LanguageFrontend.record( */ context(DeclarationHolder) -fun LanguageFrontend.field( +fun LanguageFrontend<*, *>.field( name: CharSequence, type: Type = newUnknownType(), init: FieldDeclaration.() -> Unit @@ -141,7 +143,7 @@ fun LanguageFrontend.field( */ context(DeclarationHolder) -fun LanguageFrontend.function( +fun LanguageFrontend<*, *>.function( name: CharSequence, returnType: Type = newUnknownType(), returnTypes: List? = null, @@ -171,7 +173,7 @@ fun LanguageFrontend.function( */ context(RecordDeclaration) -fun LanguageFrontend.method( +fun LanguageFrontend<*, *>.method( name: CharSequence, returnType: Type = newUnknownType(), init: MethodDeclaration.() -> Unit @@ -196,7 +198,9 @@ fun LanguageFrontend.method( */ context(RecordDeclaration) -fun LanguageFrontend.constructor(init: ConstructorDeclaration.() -> Unit): ConstructorDeclaration { +fun LanguageFrontend<*, *>.constructor( + init: ConstructorDeclaration.() -> Unit +): ConstructorDeclaration { val recordDecl: RecordDeclaration = this@RecordDeclaration val node = newConstructorDeclaration(recordDecl.name, recordDeclaration = recordDecl) @@ -217,7 +221,7 @@ fun LanguageFrontend.constructor(init: ConstructorDeclaration.() -> Unit): Const */ context(FunctionDeclaration) -fun LanguageFrontend.body( +fun LanguageFrontend<*, *>.body( needsScope: Boolean = true, init: CompoundStatement.() -> Unit ): CompoundStatement { @@ -236,7 +240,7 @@ fun LanguageFrontend.body( */ context(FunctionDeclaration) -fun LanguageFrontend.param( +fun LanguageFrontend<*, *>.param( name: CharSequence, type: Type = newUnknownType(), init: (ParamVariableDeclaration.() -> Unit)? = null @@ -260,7 +264,7 @@ fun LanguageFrontend.param( */ context(StatementHolder) -fun LanguageFrontend.returnStmt(init: ReturnStatement.() -> Unit): ReturnStatement { +fun LanguageFrontend<*, *>.returnStmt(init: ReturnStatement.() -> Unit): ReturnStatement { val node = (this@LanguageFrontend).newReturnStatement() init(node) @@ -276,7 +280,7 @@ fun LanguageFrontend.returnStmt(init: ReturnStatement.() -> Unit): ReturnStateme */ context(StatementHolder) -fun LanguageFrontend.declare(init: DeclarationStatement.() -> Unit): DeclarationStatement { +fun LanguageFrontend<*, *>.declare(init: DeclarationStatement.() -> Unit): DeclarationStatement { val node = (this@LanguageFrontend).newDeclarationStatement() init(node) @@ -292,7 +296,7 @@ fun LanguageFrontend.declare(init: DeclarationStatement.() -> Unit): Declaration */ context(DeclarationStatement) -fun LanguageFrontend.variable( +fun LanguageFrontend<*, *>.variable( name: String, type: Type = newUnknownType(), init: (VariableDeclaration.() -> Unit)? = null @@ -321,7 +325,7 @@ fun LanguageFrontend.variable( */ context(Holder) -fun LanguageFrontend.call( +fun LanguageFrontend<*, *>.call( name: CharSequence, isStatic: Boolean = false, init: (CallExpression.() -> Unit)? = null @@ -364,7 +368,7 @@ fun LanguageFrontend.call( */ context(Holder) -fun LanguageFrontend.memberCall( +fun LanguageFrontend<*, *>.memberCall( localName: CharSequence, member: Expression, isStatic: Boolean = false, @@ -395,7 +399,7 @@ fun LanguageFrontend.memberCall( */ context(Holder) -fun LanguageFrontend.construct( +fun LanguageFrontend<*, *>.construct( name: CharSequence, init: (ConstructExpression.() -> Unit)? = null ): ConstructExpression { @@ -418,7 +422,7 @@ fun LanguageFrontend.construct( context(Holder) -fun LanguageFrontend.new(init: (NewExpression.() -> Unit)? = null): NewExpression { +fun LanguageFrontend<*, *>.new(init: (NewExpression.() -> Unit)? = null): NewExpression { val node = newNewExpression() if (init != null) init(node) @@ -431,7 +435,7 @@ fun LanguageFrontend.new(init: (NewExpression.() -> Unit)? = null): NewExpressio return node } -fun LanguageFrontend.memberOrRef(name: Name, type: Type = newUnknownType()): Expression { +fun LanguageFrontend<*, *>.memberOrRef(name: Name, type: Type = newUnknownType()): Expression { val node = if (name.parent != null) { newMemberExpression(name.localName, memberOrRef(name.parent)) @@ -450,7 +454,7 @@ fun LanguageFrontend.memberOrRef(name: Name, type: Type = newUnknownType()): Exp */ context(StatementHolder) -fun LanguageFrontend.ifStmt(init: IfStatement.() -> Unit): IfStatement { +fun LanguageFrontend<*, *>.ifStmt(init: IfStatement.() -> Unit): IfStatement { val node = newIfStatement() init(node) @@ -466,7 +470,7 @@ fun LanguageFrontend.ifStmt(init: IfStatement.() -> Unit): IfStatement { */ context(StatementHolder) -fun LanguageFrontend.forEachStmt(init: ForEachStatement.() -> Unit): ForEachStatement { +fun LanguageFrontend<*, *>.forEachStmt(init: ForEachStatement.() -> Unit): ForEachStatement { val node = newForEachStatement() init(node) @@ -483,7 +487,7 @@ fun LanguageFrontend.forEachStmt(init: ForEachStatement.() -> Unit): ForEachStat */ context(StatementHolder) -fun LanguageFrontend.switchStmt( +fun LanguageFrontend<*, *>.switchStmt( selector: Expression, needsScope: Boolean = true, init: SwitchStatement.() -> Unit @@ -504,7 +508,7 @@ fun LanguageFrontend.switchStmt( */ context(StatementHolder) -fun LanguageFrontend.whileStmt( +fun LanguageFrontend<*, *>.whileStmt( needsScope: Boolean = true, init: WhileStatement.() -> Unit ): WhileStatement { @@ -525,7 +529,7 @@ fun LanguageFrontend.whileStmt( */ context(IfStatement) -fun LanguageFrontend.condition(init: IfStatement.() -> BinaryOperator): BinaryOperator { +fun LanguageFrontend<*, *>.condition(init: IfStatement.() -> BinaryOperator): BinaryOperator { return init(this@IfStatement) } @@ -536,7 +540,9 @@ fun LanguageFrontend.condition(init: IfStatement.() -> BinaryOperator): BinaryOp */ context(WhileStatement) -fun LanguageFrontend.whileCondition(init: WhileStatement.() -> BinaryOperator): BinaryOperator { +fun LanguageFrontend<*, *>.whileCondition( + init: WhileStatement.() -> BinaryOperator +): BinaryOperator { return init(this@WhileStatement) } @@ -547,7 +553,7 @@ fun LanguageFrontend.whileCondition(init: WhileStatement.() -> BinaryOperator): */ context(IfStatement) -fun LanguageFrontend.thenStmt( +fun LanguageFrontend<*, *>.thenStmt( needsScope: Boolean = true, init: CompoundStatement.() -> Unit ): CompoundStatement { @@ -566,7 +572,7 @@ fun LanguageFrontend.thenStmt( */ context(IfStatement) -fun LanguageFrontend.elseIf(init: IfStatement.() -> Unit): IfStatement { +fun LanguageFrontend<*, *>.elseIf(init: IfStatement.() -> Unit): IfStatement { val node = newIfStatement() init(node) @@ -584,7 +590,7 @@ fun LanguageFrontend.elseIf(init: IfStatement.() -> Unit): IfStatement { */ context(WhileStatement) -fun LanguageFrontend.loopBody(init: CompoundStatement.() -> Unit): CompoundStatement { +fun LanguageFrontend<*, *>.loopBody(init: CompoundStatement.() -> Unit): CompoundStatement { val node = newCompoundStatement() init(node) statement = node @@ -598,7 +604,7 @@ fun LanguageFrontend.loopBody(init: CompoundStatement.() -> Unit): CompoundState */ context(ForEachStatement) -fun LanguageFrontend.loopBody(init: CompoundStatement.() -> Unit): CompoundStatement { +fun LanguageFrontend<*, *>.loopBody(init: CompoundStatement.() -> Unit): CompoundStatement { val node = newCompoundStatement() init(node) statement = node @@ -613,7 +619,7 @@ fun LanguageFrontend.loopBody(init: CompoundStatement.() -> Unit): CompoundState */ context(SwitchStatement) -fun LanguageFrontend.switchBody(init: CompoundStatement.() -> Unit): CompoundStatement { +fun LanguageFrontend<*, *>.switchBody(init: CompoundStatement.() -> Unit): CompoundStatement { val node = newCompoundStatement() init(node) statement = node @@ -628,7 +634,7 @@ fun LanguageFrontend.switchBody(init: CompoundStatement.() -> Unit): CompoundSta */ context(IfStatement) -fun LanguageFrontend.elseStmt( +fun LanguageFrontend<*, *>.elseStmt( needsScope: Boolean = true, init: CompoundStatement.() -> Unit ): CompoundStatement { @@ -646,7 +652,7 @@ fun LanguageFrontend.elseStmt( */ context(Holder) -fun LanguageFrontend.label( +fun LanguageFrontend<*, *>.label( label: String, init: (LabelStatement.() -> Statement)? = null ): LabelStatement { @@ -671,7 +677,7 @@ fun LanguageFrontend.label( */ context(StatementHolder) -fun LanguageFrontend.continueStmt(label: String? = null): ContinueStatement { +fun LanguageFrontend<*, *>.continueStmt(label: String? = null): ContinueStatement { val node = newContinueStatement() node.label = label @@ -686,7 +692,7 @@ fun LanguageFrontend.continueStmt(label: String? = null): ContinueStatement { */ context(Holder) -fun LanguageFrontend.breakStmt(label: String? = null): BreakStatement { +fun LanguageFrontend<*, *>.breakStmt(label: String? = null): BreakStatement { val node = newBreakStatement() node.label = label @@ -705,7 +711,7 @@ fun LanguageFrontend.breakStmt(label: String? = null): BreakStatement { */ context(Holder) -fun LanguageFrontend.case(caseExpr: Expression? = null): CaseStatement { +fun LanguageFrontend<*, *>.case(caseExpr: Expression? = null): CaseStatement { val node = newCaseStatement() node.caseExpression = caseExpr @@ -724,7 +730,7 @@ fun LanguageFrontend.case(caseExpr: Expression? = null): CaseStatement { */ context(Holder) -fun LanguageFrontend.default(): DefaultStatement { +fun LanguageFrontend<*, *>.default(): DefaultStatement { val node = newDefaultStatement() // Only add this to a statement holder if the nearest holder is a statement holder @@ -742,7 +748,7 @@ fun LanguageFrontend.default(): DefaultStatement { */ context(Holder) -fun LanguageFrontend.literal(value: N, type: Type = newUnknownType()): Literal { +fun LanguageFrontend<*, *>.literal(value: N, type: Type = newUnknownType()): Literal { val node = newLiteral(value, type) // Only add this to an argument holder if the nearest holder is an argument holder @@ -761,7 +767,7 @@ fun LanguageFrontend.literal(value: N, type: Type = newUnknownType()): Liter */ context(Holder) -fun LanguageFrontend.ref( +fun LanguageFrontend<*, *>.ref( name: CharSequence, type: Type = newUnknownType(), init: (DeclaredReferenceExpression.() -> Unit)? = null @@ -789,7 +795,7 @@ fun LanguageFrontend.ref( */ context(Holder) -fun LanguageFrontend.member( +fun LanguageFrontend<*, *>.member( name: CharSequence, base: Expression? = null, operatorCode: String = "." @@ -823,7 +829,7 @@ fun LanguageFrontend.member( * Creates a new [BinaryOperator] with a `*` [BinaryOperator.operatorCode] in the Fluent Node DSL * and invokes [ArgumentHolder.addArgument] of the nearest enclosing [ArgumentHolder]. */ -context(LanguageFrontend, ArgumentHolder) +context(LanguageFrontend<*, *>, ArgumentHolder) operator fun Expression.times(rhs: Expression): BinaryOperator { val node = (this@LanguageFrontend).newBinaryOperator("*") @@ -845,7 +851,7 @@ operator fun Expression.times(rhs: Expression): BinaryOperator { * Creates a new [BinaryOperator] with a `+` [BinaryOperator.operatorCode] in the Fluent Node DSL * and invokes [ArgumentHolder.addArgument] of the nearest enclosing [ArgumentHolder]. */ -context(LanguageFrontend, ArgumentHolder) +context(LanguageFrontend<*, *>, ArgumentHolder) operator fun Expression.plus(rhs: Expression): BinaryOperator { val node = (this@LanguageFrontend).newBinaryOperator("+") @@ -867,7 +873,7 @@ operator fun Expression.plus(rhs: Expression): BinaryOperator { * Creates a new [BinaryOperator] with a `+` [BinaryOperator.operatorCode] in the Fluent Node DSL * and invokes [StatementHolder.addStatement] of the nearest enclosing [StatementHolder]. */ -context(LanguageFrontend, StatementHolder) +context(LanguageFrontend<*, *>, StatementHolder) operator fun Expression.plusAssign(rhs: Expression): Unit { val node = (this@LanguageFrontend).newBinaryOperator("+=") @@ -881,7 +887,7 @@ operator fun Expression.plusAssign(rhs: Expression): Unit { * Creates a new [BinaryOperator] with a `+` [BinaryOperator.operatorCode] in the Fluent Node DSL * and invokes [ArgumentHolder.addArgument] of the nearest enclosing [ArgumentHolder]. */ -context(LanguageFrontend, ArgumentHolder) +context(LanguageFrontend<*, *>, ArgumentHolder) operator fun Expression.rem(rhs: Expression): BinaryOperator { val node = (this@LanguageFrontend).newBinaryOperator("%") @@ -903,7 +909,7 @@ operator fun Expression.rem(rhs: Expression): BinaryOperator { * Creates a new [BinaryOperator] with a `-` [BinaryOperator.operatorCode] in the Fluent Node DSL * and invokes [ArgumentHolder.addArgument] of the nearest enclosing [ArgumentHolder]. */ -context(LanguageFrontend, ArgumentHolder) +context(LanguageFrontend<*, *>, ArgumentHolder) operator fun Expression.minus(rhs: Expression): BinaryOperator { val node = (this@LanguageFrontend).newBinaryOperator("-") @@ -919,7 +925,7 @@ operator fun Expression.minus(rhs: Expression): BinaryOperator { * Creates a new [UnaryOperator] with a `&` [UnaryOperator.operatorCode] in the Fluent Node DSL and * invokes [ArgumentHolder.addArgument] of the nearest enclosing [ArgumentHolder]. */ -context(LanguageFrontend, ArgumentHolder) +context(LanguageFrontend<*, *>, ArgumentHolder) fun reference(input: Expression): UnaryOperator { val node = (this@LanguageFrontend).newUnaryOperator("&", false, false) @@ -934,7 +940,7 @@ fun reference(input: Expression): UnaryOperator { * Creates a new [UnaryOperator] with a `--` [UnaryOperator.operatorCode] in the Fluent Node DSL and * invokes [StatementHolder.addStatement] of the nearest enclosing [StatementHolder]. */ -context(LanguageFrontend, Holder) +context(LanguageFrontend<*, *>, Holder) operator fun Expression.dec(): UnaryOperator { val node = (this@LanguageFrontend).newUnaryOperator("--", true, false) @@ -951,7 +957,7 @@ operator fun Expression.dec(): UnaryOperator { * Creates a new [UnaryOperator] with a `++` [UnaryOperator.operatorCode] in the Fluent Node DSL and * invokes [ArgumentHolder.addArgument] of the nearest enclosing [ArgumentHolder]. */ -context(LanguageFrontend, Holder) +context(LanguageFrontend<*, *>, Holder) operator fun Expression.inc(): UnaryOperator { val node = (this@LanguageFrontend).newUnaryOperator("++", true, false) @@ -968,7 +974,7 @@ operator fun Expression.inc(): UnaryOperator { * Creates a new [BinaryOperator] with a `==` [BinaryOperator.operatorCode] in the Fluent Node DSL * and invokes [ArgumentHolder.addArgument] of the nearest enclosing [ArgumentHolder]. */ -context(LanguageFrontend, ArgumentHolder) +context(LanguageFrontend<*, *>, ArgumentHolder) infix fun Expression.eq(rhs: Expression): BinaryOperator { val node = (this@LanguageFrontend).newBinaryOperator("==") @@ -984,7 +990,7 @@ infix fun Expression.eq(rhs: Expression): BinaryOperator { * Creates a new [BinaryOperator] with a `>` [BinaryOperator.operatorCode] in the Fluent Node DSL * and invokes [ArgumentHolder.addArgument] of the nearest enclosing [ArgumentHolder]. */ -context(LanguageFrontend, ArgumentHolder) +context(LanguageFrontend<*, *>, ArgumentHolder) infix fun Expression.gt(rhs: Expression): BinaryOperator { val node = (this@LanguageFrontend).newBinaryOperator(">") @@ -1000,7 +1006,7 @@ infix fun Expression.gt(rhs: Expression): BinaryOperator { * Creates a new [BinaryOperator] with a `<` [BinaryOperator.operatorCode] in the Fluent Node DSL * and invokes [ArgumentHolder.addArgument] of the nearest enclosing [ArgumentHolder]. */ -context(LanguageFrontend, ArgumentHolder) +context(LanguageFrontend<*, *>, ArgumentHolder) infix fun Expression.lt(rhs: Expression): BinaryOperator { val node = (this@LanguageFrontend).newBinaryOperator("<") @@ -1016,7 +1022,7 @@ infix fun Expression.lt(rhs: Expression): BinaryOperator { * Creates a new [ConditionalExpression] with a `=` [BinaryOperator.operatorCode] in the Fluent Node * DSL and invokes [StatementHolder.addStatement] of the nearest enclosing [StatementHolder]. */ -context(LanguageFrontend, StatementHolder) +context(LanguageFrontend<*, *>, StatementHolder) fun Expression.conditional( condition: Expression, @@ -1034,7 +1040,7 @@ fun Expression.conditional( * Creates a new [BinaryOperator] with a `=` [BinaryOperator.operatorCode] in the Fluent Node DSL * and invokes [StatementHolder.addStatement] of the nearest enclosing [StatementHolder]. */ -context(LanguageFrontend, StatementHolder) +context(LanguageFrontend<*, *>, StatementHolder) infix fun Expression.assign(init: BinaryOperator.() -> Expression): BinaryOperator { val node = (this@LanguageFrontend).newBinaryOperator("=") @@ -1050,7 +1056,7 @@ infix fun Expression.assign(init: BinaryOperator.() -> Expression): BinaryOperat * Creates a new [BinaryOperator] with a `=` [BinaryOperator.operatorCode] in the Fluent Node DSL * and invokes [StatementHolder.addStatement] of the nearest enclosing [StatementHolder]. */ -context(LanguageFrontend, Holder) +context(LanguageFrontend<*, *>, Holder) infix fun Expression.assign(rhs: Expression): BinaryOperator { val node = (this@LanguageFrontend).newBinaryOperator("=") @@ -1065,7 +1071,7 @@ infix fun Expression.assign(rhs: Expression): BinaryOperator { } /** Creates a new [Type] with the given [name] in the Fluent Node DSL. */ -fun LanguageFrontend.t(name: CharSequence, init: (Type.() -> Unit)? = null): Type { +fun LanguageFrontend<*, *>.t(name: CharSequence, init: (Type.() -> Unit)? = null): Type { val type = parseType(name) if (init != null) { init(type) @@ -1077,7 +1083,7 @@ fun LanguageFrontend.t(name: CharSequence, init: (Type.() -> Unit)? = null): Typ * Internally used to enter a new scope if [needsScope] is true before invoking [init] and leaving * it afterwards. */ -private fun LanguageFrontend.scopeIfNecessary( +private fun LanguageFrontend<*, *>.scopeIfNecessary( needsScope: Boolean, node: T, init: T.() -> Unit diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/BooleanType.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/BooleanType.kt index a81cb7363b..e3b951dcd0 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/BooleanType.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/BooleanType.kt @@ -26,13 +26,12 @@ package de.fraunhofer.aisec.cpg.graph.types import de.fraunhofer.aisec.cpg.frontends.Language -import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend /** Instances of this class represent boolean types. */ class BooleanType( typeName: CharSequence = "bool", bitWidth: Int? = 1, - language: Language? = null, + language: Language<*>? = null, modifier: Modifier = Modifier.NOT_APPLICABLE ) : NumericType(typeName, bitWidth, language, modifier) { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/FloatingPointType.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/FloatingPointType.kt index e34c4a01c6..9a33cfafda 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/FloatingPointType.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/FloatingPointType.kt @@ -26,13 +26,12 @@ package de.fraunhofer.aisec.cpg.graph.types import de.fraunhofer.aisec.cpg.frontends.Language -import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend /** Instances of this class represent floating point types. */ class FloatingPointType( typeName: CharSequence = "", bitWidth: Int? = null, - language: Language? = null, + language: Language<*>? = null, modifier: Modifier = Modifier.SIGNED ) : NumericType(typeName, bitWidth, language, modifier) { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/FunctionPointerType.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/FunctionPointerType.kt index 1a43e19cce..a4d0d47119 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/FunctionPointerType.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/FunctionPointerType.kt @@ -26,7 +26,6 @@ package de.fraunhofer.aisec.cpg.graph.types import de.fraunhofer.aisec.cpg.frontends.Language -import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.propertyEqualsList import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.transformIntoOutgoingPropertyEdgeList @@ -57,7 +56,7 @@ class FunctionPointerType : Type { constructor( parameters: List = listOf(), - language: Language? = null, + language: Language<*>? = null, returnType: Type = UnknownType.getUnknownType(language) ) : super(EMPTY_NAME, language) { parametersPropertyEdge = transformIntoOutgoingPropertyEdgeList(parameters, this) @@ -67,7 +66,7 @@ class FunctionPointerType : Type { constructor( type: Type, parameters: List = listOf(), - language: Language? = null, + language: Language<*>? = null, returnType: Type = UnknownType.getUnknownType(language) ) : super(type) { parametersPropertyEdge = transformIntoOutgoingPropertyEdgeList(parameters, this) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/FunctionType.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/FunctionType.kt index 182a2b2d47..7f10a8172c 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/FunctionType.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/FunctionType.kt @@ -26,7 +26,6 @@ package de.fraunhofer.aisec.cpg.graph.types import de.fraunhofer.aisec.cpg.frontends.Language -import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration import de.fraunhofer.aisec.cpg.graph.newUnknownType import de.fraunhofer.aisec.cpg.graph.registerType @@ -43,7 +42,7 @@ constructor( typeName: String = "", var parameters: List = listOf(), var returnTypes: List = listOf(), - language: Language? = null + language: Language<*>? = null ) : Type(typeName, language) { override fun reference(pointer: PointerType.PointerOrigin?): Type { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/IntegerType.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/IntegerType.kt index 353ac3e599..c788bc970f 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/IntegerType.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/IntegerType.kt @@ -26,13 +26,12 @@ package de.fraunhofer.aisec.cpg.graph.types import de.fraunhofer.aisec.cpg.frontends.Language -import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend /** Instances of this class represent integer types. */ class IntegerType( typeName: CharSequence = "", bitWidth: Int? = null, - language: Language? = null, + language: Language<*>? = null, modifier: Modifier = Modifier.SIGNED ) : NumericType(typeName, bitWidth, language, modifier) { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/NumericType.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/NumericType.kt index 876cf833b0..498fd2a252 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/NumericType.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/NumericType.kt @@ -26,14 +26,13 @@ package de.fraunhofer.aisec.cpg.graph.types import de.fraunhofer.aisec.cpg.frontends.Language -import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend import java.util.* /** This type collects all kind of numeric types. */ open class NumericType( typeName: CharSequence = "", val bitWidth: Int? = null, - language: Language? = null, + language: Language<*>? = null, val modifier: Modifier = Modifier.SIGNED ) : ObjectType(typeName, listOf(), true, language) { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/ObjectType.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/ObjectType.kt index d453007679..d81ecc441e 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/ObjectType.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/ObjectType.kt @@ -26,7 +26,6 @@ package de.fraunhofer.aisec.cpg.graph.types import de.fraunhofer.aisec.cpg.frontends.Language -import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend import de.fraunhofer.aisec.cpg.graph.HasType.SecondaryTypeEdge import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration import de.fraunhofer.aisec.cpg.graph.edge.Properties @@ -58,7 +57,7 @@ open class ObjectType : Type, SecondaryTypeEdge { typeName: CharSequence, generics: List, primitive: Boolean, - language: Language? + language: Language<*>? ) : super(typeName, language) { this.genericsPropertyEdges = transformIntoOutgoingPropertyEdgeList(generics, this) isPrimitive = primitive @@ -69,7 +68,7 @@ open class ObjectType : Type, SecondaryTypeEdge { type: Type?, generics: List, primitive: Boolean, - language: Language? + language: Language<*>? ) : super(type) { this.language = language this.genericsPropertyEdges = transformIntoOutgoingPropertyEdgeList(generics, this) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/ParameterizedType.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/ParameterizedType.kt index 77a9a06953..2ced2d0d51 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/ParameterizedType.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/ParameterizedType.kt @@ -26,7 +26,6 @@ package de.fraunhofer.aisec.cpg.graph.types import de.fraunhofer.aisec.cpg.frontends.Language -import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend import de.fraunhofer.aisec.cpg.graph.types.PointerType.PointerOrigin /** @@ -38,7 +37,7 @@ class ParameterizedType : Type { language = type.language } - constructor(typeName: String?, language: Language?) : super(typeName) { + constructor(typeName: String?, language: Language<*>?) : super(typeName) { this.language = language } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/StringType.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/StringType.kt index 633a1dbf8d..0be5e3eb34 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/StringType.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/StringType.kt @@ -26,11 +26,10 @@ package de.fraunhofer.aisec.cpg.graph.types import de.fraunhofer.aisec.cpg.frontends.Language -import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend class StringType( typeName: CharSequence = "", - language: Language? = null, + language: Language<*>? = null, generics: List = listOf() ) : ObjectType(typeName, generics, false, language) { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/Type.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/Type.kt index a666695879..f293c99218 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/Type.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/Type.kt @@ -27,7 +27,6 @@ package de.fraunhofer.aisec.cpg.graph.types import de.fraunhofer.aisec.cpg.PopulatedByPass import de.fraunhofer.aisec.cpg.frontends.Language -import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend import de.fraunhofer.aisec.cpg.graph.Name import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.parseName @@ -68,7 +67,7 @@ abstract class Type : Node { typeOrigin = type?.typeOrigin } - constructor(typeName: CharSequence, language: Language?) { + constructor(typeName: CharSequence, language: Language<*>?) { name = if (this is FunctionType) { Name(typeName.toString(), null, language) @@ -79,7 +78,7 @@ abstract class Type : Node { typeOrigin = Origin.UNRESOLVED } - constructor(fullTypeName: Name, language: Language?) { + constructor(fullTypeName: Name, language: Language<*>?) { name = fullTypeName.clone() typeOrigin = Origin.UNRESOLVED this.language = language diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/UnknownType.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/UnknownType.kt index a31b79dd44..84a5947453 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/UnknownType.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/UnknownType.kt @@ -26,7 +26,6 @@ package de.fraunhofer.aisec.cpg.graph.types import de.fraunhofer.aisec.cpg.frontends.Language -import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend import de.fraunhofer.aisec.cpg.graph.Name import de.fraunhofer.aisec.cpg.graph.types.PointerType.PointerOrigin import java.util.* @@ -88,7 +87,7 @@ class UnknownType : Type { /** Use this function to obtain an [UnknownType] for the particular [language]. */ @JvmStatic - fun getUnknownType(language: Language?): UnknownType { + fun getUnknownType(language: Language<*>?): UnknownType { if (language == null) return unknownTypeNull return unknownTypes.computeIfAbsent(language) { 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 fd13835ba4..a86ca0329a 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 @@ -393,7 +393,7 @@ object SubgraphWalker { private var walker: IterativeGraphWalker? = null private val scopeManager: ScopeManager - constructor(lang: LanguageFrontend) { + constructor(lang: LanguageFrontend<*, *>) { scopeManager = lang.scopeManager } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/Util.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/Util.kt index e7e57d3bad..ab9f412817 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/Util.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/Util.kt @@ -177,9 +177,9 @@ object Util { } @JvmStatic - fun warnWithFileLocation( - lang: LanguageFrontend, - astNode: S, + fun warnWithFileLocation( + lang: LanguageFrontend, + astNode: AstNode, log: Logger, format: String?, vararg arguments: Any? @@ -187,7 +187,7 @@ object Util { log.warn( String.format( "%s: %s", - PhysicalLocation.locationLink(lang.getLocationFromRawNode(astNode)), + PhysicalLocation.locationLink(lang.locationOf(astNode)), format ), *arguments @@ -195,9 +195,9 @@ object Util { } @JvmStatic - fun errorWithFileLocation( - lang: LanguageFrontend, - astNode: S, + fun errorWithFileLocation( + lang: LanguageFrontend, + astNode: AstNode, log: Logger, format: String?, vararg arguments: Any? @@ -205,7 +205,7 @@ object Util { log.error( String.format( "%s: %s", - PhysicalLocation.locationLink(lang.getLocationFromRawNode(astNode)), + PhysicalLocation.locationLink(lang.locationOf(astNode)), format ), *arguments diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolver.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolver.kt index e1322036a0..ae2f943134 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolver.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolver.kt @@ -553,7 +553,7 @@ open class CallResolver(ctx: TranslationContext) : SymbolResolverPass(ctx) { } } - protected val Language?.isCPP: Boolean + protected val Language<*>?.isCPP: Boolean get() { return this != null && this::class.simpleName == "CPPLanguage" } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/Pass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/Pass.kt index 5a466b8c40..2ad6a0ac13 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/Pass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/Pass.kt @@ -99,7 +99,7 @@ sealed class Pass(final override val ctx: TranslationContext) : * @return true, if the pass does not require a specific language frontend or if it matches the * [RequiredFrontend] */ - fun runsWithCurrentFrontend(usedFrontends: Collection): Boolean { + fun runsWithCurrentFrontend(usedFrontends: Collection>): Boolean { if (!this.javaClass.isAnnotationPresent(RequiredFrontend::class.java)) return true val requiredFrontend = this.javaClass.getAnnotation(RequiredFrontend::class.java).value for (used in usedFrontends) { @@ -123,7 +123,7 @@ fun executePassSequential( cls: KClass>, ctx: TranslationContext, result: TranslationResult, - executedFrontends: Collection + executedFrontends: Collection> ) { // This is a bit tricky but actually better than other reflection magic. We are creating a // "prototype" instance of our pass class, so we can deduce certain type information more @@ -160,7 +160,7 @@ inline fun executePass( cls: KClass>, ctx: TranslationContext, target: T, - executedFrontends: Collection + executedFrontends: Collection> ): Pass? { val language = if (target is LanguageProvider) { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/Inference.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/Inference.kt index 5352160dbb..d532673e72 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/Inference.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/Inference.kt @@ -28,7 +28,6 @@ package de.fraunhofer.aisec.cpg.passes.inference import de.fraunhofer.aisec.cpg.TranslationContext import de.fraunhofer.aisec.cpg.frontends.HasClasses import de.fraunhofer.aisec.cpg.frontends.Language -import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.declarations.* import de.fraunhofer.aisec.cpg.graph.scopes.Scope @@ -54,7 +53,7 @@ class Inference(val start: Node, override val ctx: TranslationContext) : LanguageProvider, ScopeProvider, IsInferredProvider, ContextProvider { val log: Logger = LoggerFactory.getLogger(Inference::class.java) - override val language: Language? + override val language: Language<*>? get() = start.language override val isInferred: Boolean diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/order/RequiredFrontend.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/order/RequiredFrontend.kt index de3d1574c2..d79fd21a8f 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/order/RequiredFrontend.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/order/RequiredFrontend.kt @@ -34,4 +34,4 @@ import kotlin.reflect.KClass */ @Retention(AnnotationRetention.RUNTIME) @Target(AnnotationTarget.CLASS) -annotation class RequiredFrontend(val value: KClass) +annotation class RequiredFrontend(val value: KClass>) diff --git a/cpg-core/src/testFixtures/kotlin/de/fraunhofer/aisec/cpg/frontends/TestLanguage.kt b/cpg-core/src/testFixtures/kotlin/de/fraunhofer/aisec/cpg/frontends/TestLanguage.kt index 8230d9c1dc..9cb0346740 100644 --- a/cpg-core/src/testFixtures/kotlin/de/fraunhofer/aisec/cpg/frontends/TestLanguage.kt +++ b/cpg-core/src/testFixtures/kotlin/de/fraunhofer/aisec/cpg/frontends/TestLanguage.kt @@ -29,6 +29,7 @@ import de.fraunhofer.aisec.cpg.* import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.TypeManager import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration +import de.fraunhofer.aisec.cpg.graph.newUnknownType import de.fraunhofer.aisec.cpg.graph.statements.expressions.ProblemExpression import de.fraunhofer.aisec.cpg.graph.types.* import de.fraunhofer.aisec.cpg.sarif.PhysicalLocation @@ -74,27 +75,32 @@ class StructTestLanguage(namespaceDelimiter: String = "::") : open class TestLanguageFrontend( namespaceDelimiter: String = "::", - language: Language = TestLanguage(namespaceDelimiter), + language: Language = TestLanguage(namespaceDelimiter), ctx: TranslationContext = TranslationContext( TranslationConfiguration.builder().build(), ScopeManager(), TypeManager() ), -) : LanguageFrontend(language, ctx) { +) : LanguageFrontend(language, ctx) { override fun parse(file: File): TranslationUnitDeclaration { TODO("Not yet implemented") } - override fun getCodeFromRawNode(astNode: T): String? { + override fun typeOf(type: Any): Type { + // reserved for future use + return newUnknownType() + } + + override fun codeOf(astNode: Any): String? { TODO("Not yet implemented") } - override fun getLocationFromRawNode(astNode: T): PhysicalLocation? { + override fun locationOf(astNode: Any): PhysicalLocation? { TODO("Not yet implemented") } - override fun setComment(s: S, ctx: T) { + override fun setComment(node: Node, astNode: Any) { TODO("Not yet implemented") } } diff --git a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXHandler.kt b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXHandler.kt index e2b5224d7f..3b2cba1412 100644 --- a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXHandler.kt +++ b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXHandler.kt @@ -30,9 +30,10 @@ import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.ProblemNode import de.fraunhofer.aisec.cpg.helpers.Util import java.util.function.Supplier +import org.eclipse.cdt.core.dom.ast.IASTNode import org.eclipse.cdt.internal.core.dom.parser.ASTNode -abstract class CXXHandler( +abstract class CXXHandler( configConstructor: Supplier, lang: CXXLanguageFrontend ) : Handler(configConstructor, lang) { @@ -58,7 +59,7 @@ abstract class CXXHandler( // The language frontend might set a location, which we should respect. Otherwise, we will // set the location here. if (node.location == null) { - frontend.setCodeAndLocation(node, ctx) + frontend.setCodeAndLocation(node, ctx) } frontend.setComment(node, ctx) diff --git a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXLanguageFrontend.kt b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXLanguageFrontend.kt index e102a7cea0..891382a383 100644 --- a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXLanguageFrontend.kt +++ b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXLanguageFrontend.kt @@ -79,7 +79,7 @@ import org.slf4j.LoggerFactory */ @RegisterExtraPass(FunctionPointerCallResolver::class) class CXXLanguageFrontend(language: Language, ctx: TranslationContext) : - LanguageFrontend(language, ctx) { + LanguageFrontend(language, ctx) { /** * The dialect used by this language frontend, either [GCCLanguage] for C or [GPPLanguage] for @@ -266,21 +266,13 @@ class CXXLanguageFrontend(language: Language, ctx: Translat } } - override fun getCodeFromRawNode(astNode: T): String? { - if (astNode is ASTNode) { - val node = astNode as ASTNode - return node.rawSignature - } - - return null + override fun codeOf(astNode: IASTNode): String? { + val node = astNode as ASTNode + return node.rawSignature } - override fun getLocationFromRawNode(astNode: T): PhysicalLocation? { - if (astNode !is ASTNode) { - return null - } - val node = astNode as ASTNode - val fLocation = node.fileLocation ?: return null + override fun locationOf(astNode: IASTNode): PhysicalLocation? { + val fLocation = astNode.fileLocation ?: return null val lineBreaks: IntArray = try { val fLoc = getField(fLocation.javaClass, "fLocationCtx") @@ -313,19 +305,19 @@ class CXXLanguageFrontend(language: Language, ctx: Translat } // our start line, indexed by 0 - val startLine = node.fileLocation.startingLineNumber - 1 + val startLine = astNode.fileLocation.startingLineNumber - 1 // our end line, indexed by 0 - val endLine = node.fileLocation.endingLineNumber - 1 + val endLine = astNode.fileLocation.endingLineNumber - 1 // our start column, index by 0 val startColumn = if (startLine == 0) { // if we are in the first line, the start column is just the node offset - node.fileLocation.nodeOffset + astNode.fileLocation.nodeOffset } else { // otherwise, we need to calculate the difference to the previous line break - node.fileLocation.nodeOffset - + astNode.fileLocation.nodeOffset - lineBreaks[startLine - 1] - 1 // additional -1 because of the '\n' itself } @@ -334,10 +326,10 @@ class CXXLanguageFrontend(language: Language, ctx: Translat val endColumn = if (endLine == 0) { // if we are in the first line, the end column is just the node offset - node.fileLocation.nodeOffset + node.fileLocation.nodeLength + astNode.fileLocation.nodeOffset + astNode.fileLocation.nodeLength } else { // otherwise, we need to calculate the difference to the previous line break - (node.fileLocation.nodeOffset + node.fileLocation.nodeLength) - + (astNode.fileLocation.nodeOffset + astNode.fileLocation.nodeLength) - lineBreaks[endLine - 1] - 1 // additional -1 because of the '\n' itself } @@ -345,7 +337,7 @@ class CXXLanguageFrontend(language: Language, ctx: Translat // for a SARIF compliant format, we need to add +1, since its index begins at 1 and // not 0 val region = Region(startLine + 1, startColumn + 1, endLine + 1, endColumn + 1) - return PhysicalLocation(Path.of(node.containingFilename).toUri(), region) + return PhysicalLocation(Path.of(astNode.containingFilename).toUri(), region) } /** @@ -446,28 +438,25 @@ class CXXLanguageFrontend(language: Language, ctx: Translat } } - override fun setComment(s: S, ctx: T) { - if (ctx is ASTNode && s is Node) { - val cpgNode = s as Node - val location = cpgNode.location ?: return + override fun setComment(node: Node, astNode: IASTNode) { + val location = node.location ?: return - // No location, no comment + // No location, no comment - val loc: Pair = - Pair(location.artifactLocation.uri.path, location.region.endLine) - comments[loc]?.let { - // only exact match for now} - cpgNode.comment = it - } - // TODO: handle orphanComments? i.e. comments which do not correspond to one line - // todo: what to do with comments which are in a line which contains multiple - // statements? + val loc: Pair = + Pair(location.artifactLocation.uri.path, location.region.endLine) + comments[loc]?.let { + // only exact match for now} + node.comment = it } + // TODO: handle orphanComments? i.e. comments which do not correspond to one line + // TODO: what to do with comments which are in a line which contains multiple + // statements? } /** Returns the [Type] that is represented by an [IASTTypeId]. */ - fun typeOf(id: IASTTypeId): Type { - return typeOf(id.abstractDeclarator, id.declSpecifier) + override fun typeOf(type: IASTTypeId): Type { + return typeOf(type.abstractDeclarator, type.declSpecifier) } /** diff --git a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/DeclarationHandler.kt b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/DeclarationHandler.kt index 968d817e86..4f70039e99 100644 --- a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/DeclarationHandler.kt +++ b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/DeclarationHandler.kt @@ -87,8 +87,7 @@ class DeclarationHandler(lang: CXXLanguageFrontend) : * into a [NamespaceDeclaration]. */ private fun handleNamespace(ctx: CPPASTNamespaceDefinition): NamespaceDeclaration { - val declaration = - newNamespaceDeclaration(ctx.name.toString(), frontend.getCodeFromRawNode(ctx)) + val declaration = newNamespaceDeclaration(ctx.name.toString(), frontend.codeOf(ctx)) frontend.scopeManager.addDeclaration(declaration) @@ -270,12 +269,12 @@ class DeclarationHandler(lang: CXXLanguageFrontend) : val templateDeclaration: TemplateDeclaration = if (ctx.declaration is CPPASTFunctionDefinition) { - newFunctionTemplateDeclaration(name, frontend.getCodeFromRawNode(ctx)) + newFunctionTemplateDeclaration(name, frontend.codeOf(ctx)) } else { - newClassTemplateDeclaration(name, frontend.getCodeFromRawNode(ctx)) + newClassTemplateDeclaration(name, frontend.codeOf(ctx)) } - templateDeclaration.location = frontend.getLocationFromRawNode(ctx) + templateDeclaration.location = frontend.locationOf(ctx) frontend.scopeManager.addDeclaration(templateDeclaration) frontend.scopeManager.enterScope(templateDeclaration) addTemplateParameters(ctx, templateDeclaration) @@ -543,7 +542,7 @@ class DeclarationHandler(lang: CXXLanguageFrontend) : val declaration = frontend.typeManager.createTypeAlias( frontend, - frontend.getCodeFromRawNode(ctx), + frontend.codeOf(ctx), type, nameDecl.name.toString() ) @@ -562,7 +561,7 @@ class DeclarationHandler(lang: CXXLanguageFrontend) : val enum = newEnumDeclaration( name = declSpecifier.name.toString(), - location = frontend.getLocationFromRawNode(ctx), + location = frontend.locationOf(ctx), ) // Loop through its members @@ -570,8 +569,8 @@ class DeclarationHandler(lang: CXXLanguageFrontend) : val enumConst = newEnumConstantDeclaration( enumerator.name.toString(), - frontend.getCodeFromRawNode(enumerator), - frontend.getLocationFromRawNode(enumerator), + frontend.codeOf(enumerator), + frontend.locationOf(enumerator), ) // In C/C++, default enums are of type int diff --git a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/DeclaratorHandler.kt b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/DeclaratorHandler.kt index 7c4291fb58..08cbc829c2 100644 --- a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/DeclaratorHandler.kt +++ b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/DeclaratorHandler.kt @@ -51,9 +51,9 @@ import org.eclipse.cdt.internal.core.dom.parser.cpp.* * See [DeclarationHandler] for a detailed explanation, why this is split into a dedicated handler. */ class DeclaratorHandler(lang: CXXLanguageFrontend) : - CXXHandler(Supplier(::ProblemDeclaration), lang) { + CXXHandler(Supplier(::ProblemDeclaration), lang) { - override fun handleNode(node: IASTNameOwner): Declaration { + override fun handleNode(node: IASTNode): Declaration { return when (node) { is CPPASTFunctionDeclarator -> handleCPPFunctionDeclarator(node) is IASTStandardFunctionDeclarator -> handleFunctionDeclarator(node) @@ -105,7 +105,7 @@ class DeclaratorHandler(lang: CXXLanguageFrontend) : // Check, if the name is qualified or if we are within a record scope return if ( (frontend.scopeManager.currentScope is RecordScope || - name.contains(language.namespaceDelimiter)) + language?.namespaceDelimiter?.let { name.contains(it) } == true) ) { // If yes, treat this like a field declaration this.handleFieldDeclarator(ctx) @@ -144,32 +144,18 @@ class DeclaratorHandler(lang: CXXLanguageFrontend) : private fun handleFieldDeclarator(ctx: IASTDeclarator): FieldDeclaration { val initializer = ctx.initializer?.let { frontend.initializerHandler.handle(it) } - val name = ctx.name.toString() + val name = parseName(ctx.name.toString()) val declaration = - if (name.contains(language.namespaceDelimiter)) { - val rr = name.split(language.namespaceDelimiter).toTypedArray() - val fieldName = rr[rr.size - 1] - newFieldDeclaration( - fieldName, - newUnknownType(), - emptyList(), - ctx.rawSignature, - frontend.getLocationFromRawNode(ctx), - initializer, - true - ) - } else { - newFieldDeclaration( - name, - newUnknownType(), - emptyList(), - ctx.rawSignature, - frontend.getLocationFromRawNode(ctx), - initializer, - true - ) - } + newFieldDeclaration( + name.localName, + newUnknownType(), + emptyList(), + ctx.rawSignature, + frontend.locationOf(ctx), + initializer, + true + ) frontend.scopeManager.addDeclaration(declaration) @@ -370,7 +356,7 @@ class DeclaratorHandler(lang: CXXLanguageFrontend) : "CDT tells us this is a (named) function declaration in parenthesis without a body directly within a block scope, this might be an ambiguity which we cannot solve currently." ) - Util.warnWithFileLocation(frontend, ctx, log, problem.problem) + Util.warnWithFileLocation(frontend, ctx, log, problem.problem) return problem } @@ -434,13 +420,13 @@ class DeclaratorHandler(lang: CXXLanguageFrontend) : newUnknownType(), emptyList(), code, - frontend.getLocationFromRawNode(ctx), + frontend.locationOf(ctx), initializer, true ) } - result.location = frontend.getLocationFromRawNode(ctx) + result.location = frontend.locationOf(ctx) frontend.scopeManager.addDeclaration(result) return result } diff --git a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/ExpressionHandler.kt b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/ExpressionHandler.kt index 33dfd68221..14002004a0 100644 --- a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/ExpressionHandler.kt +++ b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/ExpressionHandler.kt @@ -82,7 +82,7 @@ class ExpressionHandler(lang: CXXLanguageFrontend) : } private fun handleLambdaExpression(node: CPPASTLambdaExpression): Expression { - val lambda = newLambdaExpression(frontend.getCodeFromRawNode(node)) + val lambda = newLambdaExpression(frontend.codeOf(node)) // Variables passed by reference are mutable. If we have initializers, we have to model the // variable explicitly. @@ -206,8 +206,7 @@ class ExpressionHandler(lang: CXXLanguageFrontend) : } // new returns a pointer, so we need to reference the type by pointer - val newExpression = - newNewExpression(code, t.reference(PointerOrigin.POINTER), frontend.language) + val newExpression = newNewExpression(code, t.reference(PointerOrigin.POINTER), ctx) newExpression.templateParameters = templateParameters val initializer: Expression? if (init != null) { @@ -357,10 +356,10 @@ class ExpressionHandler(lang: CXXLanguageFrontend) : // this can either be just a meaningless bracket or it can be a cast expression val typeName = (ctx.operand as IASTIdExpression).name.toString() if (frontend.typeManager.typeExists(typeName)) { - val cast = newCastExpression(frontend.getCodeFromRawNode(ctx)) + val cast = newCastExpression(frontend.codeOf(ctx)) cast.castType = parseType(typeName) cast.expression = input ?: newProblemExpression("could not parse input") - cast.location = frontend.getLocationFromRawNode(ctx) + cast.location = frontend.locationOf(ctx) return cast } } diff --git a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/PerformanceRegressionTest.kt b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/PerformanceRegressionTest.kt index ef33d46eaf..03b473a6af 100644 --- a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/PerformanceRegressionTest.kt +++ b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/PerformanceRegressionTest.kt @@ -53,7 +53,7 @@ class PerformanceRegressionTest { * in reasonable time. We had issues with literals and their hashcode when they were inserted * into a set. * * Second, we want to make that list essentially a one-liner because we had issues when - * populating the [Node.location] property using [CXXLanguageFrontend.getLocationFromRawNode]. + * populating the [Node.location] property using [CXXLanguageFrontend.locationOf]. */ @Test fun testParseLargeList() { diff --git a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXLanguageFrontendTest.kt b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXLanguageFrontendTest.kt index 9c1c5b0692..088206af73 100644 --- a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXLanguageFrontendTest.kt +++ b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXLanguageFrontendTest.kt @@ -1076,10 +1076,10 @@ internal class CXXLanguageFrontendTest : BaseTest() { fun testLocation() { val file = File("src/test/resources/components/foreachstmt.cpp") val tu = analyzeAndGetFirstTU(listOf(file), file.parentFile.toPath(), true) - val main = tu.getDeclarationsByName("main", FunctionDeclaration::class.java) - assertFalse(main.isEmpty()) + val main = tu.functions["main"] + assertNotNull(main) - val location = main.iterator().next().location + val location = main.location assertNotNull(location) val path = Path.of(location.artifactLocation.uri) 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 a615d99d00..d1a9ffe7a1 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 @@ -30,7 +30,10 @@ import de.fraunhofer.aisec.cpg.frontends.Language import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend import de.fraunhofer.aisec.cpg.frontends.SupportsParallelParsing import de.fraunhofer.aisec.cpg.frontends.TranslationException +import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration +import de.fraunhofer.aisec.cpg.graph.newUnknownType +import de.fraunhofer.aisec.cpg.graph.types.Type import de.fraunhofer.aisec.cpg.passes.GoExtraPass import de.fraunhofer.aisec.cpg.passes.order.RegisterExtraPass import de.fraunhofer.aisec.cpg.sarif.PhysicalLocation @@ -40,7 +43,7 @@ import java.io.FileOutputStream @SupportsParallelParsing(false) @RegisterExtraPass(GoExtraPass::class) class GoLanguageFrontend(language: Language, ctx: TranslationContext) : - LanguageFrontend(language, ctx) { + LanguageFrontend(language, ctx) { companion object { init { @@ -88,17 +91,24 @@ class GoLanguageFrontend(language: Language, ctx: Translatio ) } - override fun getCodeFromRawNode(astNode: T): String? { + override fun typeOf(type: Any): Type { + // this is handled by native code + return newUnknownType() + } + + override fun codeOf(astNode: Any): String? { // this is handled by native code return null } - override fun getLocationFromRawNode(astNode: T): PhysicalLocation? { + override fun locationOf(astNode: Any): PhysicalLocation? { // this is handled by native code return null } - override fun setComment(s: S, ctx: T) {} + override fun setComment(node: Node, astNode: Any) { + // this is handled by native code + } private external fun parseInternal( s: String?, diff --git a/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/DeclarationHandler.kt b/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/DeclarationHandler.kt index 59900c62db..0ef6768d38 100644 --- a/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/DeclarationHandler.kt +++ b/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/DeclarationHandler.kt @@ -323,7 +323,7 @@ open class DeclarationHandler(lang: JavaLanguageFrontend) : val variable = fieldDecl.getVariable(0) val modifiers = fieldDecl.modifiers.map { modifier -> modifier.keyword.asString() } val joinedModifiers = java.lang.String.join(" ", modifiers) + " " - val location = frontend.getLocationFromRawNode(fieldDecl) + val location = frontend.locationOf(fieldDecl) val initializer = variable.initializer .map { ctx: Expression -> frontend.expressionHandler.handle(ctx) } @@ -374,7 +374,7 @@ open class DeclarationHandler(lang: JavaLanguageFrontend) : enumDecl: com.github.javaparser.ast.body.EnumDeclaration ): EnumDeclaration { val name = enumDecl.nameAsString - val location = frontend.getLocationFromRawNode(enumDecl) + val location = frontend.locationOf(enumDecl) val enumDeclaration = this.newEnumDeclaration(name, enumDecl.toString(), location) val entries = enumDecl.entries.mapNotNull { handle(it) as EnumConstantDeclaration? } @@ -392,7 +392,7 @@ open class DeclarationHandler(lang: JavaLanguageFrontend) : return this.newEnumConstantDeclaration( enumConstDecl.nameAsString, enumConstDecl.toString(), - frontend.getLocationFromRawNode(enumConstDecl) + frontend.locationOf(enumConstDecl) ) } 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 cacbab7f16..1428eae0d4 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 @@ -49,8 +49,8 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : private fun handleLambdaExpr(expr: Expression): Statement { val lambdaExpr = expr.asLambdaExpr() - val lambda = newLambdaExpression(frontend.getCodeFromRawNode(lambdaExpr)) - val anonymousFunction = newFunctionDeclaration("", frontend.getCodeFromRawNode(lambdaExpr)) + val lambda = newLambdaExpression(frontend.codeOf(lambdaExpr)) + val anonymousFunction = newFunctionDeclaration("", frontend.codeOf(lambdaExpr)) frontend.scopeManager.enterScope(anonymousFunction) for (parameter in lambdaExpr.parameters) { val resolvedType = frontend.getTypeAsGoodAsPossible(parameter.type) @@ -326,12 +326,7 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : scope.toString() ) base.isStaticAccess = isStaticAccess - frontend.setCodeAndLocation< - de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression, Expression - >( - base, - fieldAccessExpr.scope - ) + frontend.setCodeAndLocation(base, fieldAccessExpr.scope) } else if (scope.isFieldAccessExpr) { base = handle(scope) as de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression? @@ -423,7 +418,7 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : return memberExpression } if (base.location == null) { - base.location = frontend.getLocationFromRawNode(fieldAccessExpr) + base.location = frontend.locationOf(fieldAccessExpr) } return this.newMemberExpression(fieldAccessExpr.name.identifier, base, fieldType, ".") } @@ -838,7 +833,7 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : if (objectCreationExpr.anonymousClassBody.isPresent) { // We have an anonymous class and will create a RecordDeclaration for it and add all the // implemented methods. - val locationHash = frontend.getLocationFromRawNode(objectCreationExpr)?.hashCode() + val locationHash = frontend.locationOf(objectCreationExpr)?.hashCode() // We use the hash of the location to distinguish multiple instances of the anonymous // class' superclass 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 a277e5b7a2..0d26100047 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 @@ -71,7 +71,7 @@ import java.util.function.Consumer JavaExternalTypeHierarchyResolver::class ) // this pass is always required for Java open class JavaLanguageFrontend(language: Language, ctx: TranslationContext) : - LanguageFrontend(language, ctx) { + LanguageFrontend(language, ctx) { var context: CompilationUnit? = null var javaSymbolResolver: JavaSymbolSolver? @@ -115,7 +115,7 @@ open class JavaLanguageFrontend(language: Language, ctx: T var namespaceDeclaration: NamespaceDeclaration? = null if (packDecl != null) { namespaceDeclaration = - newNamespaceDeclaration(packDecl.name.asString(), getCodeFromRawNode(packDecl)) + newNamespaceDeclaration(packDecl.name.asString(), codeOf(packDecl)) setCodeAndLocation(namespaceDeclaration, packDecl) scopeManager.addDeclaration(namespaceDeclaration) scopeManager.enterScope(namespaceDeclaration) @@ -144,6 +144,11 @@ open class JavaLanguageFrontend(language: Language, ctx: T } } + override fun typeOf(type: Type): de.fraunhofer.aisec.cpg.graph.types.Type { + // reserved for future use + return newUnknownType() + } + @Throws(TranslationException::class, FileNotFoundException::class) fun parse(file: File?, parser: JavaParser): CompilationUnit { val result = parser.parse(file) @@ -170,38 +175,31 @@ open class JavaLanguageFrontend(language: Language, ctx: T return optional.get() } - override fun getCodeFromRawNode(astNode: T): String { - if (astNode is Node) { - val node = astNode as Node - val optional = node.tokenRange - if (optional.isPresent) { - return optional.get().toString() - } + override fun codeOf(astNode: Node): String? { + val optional = astNode.tokenRange + if (optional?.isPresent == true) { + return optional.get().toString() } return astNode.toString() } - override fun getLocationFromRawNode(astNode: T): PhysicalLocation? { - if (astNode is Node) { - val node = astNode as Node - - // find compilation unit of node - val cu = node.findCompilationUnit().orElse(null) ?: return null - - // retrieve storage - val storage = cu.storage.orElse(null) ?: return null - val optional = node.range - if (optional.isPresent) { - val r = optional.get() - val region = - Region( - r.begin.line, - r.begin.column, - r.end.line, - r.end.column + 1 - ) // +1 for SARIF compliance - return PhysicalLocation(storage.path.toUri(), region) - } + override fun locationOf(astNode: Node): PhysicalLocation? { + // find compilation unit of node + val cu = astNode.findCompilationUnit().orElse(null) ?: return null + + // retrieve storage + val storage = cu.storage.orElse(null) ?: return null + val optional = astNode.range + if (optional.isPresent) { + val r = optional.get() + val region = + Region( + r.begin.line, + r.begin.column, + r.end.line, + r.end.column + 1 + ) // +1 for SARIF compliance + return PhysicalLocation(storage.path.toUri(), region) } return null } @@ -424,13 +422,8 @@ open class JavaLanguageFrontend(language: Language, ctx: T context = null } - override fun setComment(s: S, ctx: T) { - if (ctx is Node && s is de.fraunhofer.aisec.cpg.graph.Node) { - val node = ctx as Node - val cpgNode = s as de.fraunhofer.aisec.cpg.graph.Node - node.comment.ifPresent { comment: Comment -> cpgNode.comment = comment.content } - // TODO: handle orphanComments? - } + override fun setComment(node: de.fraunhofer.aisec.cpg.graph.Node, astNode: Node) { + astNode.comment.ifPresent { comment: Comment -> node.comment = comment.content } } /** @@ -451,7 +444,7 @@ open class JavaLanguageFrontend(language: Language, ctx: T private fun handleAnnotations(owner: NodeWithAnnotations<*>): List { val list = ArrayList() for (expr in owner.annotations) { - val annotation = newAnnotation(expr.nameAsString, getCodeFromRawNode(expr)) + val annotation = newAnnotation(expr.nameAsString, codeOf(expr)) val members = ArrayList() // annotations can be specified as member / value pairs @@ -461,7 +454,7 @@ open class JavaLanguageFrontend(language: Language, ctx: T newAnnotationMember( pair.nameAsString, expressionHandler.handle(pair.value) as Expression, - getCodeFromRawNode(pair) + codeOf(pair) ) members.add(member) } @@ -473,7 +466,7 @@ open class JavaLanguageFrontend(language: Language, ctx: T newAnnotationMember( ANNOTATION_MEMBER_VALUE, expressionHandler.handle(value.asLiteralExpr()) as Expression, - getCodeFromRawNode(value) + codeOf(value) ) members.add(member) } diff --git a/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/StatementHandler.kt b/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/StatementHandler.kt index 0ed5969add..2e74aad2f8 100644 --- a/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/StatementHandler.kt +++ b/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/StatementHandler.kt @@ -46,9 +46,6 @@ import de.fraunhofer.aisec.cpg.sarif.PhysicalLocation import de.fraunhofer.aisec.cpg.sarif.Region import java.util.function.Supplier import java.util.stream.Collectors -import kotlin.collections.ArrayList -import kotlin.collections.MutableList -import kotlin.collections.mapNotNull import kotlin.collections.set import org.slf4j.LoggerFactory @@ -63,7 +60,10 @@ class StatementHandler(lang: JavaLanguageFrontend?) : val expression = frontend.expressionHandler.handle(stmt.asExpressionStmt().expression) // update expression's code and location to match the statement - frontend.setCodeAndLocation(expression, stmt) + if (expression != null) { + frontend.setCodeAndLocation(expression, stmt) + } + return expression } @@ -189,10 +189,11 @@ class StatementHandler(lang: JavaLanguageFrontend?) : val initExprList = this.newExpressionList() for (initExpr in forStmt.initialization) { val s = frontend.expressionHandler.handle(initExpr) - - // make sure location is set - frontend.setCodeAndLocation(s, initExpr) - s?.let { initExprList.addExpression(it) } + s?.let { + // make sure location is set + frontend.setCodeAndLocation(it, initExpr) + initExprList.addExpression(it) + } // can not update location if (s?.location == null) { @@ -242,10 +243,11 @@ class StatementHandler(lang: JavaLanguageFrontend?) : val iterationExprList = this.newExpressionList() for (updateExpr in forStmt.update) { val s = frontend.expressionHandler.handle(updateExpr) - - // make sure location is set - frontend.setCodeAndLocation(s, updateExpr) - s?.let { iterationExprList.addExpression(it) } + s?.let { + // make sure location is set + frontend.setCodeAndLocation(s, updateExpr) + iterationExprList.addExpression(it) + } // can not update location if (s?.location == null) { @@ -353,7 +355,7 @@ class StatementHandler(lang: JavaLanguageFrontend?) : caseExpression: Expression?, sEntry: SwitchEntry ): de.fraunhofer.aisec.cpg.graph.statements.Statement { - val parentLocation = frontend.getLocationFromRawNode(sEntry) + val parentLocation = frontend.locationOf(sEntry) val optionalTokenRange = sEntry.tokenRange var caseTokens = Pair(null, null) if (optionalTokenRange.isEmpty) { diff --git a/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/DeclarationHandler.kt b/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/DeclarationHandler.kt index 177c6189fd..e547148e18 100644 --- a/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/DeclarationHandler.kt +++ b/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/DeclarationHandler.kt @@ -58,7 +58,7 @@ class DeclarationHandler(lang: LLVMIRLanguageFrontend) : newProblemDeclaration( "Not handling declaration kind $kind yet.", ProblemNode.ProblemType.TRANSLATION, - frontend.getCodeFromRawNode(value) + frontend.codeOf(value) ) } } @@ -75,7 +75,7 @@ class DeclarationHandler(lang: LLVMIRLanguageFrontend) : val type = frontend.typeOf(valueRef) val variableDeclaration = - newVariableDeclaration(name, type, frontend.getCodeFromRawNode(valueRef), false) + newVariableDeclaration(name, type, frontend.codeOf(valueRef), false) // cache binding frontend.bindingsCache[valueRef.symbolName] = variableDeclaration @@ -98,8 +98,7 @@ class DeclarationHandler(lang: LLVMIRLanguageFrontend) : */ private fun handleFunction(func: LLVMValueRef): FunctionDeclaration { val name = LLVMGetValueName(func) - val functionDeclaration = - newFunctionDeclaration(name.string, frontend.getCodeFromRawNode(func)) + val functionDeclaration = newFunctionDeclaration(name.string, frontend.codeOf(func)) // return types are a bit tricky, because the type of the function is a pointer to the // function type, which then has the return type in it @@ -120,13 +119,7 @@ class DeclarationHandler(lang: LLVMIRLanguageFrontend) : val type = frontend.typeOf(param) // TODO: support variardic - val decl = - newParamVariableDeclaration( - paramName, - type, - false, - frontend.getCodeFromRawNode(param) - ) + val decl = newParamVariableDeclaration(paramName, type, false, frontend.codeOf(param)) frontend.scopeManager.addDeclaration(decl) frontend.bindingsCache[paramSymbolName] = decl @@ -227,17 +220,7 @@ class DeclarationHandler(lang: LLVMIRLanguageFrontend) : // there are no names, so we need to invent some dummy ones for easier reading val fieldName = "field_$i" - val field = - newFieldDeclaration( - fieldName, - fieldType, - listOf(), - "", - null, - null, - false, - frontend.language - ) + val field = newFieldDeclaration(fieldName, fieldType, listOf(), "", null, null, false) frontend.scopeManager.addDeclaration(field) } diff --git a/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/ExpressionHandler.kt b/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/ExpressionHandler.kt index ce70d27af5..c37c551af4 100644 --- a/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/ExpressionHandler.kt +++ b/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/ExpressionHandler.kt @@ -64,11 +64,11 @@ class ExpressionHandler(lang: LLVMIRLanguageFrontend) : newDeclaredReferenceExpression("poison", frontend.typeOf(value), "poison") } LLVMConstantTokenNoneValueKind -> - newLiteral(null, newUnknownType(), frontend.getCodeFromRawNode(value)) + newLiteral(null, newUnknownType(), frontend.codeOf(value)) LLVMUndefValueValueKind -> - initializeAsUndef(frontend.typeOf(value), frontend.getCodeFromRawNode(value)) + initializeAsUndef(frontend.typeOf(value), frontend.codeOf(value)) LLVMConstantAggregateZeroValueKind -> - initializeAsZero(frontend.typeOf(value), frontend.getCodeFromRawNode(value)) + initializeAsZero(frontend.typeOf(value), frontend.codeOf(value)) LLVMArgumentValueKind, LLVMGlobalVariableValueKind, // this is a little tricky. It seems weird, that an instruction value kind turns @@ -80,11 +80,7 @@ class ExpressionHandler(lang: LLVMIRLanguageFrontend) : LLVMFunctionValueKind -> handleFunction(value) LLVMGlobalAliasValueKind -> { val name = frontend.getNameOf(value).first - newDeclaredReferenceExpression( - name, - frontend.typeOf(value), - frontend.getCodeFromRawNode(value) - ) + newDeclaredReferenceExpression(name, frontend.typeOf(value), frontend.codeOf(value)) } LLVMMetadataAsValueValueKind, LLVMInlineAsmValueKind -> { @@ -92,7 +88,7 @@ class ExpressionHandler(lang: LLVMIRLanguageFrontend) : return newProblemExpression( "Metadata or ASM value kind not supported yet", ProblemNode.ProblemType.TRANSLATION, - frontend.getCodeFromRawNode(value) + frontend.codeOf(value) ) } else -> { @@ -125,7 +121,7 @@ class ExpressionHandler(lang: LLVMIRLanguageFrontend) : newProblemExpression( "Unknown expression $kind", ProblemNode.ProblemType.TRANSLATION, - frontend.getCodeFromRawNode(value) + frontend.codeOf(value) ) } } @@ -137,7 +133,7 @@ class ExpressionHandler(lang: LLVMIRLanguageFrontend) : return newDeclaredReferenceExpression( valueRef.name, frontend.typeOf(valueRef), - frontend.getCodeFromRawNode(valueRef) + frontend.codeOf(valueRef) ) } @@ -237,7 +233,7 @@ class ExpressionHandler(lang: LLVMIRLanguageFrontend) : ?: newProblemExpression( "Wrong type of constant binary operation +", ProblemNode.ProblemType.TRANSLATION, - frontend.getCodeFromRawNode(value) + frontend.codeOf(value) ) LLVMSub, LLVMFSub -> @@ -245,7 +241,7 @@ class ExpressionHandler(lang: LLVMIRLanguageFrontend) : ?: newProblemExpression( "Wrong type of constant binary operation -", ProblemNode.ProblemType.TRANSLATION, - frontend.getCodeFromRawNode(value) + frontend.codeOf(value) ) LLVMAShr -> frontend.statementHandler.handleBinaryOperator(value, ">>", false) @@ -253,20 +249,20 @@ class ExpressionHandler(lang: LLVMIRLanguageFrontend) : ?: newProblemExpression( "Wrong type of constant binary operation >>", ProblemNode.ProblemType.TRANSLATION, - frontend.getCodeFromRawNode(value) + frontend.codeOf(value) ) LLVMICmp -> frontend.statementHandler.handleIntegerComparison(value) as? Expression ?: newProblemExpression( "Wrong type of constant comparison", ProblemNode.ProblemType.TRANSLATION, - frontend.getCodeFromRawNode(value) + frontend.codeOf(value) ) else -> { log.error("Not handling constant expression of opcode {} yet", kind) newProblemExpression( "Not handling constant expression of opcode $kind yet", ProblemNode.ProblemType.TRANSLATION, - frontend.getCodeFromRawNode(value) + frontend.codeOf(value) ) } } @@ -284,7 +280,7 @@ class ExpressionHandler(lang: LLVMIRLanguageFrontend) : // retrieve the type val type = frontend.typeOf(value) - val expr: ConstructExpression = newConstructExpression(frontend.getCodeFromRawNode(value)) + val expr: ConstructExpression = newConstructExpression(frontend.codeOf(value)) // map the construct expression to the record declaration of the type expr.instantiates = (type as? ObjectType)?.recordDeclaration @@ -313,14 +309,10 @@ class ExpressionHandler(lang: LLVMIRLanguageFrontend) : if (LLVMIsConstantString(valueRef) == 1) { val string = LLVMGetAsString(valueRef, SizeTPointer(0)).string - return newLiteral( - string, - frontend.typeOf(valueRef), - frontend.getCodeFromRawNode(valueRef) - ) + return newLiteral(string, frontend.typeOf(valueRef), frontend.codeOf(valueRef)) } - val list = newInitializerListExpression(frontend.getCodeFromRawNode(valueRef)) + val list = newInitializerListExpression(frontend.codeOf(valueRef)) val arrayType = LLVMTypeOf(valueRef) val length = if (LLVMIsAConstantDataArray(valueRef) != null) { @@ -400,7 +392,7 @@ class ExpressionHandler(lang: LLVMIRLanguageFrontend) : /** Returns a literal with the type of [value] and value `null`. */ private fun handleNullPointer(value: LLVMValueRef): Expression { val type = frontend.typeOf(value) - return newLiteral(null, type, frontend.getCodeFromRawNode(value)) + return newLiteral(null, type, frontend.codeOf(value)) } /** @@ -444,7 +436,7 @@ class ExpressionHandler(lang: LLVMIRLanguageFrontend) : newProblemExpression( "Default node for getelementptr", ProblemNode.ProblemType.TRANSLATION, - frontend.getCodeFromRawNode(instr) + frontend.codeOf(instr) ) // loop through all operands / indices @@ -563,7 +555,7 @@ class ExpressionHandler(lang: LLVMIRLanguageFrontend) : * [cast instruction](https://llvm.org/docs/LangRef.html#conversion-operations). */ fun handleCastInstruction(instr: LLVMValueRef): Expression { - val castExpr = newCastExpression(frontend.getCodeFromRawNode(instr)) + val castExpr = newCastExpression(frontend.codeOf(instr)) castExpr.castType = frontend.typeOf(instr) castExpr.expression = frontend.getOperandValueAtIndex(instr, 0) return castExpr diff --git a/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/LLVMIRLanguageFrontend.kt b/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/LLVMIRLanguageFrontend.kt index 6d379022c8..ed44dba0e2 100644 --- a/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/LLVMIRLanguageFrontend.kt +++ b/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/LLVMIRLanguageFrontend.kt @@ -29,6 +29,7 @@ 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.TranslationException +import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.declarations.Declaration import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration @@ -47,12 +48,18 @@ import de.fraunhofer.aisec.cpg.sarif.PhysicalLocation import java.io.File import java.nio.ByteBuffer import org.bytedeco.javacpp.BytePointer +import org.bytedeco.javacpp.Pointer import org.bytedeco.llvm.LLVM.* import org.bytedeco.llvm.global.LLVM.* +/** + * Because we are using the C LLVM API, there are two possibly AST nodes that we need to consider: + * [LLVMValueRef] and [LLVMBasicBlockRef]. Because they do not share any class hierarchy, we need to + * resort to use [Pointer] as the AST node type here. + */ @RegisterExtraPass(CompressLLVMPass::class) class LLVMIRLanguageFrontend(language: Language, ctx: TranslationContext) : - LanguageFrontend(language, ctx) { + LanguageFrontend(language, ctx) { val statementHandler = StatementHandler(this) val declarationHandler = DeclarationHandler(this) @@ -155,6 +162,10 @@ class LLVMIRLanguageFrontend(language: Language, ctx: Tr return tu } + override fun typeOf(type: LLVMTypeRef): Type { + return typeFrom(type, mutableMapOf()) + } + /** Returns a pair of the name and symbol name of [valueRef]. */ fun getNameOf(valueRef: LLVMValueRef): Pair { var name = valueRef.name @@ -213,23 +224,23 @@ class LLVMIRLanguageFrontend(language: Language, ctx: Tr return res } - override fun getCodeFromRawNode(astNode: T): String? { + override fun codeOf(astNode: Pointer): String? { if (astNode is LLVMValueRef) { val code = LLVMPrintValueToString(astNode) return code.string } else if (astNode is LLVMBasicBlockRef) { - return this.getCodeFromRawNode(LLVMBasicBlockAsValue(astNode)) + return this.codeOf(LLVMBasicBlockAsValue(astNode)) } return null } - override fun getLocationFromRawNode(astNode: T): PhysicalLocation? { + override fun locationOf(astNode: Pointer): PhysicalLocation? { return null } - override fun setComment(s: S, ctx: T) { + override fun setComment(node: Node, astNode: Pointer) { // There are no comments in LLVM } @@ -251,7 +262,7 @@ class LLVMIRLanguageFrontend(language: Language, ctx: Tr } fun guessSlotNumber(valueRef: LLVMValueRef): String { - val code = getCodeFromRawNode(valueRef) + val code = codeOf(valueRef) return if (code?.contains("=") == true) { code.split("=").firstOrNull()?.trim()?.trim('%') ?: "" } else { diff --git a/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/StatementHandler.kt b/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/StatementHandler.kt index e29edb41e4..2c027c2d8d 100644 --- a/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/StatementHandler.kt +++ b/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/StatementHandler.kt @@ -74,7 +74,7 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : when (opcode) { LLVMRet -> { - val ret = newReturnStatement(frontend.getCodeFromRawNode(instr)) + val ret = newReturnStatement(frontend.codeOf(instr)) val numOps = LLVMGetNumOperands(instr) if (numOps != 0) { @@ -98,14 +98,14 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : } LLVMUnreachable -> { // Does nothing - return newEmptyStatement(frontend.getCodeFromRawNode(instr)) + return newEmptyStatement(frontend.codeOf(instr)) } LLVMCallBr -> { // Maps to a call but also to a goto statement? Barely used => not relevant log.error("Cannot parse callbr instruction yet") } LLVMFNeg -> { - val fneg = newUnaryOperator("-", false, true, frontend.getCodeFromRawNode(instr)) + val fneg = newUnaryOperator("-", false, true, frontend.codeOf(instr)) fneg.input = frontend.getOperandValueAtIndex(instr, 0) return fneg } @@ -133,7 +133,7 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : } LLVMPHI -> { frontend.phiList.add(instr) - return newEmptyStatement(frontend.getCodeFromRawNode(instr)) + return newEmptyStatement(frontend.codeOf(instr)) } LLVMSelect -> { return declarationOrNot(frontend.expressionHandler.handleSelect(instr), instr) @@ -143,7 +143,7 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : log.info( "userop instruction is not a real instruction. Replacing it with empty statement" ) - return newEmptyStatement(frontend.getCodeFromRawNode(instr)) + return newEmptyStatement(frontend.codeOf(instr)) } LLVMVAArg -> { return handleVaArg(instr) @@ -179,7 +179,7 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : "throw", postfix = false, prefix = true, - code = frontend.getCodeFromRawNode(instr) + code = frontend.codeOf(instr) ) } LLVMLandingPad -> { @@ -216,7 +216,7 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : return newProblemExpression( "Not handling instruction opcode $opcode yet", ProblemNode.ProblemType.TRANSLATION, - frontend.getCodeFromRawNode(instr) + frontend.codeOf(instr) ) } @@ -249,7 +249,7 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : gotoStatement.name = name gotoStatement } else { - val emptyStatement = newEmptyStatement(frontend.getCodeFromRawNode(instr)) + val emptyStatement = newEmptyStatement(frontend.codeOf(instr)) emptyStatement.name = name emptyStatement } @@ -267,7 +267,7 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : @FunctionReplacement(["llvm.catchswitch", "llvm.matchesCatchpad"], "catchswitch") private fun handleCatchswitch(instr: LLVMValueRef): Statement { val numOps = LLVMGetNumOperands(instr) - val nodeCode = frontend.getCodeFromRawNode(instr) + val nodeCode = frontend.codeOf(instr) val parent = frontend.getOperandValueAtIndex(instr, 0) @@ -277,7 +277,7 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : newCallExpression( llvmInternalRef("llvm.catchswitch"), "llvm.catchswitch", - frontend.getCodeFromRawNode(instr), + frontend.codeOf(instr), false ) dummyCall.addArgument(parent, "parent") @@ -309,7 +309,7 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : newCallExpression( llvmInternalRef("llvm.matchesCatchpad"), "llvm.matchesCatchpad", - frontend.getCodeFromRawNode(instr), + frontend.codeOf(instr), false ) @@ -365,7 +365,7 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : newCallExpression( llvmInternalRef("llvm.cleanuppad"), "llvm.cleanuppad", - frontend.getCodeFromRawNode(instr), + frontend.codeOf(instr), false ) dummyCall.addArgument(catchswitch, "parentCatchswitch") @@ -392,7 +392,7 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : newCallExpression( llvmInternalRef("llvm.catchpad"), "llvm.catchpad", - frontend.getCodeFromRawNode(instr), + frontend.codeOf(instr), false ) dummyCall.addArgument(catchswitch, "parentCatchswitch") @@ -415,13 +415,13 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : newCallExpression( llvmInternalRef("llvm.va_arg"), "llvm.va_arg", - frontend.getCodeFromRawNode(instr), + frontend.codeOf(instr), false ) val operandName = frontend.getOperandValueAtIndex(instr, 0) callExpr.addArgument(operandName) val expectedType = frontend.typeOf(instr) - val typeLiteral = newLiteral(expectedType, expectedType, frontend.getCodeFromRawNode(instr)) + val typeLiteral = newLiteral(expectedType, expectedType, frontend.codeOf(instr)) callExpr.addArgument(typeLiteral) // TODO: Is this correct?? return declarationOrNot(callExpr, instr) } @@ -477,7 +477,7 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : return newProblemExpression( "Not opcode found for binary operator", ProblemNode.ProblemType.TRANSLATION, - frontend.getCodeFromRawNode(instr) + frontend.codeOf(instr) ) } @@ -487,7 +487,7 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : * [ArrayCreationExpression], which creates a fixed sized array, i.e., a block of memory. */ private fun handleAlloca(instr: LLVMValueRef): Statement { - val array = newArrayCreationExpression(frontend.getCodeFromRawNode(instr)) + val array = newArrayCreationExpression(frontend.codeOf(instr)) array.type = frontend.typeOf(instr) @@ -506,7 +506,7 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : * of a de-referenced pointer in C like `*a = 1`. */ private fun handleStore(instr: LLVMValueRef): Statement { - val binOp = newBinaryOperator("=", frontend.getCodeFromRawNode(instr)) + val binOp = newBinaryOperator("=", frontend.codeOf(instr)) val dereference = newUnaryOperator("*", postfix = false, prefix = true, "") dereference.input = frontend.getOperandValueAtIndex(instr, 1) @@ -637,7 +637,7 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : newProblemExpression( "Default statement for insertvalue", ProblemNode.ProblemType.TRANSLATION, - frontend.getCodeFromRawNode(instr) + frontend.codeOf(instr) ) if (operand !is ConstructExpression) { copy = declarationOrNot(operand, instr) @@ -646,7 +646,7 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : newDeclaredReferenceExpression( copy.singleDeclaration?.name?.localName, (copy.singleDeclaration as? VariableDeclaration)?.type ?: newUnknownType(), - frontend.getCodeFromRawNode(instr) + frontend.codeOf(instr) ) } } @@ -706,9 +706,9 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : } } - val compoundStatement = newCompoundStatement(frontend.getCodeFromRawNode(instr)) + val compoundStatement = newCompoundStatement(frontend.codeOf(instr)) - val assignment = newBinaryOperator("=", frontend.getCodeFromRawNode(instr)) + val assignment = newBinaryOperator("=", frontend.codeOf(instr)) assignment.lhs = base assignment.rhs = valueToSet compoundStatement.addStatement(copy) @@ -728,7 +728,7 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : @FunctionReplacement(["llvm.freeze"], "freeze") private fun handleFreeze(instr: LLVMValueRef): Statement { val operand = frontend.getOperandValueAtIndex(instr, 0) - val instrCode = frontend.getCodeFromRawNode(instr) + val instrCode = frontend.codeOf(instr) // condition: arg != undef && arg != poison val condition = newBinaryOperator("&&", instrCode) @@ -775,11 +775,10 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : */ @FunctionReplacement(["llvm.fence"], "fence") private fun handleFence(instr: LLVMValueRef): Statement { - val instrString = frontend.getCodeFromRawNode(instr) + val instrString = frontend.codeOf(instr) val callExpression = newCallExpression(llvmInternalRef("llvm.fence"), "llvm.fence", instrString, false) - val ordering = - newLiteral(LLVMGetOrdering(instr), parseType("i32"), frontend.getCodeFromRawNode(instr)) + val ordering = newLiteral(LLVMGetOrdering(instr), parseType("i32"), frontend.codeOf(instr)) callExpression.addArgument(ordering, "ordering") if (instrString?.contains("syncscope") == true) { val syncscope = instrString.split("\"")[1] @@ -805,7 +804,7 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : * the if-then statement. */ private fun handleAtomiccmpxchg(instr: LLVMValueRef): Statement { - val instrStr = frontend.getCodeFromRawNode(instr) + val instrStr = frontend.codeOf(instr) val compoundStatement = newCompoundStatement(instrStr) compoundStatement.name = Name("atomiccmpxchg") val ptr = frontend.getOperandValueAtIndex(instr, 0) @@ -867,7 +866,7 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : */ private fun handleAtomicrmw(instr: LLVMValueRef): Statement { val lhs = LLVMGetValueName(instr).string - val instrStr = frontend.getCodeFromRawNode(instr) + val instrStr = frontend.codeOf(instr) val operation = LLVMGetAtomicRMWBinOp(instr) val ptr = frontend.getOperandValueAtIndex(instr, 0) val value = frontend.getOperandValueAtIndex(instr, 1) @@ -958,12 +957,12 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : ">" } val condition = newBinaryOperator(operatorCode, instrStr) - val castExprLhs = newCastExpression(frontend.getCodeFromRawNode(instr)) + val castExprLhs = newCastExpression(frontend.codeOf(instr)) castExprLhs.castType = parseType("u${ty.name}") castExprLhs.expression = ptrDeref condition.lhs = castExprLhs - val castExprRhs = newCastExpression(frontend.getCodeFromRawNode(instr)) + val castExprRhs = newCastExpression(frontend.codeOf(instr)) castExprRhs.castType = parseType("u${ty.name}") castExprRhs.expression = value condition.rhs = castExprRhs @@ -1006,7 +1005,7 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : */ private fun handleIndirectbrStatement(instr: LLVMValueRef): Statement { val numOps = LLVMGetNumOperands(instr) - val nodeCode = frontend.getCodeFromRawNode(instr) + val nodeCode = frontend.codeOf(instr) if (numOps < 2) throw TranslationException( "Indirectbr statement without address and at least one target" @@ -1042,7 +1041,7 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : private fun handleBrStatement(instr: LLVMValueRef): Statement { if (LLVMGetNumOperands(instr) == 3) { // if(op) then {goto label1} else {goto label2} - val ifStatement = newIfStatement(frontend.getCodeFromRawNode(instr)) + val ifStatement = newIfStatement(frontend.codeOf(instr)) val condition = frontend.getOperandValueAtIndex(instr, 0) ifStatement.condition = condition @@ -1071,7 +1070,7 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : */ private fun handleSwitchStatement(instr: LLVMValueRef): Statement { val numOps = LLVMGetNumOperands(instr) - val nodeCode = frontend.getCodeFromRawNode(instr) + val nodeCode = frontend.codeOf(instr) if (numOps < 2 || numOps % 2 != 0) throw TranslationException("Switch statement without operand and default branch") @@ -1113,7 +1112,7 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : * Returns either a [DeclarationStatement] or a [CallExpression]. */ private fun handleFunctionCall(instr: LLVMValueRef): Statement { - val instrStr = frontend.getCodeFromRawNode(instr) + val instrStr = frontend.codeOf(instr) val calledFunc = LLVMGetCalledValue(instr) var calledFuncName: CharSequence = LLVMGetValueName(calledFunc).string var max = LLVMGetNumOperands(instr) - 1 @@ -1146,7 +1145,7 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : newDeclaredReferenceExpression( calledFuncName, frontend.typeOf(calledFunc), - frontend.getCodeFromRawNode(calledFunc) + frontend.codeOf(calledFunc) ) val callExpr = newCallExpression(callee, calledFuncName, instrStr, false) @@ -1176,7 +1175,7 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : newUnknownType(), instrStr, true, - frontend.language + instr ) val catchCompoundStatement = newCompoundStatement(instrStr) @@ -1196,7 +1195,7 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : * [CompressLLVMPass] will move this instruction to the correct location */ private fun handleLandingpad(instr: LLVMValueRef): Statement { - val catchInstr = newCatchClause(frontend.getCodeFromRawNode(instr)) + val catchInstr = newCatchClause(frontend.codeOf(instr)) /* Get the number of clauses on the landingpad instruction and iterate through the clauses to get all types for the catch clauses */ val numClauses = LLVMGetNumClauses(instr) var catchType = "" @@ -1227,9 +1226,9 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : newVariableDeclaration( exceptionName, parseType(catchType), // TODO: This doesn't work for multiple types to catch - frontend.getCodeFromRawNode(instr), + frontend.codeOf(instr), false, - frontend.language + instr ) frontend.bindingsCache["%${exceptionName}"] = except catchInstr.parameter = except @@ -1243,7 +1242,7 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : * modified value is constructed. */ private fun handleInsertelement(instr: LLVMValueRef): Statement { - val instrStr = frontend.getCodeFromRawNode(instr) + val instrStr = frontend.codeOf(instr) val compoundStatement = newCompoundStatement(instrStr) // TODO: Probably we should make a proper copy of the array @@ -1273,7 +1272,7 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : * instruction which is modeled as access to an array at a given index. */ private fun handleExtractelement(instr: LLVMValueRef): Statement { - val arrayExpr = newArraySubscriptionExpression(frontend.getCodeFromRawNode(instr)) + val arrayExpr = newArraySubscriptionExpression(frontend.codeOf(instr)) arrayExpr.arrayExpression = frontend.getOperandValueAtIndex(instr, 0) arrayExpr.subscriptExpression = frontend.getOperandValueAtIndex(instr, 1) @@ -1289,7 +1288,7 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : * barely used and also the features of LLVM are very limited in that scenario. */ private fun handleShufflevector(instr: LLVMValueRef): Statement { - val instrStr = frontend.getCodeFromRawNode(instr) + val instrStr = frontend.codeOf(instr) val list = newInitializerListExpression(instrStr) val elementType = frontend.typeOf(instr).dereference() @@ -1420,8 +1419,8 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : val firstBB = (functions[0] as FunctionDeclaration).body as CompoundStatement val varName = instr.name val type = frontend.typeOf(instr) - val code = frontend.getCodeFromRawNode(instr) - val declaration = newVariableDeclaration(varName, type, code, false, frontend.language) + val code = frontend.codeOf(instr) + val declaration = newVariableDeclaration(varName, type, code, false, instr) declaration.type = type flatAST.add(declaration) @@ -1474,9 +1473,9 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : newVariableDeclaration( lhs, frontend.typeOf(valueRef), - frontend.getCodeFromRawNode(valueRef), + frontend.codeOf(valueRef), false, - frontend.language + valueRef ) decl.initializer = rhs @@ -1503,7 +1502,7 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : var instr = LLVMGetFirstInstruction(bb) while (instr != null) { - log.debug("Parsing {}", frontend.getCodeFromRawNode(instr)) + log.debug("Parsing {}", frontend.codeOf(instr)) val stmt = frontend.statementHandler.handle(instr) if (stmt != null) { @@ -1578,17 +1577,17 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : binaryOperator.input = unorderedCall } else { // Resulting statement: lhs = op1 op2. - binaryOperator = newBinaryOperator(op, frontend.getCodeFromRawNode(instr)) + binaryOperator = newBinaryOperator(op, frontend.codeOf(instr)) if (unsigned) { val op1Type = "u${op1.type.name}" - val castExprLhs = newCastExpression(frontend.getCodeFromRawNode(instr)) + val castExprLhs = newCastExpression(frontend.codeOf(instr)) castExprLhs.castType = parseType(op1Type) castExprLhs.expression = op1 binaryOperator.lhs = castExprLhs val op2Type = "u${op2.type.name}" - val castExprRhs = newCastExpression(frontend.getCodeFromRawNode(instr)) + val castExprRhs = newCastExpression(frontend.codeOf(instr)) castExprRhs.castType = parseType(op2Type) castExprRhs.expression = op2 binaryOperator.rhs = castExprRhs @@ -1601,7 +1600,7 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : // Special case for floating point comparisons which check if a value is "unordered // or ". // Statement is then lhs = isunordered(op1, op2) || (op1 op2) - binOpUnordered = newBinaryOperator("||", frontend.getCodeFromRawNode(instr)) + binOpUnordered = newBinaryOperator("||", frontend.codeOf(instr)) binOpUnordered.rhs = binaryOperator val unorderedCall = newCallExpression( @@ -1635,7 +1634,7 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : * statement has been processed. */ private fun assembleGotoStatement(instr: LLVMValueRef, bbTarget: LLVMValueRef): GotoStatement { - val goto = newGotoStatement(frontend.getCodeFromRawNode(instr)) + val goto = newGotoStatement(frontend.codeOf(instr)) val assigneeTargetLabel = BiConsumer { _: Any, to: Node -> if (to is LabelStatement) { goto.targetLabel = to diff --git a/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonLanguageFrontend.kt b/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonLanguageFrontend.kt index f4db007664..11f1116b10 100644 --- a/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonLanguageFrontend.kt +++ b/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonLanguageFrontend.kt @@ -29,7 +29,10 @@ 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.TranslationException +import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration +import de.fraunhofer.aisec.cpg.graph.newUnknownType +import de.fraunhofer.aisec.cpg.graph.types.Type import de.fraunhofer.aisec.cpg.sarif.PhysicalLocation import java.io.File import java.nio.file.Paths @@ -37,7 +40,7 @@ import jep.JepException import kotlin.io.path.absolutePathString class PythonLanguageFrontend(language: Language, ctx: TranslationContext) : - LanguageFrontend(language, ctx) { + LanguageFrontend(language, ctx) { private val jep = JepSingleton // configure Jep @Throws(TranslationException::class) @@ -45,17 +48,22 @@ class PythonLanguageFrontend(language: Language, ctx: Tr return parseInternal(file.readText(Charsets.UTF_8), file.path) } - override fun getCodeFromRawNode(astNode: T): String? { + override fun typeOf(type: Any): Type { + // will be invoked by native function + return newUnknownType() + } + + override fun codeOf(astNode: Any): String? { // will be invoked by native function return null } - override fun getLocationFromRawNode(astNode: T): PhysicalLocation? { + override fun locationOf(astNode: Any): PhysicalLocation? { // will be invoked by native function return null } - override fun setComment(s: S, ctx: T) { + override fun setComment(node: Node, astNode: Any) { // will be invoked by native function } diff --git a/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/DeclarationHandler.kt b/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/DeclarationHandler.kt index e433844b9c..a0317bd83a 100644 --- a/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/DeclarationHandler.kt +++ b/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/DeclarationHandler.kt @@ -64,8 +64,8 @@ class DeclarationHandler(lang: TypeScriptLanguageFrontend) : name, type, listOf(), - this.frontend.getCodeFromRawNode(node), - this.frontend.getLocationFromRawNode(node), + this.frontend.codeOf(node), + this.frontend.locationOf(node), null, false, ) @@ -86,7 +86,7 @@ class DeclarationHandler(lang: TypeScriptLanguageFrontend) : } else { "class" }, - this.frontend.getCodeFromRawNode(node) + this.frontend.codeOf(node) ) this.frontend.scopeManager.enterScope(record) @@ -113,20 +113,11 @@ class DeclarationHandler(lang: TypeScriptLanguageFrontend) : val type = node.typeChildNode?.let { this.frontend.typeHandler.handle(it) } ?: newUnknownType() - return newParamVariableDeclaration( - name, - type, - false, - this.frontend.getCodeFromRawNode(node) - ) + return newParamVariableDeclaration(name, type, false, this.frontend.codeOf(node)) } fun handleSourceFile(node: TypeScriptNode): TranslationUnitDeclaration { - val tu = - newTranslationUnitDeclaration( - node.location.file, - this.frontend.getCodeFromRawNode(node) - ) + val tu = newTranslationUnitDeclaration(node.location.file, this.frontend.codeOf(node)) this.frontend.scopeManager.resetToGlobal(tu) @@ -155,23 +146,18 @@ class DeclarationHandler(lang: TypeScriptLanguageFrontend) : "MethodDeclaration" -> { val record = this.frontend.scopeManager.currentRecord - newMethodDeclaration( - name, - this.frontend.getCodeFromRawNode(node), - false, - record - ) + newMethodDeclaration(name, this.frontend.codeOf(node), false, record) } "Constructor" -> { val record = this.frontend.scopeManager.currentRecord newConstructorDeclaration( record?.name?.toString() ?: "", - this.frontend.getCodeFromRawNode(node), + this.frontend.codeOf(node), record ) } - else -> newFunctionDeclaration(name, this.frontend.getCodeFromRawNode(node)) + else -> newFunctionDeclaration(name, this.frontend.codeOf(node)) } node.typeChildNode?.let { @@ -215,20 +201,15 @@ class DeclarationHandler(lang: TypeScriptLanguageFrontend) : // TODO: support ObjectBindingPattern (whatever it is). seems to be multiple assignment - val `var` = - newVariableDeclaration( - name, - newUnknownType(), - this.frontend.getCodeFromRawNode(node), - false - ) - `var`.location = this.frontend.getLocationFromRawNode(node) + val declaration = + newVariableDeclaration(name, newUnknownType(), this.frontend.codeOf(node), false) + declaration.location = this.frontend.locationOf(node) // the last node that is not an identifier or an object binding pattern is an initializer node.children ?.lastOrNull { it.type != "Identifier" && it.type != "ObjectBindingPattern" } - ?.let { `var`.initializer = this.frontend.expressionHandler.handle(it) } + ?.let { declaration.initializer = this.frontend.expressionHandler.handle(it) } - return `var` + return declaration } } diff --git a/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/ExpressionHandler.kt b/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/ExpressionHandler.kt index 0837f41d35..224e388e56 100644 --- a/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/ExpressionHandler.kt +++ b/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/ExpressionHandler.kt @@ -65,12 +65,12 @@ class ExpressionHandler(lang: TypeScriptLanguageFrontend) : val key = node.children?.first()?.let { this.handle(it) } val value = node.children?.last()?.let { this.handle(it) } - return newKeyValueExpression(key, value, this.frontend.getCodeFromRawNode(node)) + return newKeyValueExpression(key, value, this.frontend.codeOf(node)) } private fun handleJsxClosingElement(node: TypeScriptNode): Expression { // this basically represents an HTML tag with attributes - val tag = newExpressionList(this.frontend.getCodeFromRawNode(node)) + val tag = newExpressionList(this.frontend.codeOf(node)) // it contains an Identifier node, we map this into the name this.frontend.getIdentifierName(node).let { tag.name = Name("") } @@ -86,7 +86,7 @@ class ExpressionHandler(lang: TypeScriptLanguageFrontend) : private fun handleJsxOpeningElement(node: TypeScriptNode): ExpressionList { // this basically represents an HTML tag with attributes - val tag = newExpressionList(this.frontend.getCodeFromRawNode(node)) + val tag = newExpressionList(this.frontend.codeOf(node)) // it contains an Identifier node, we map this into the name this.frontend.getIdentifierName(node).let { tag.name = Name("<$it>") } @@ -100,7 +100,7 @@ class ExpressionHandler(lang: TypeScriptLanguageFrontend) : } private fun handeJsxElement(node: TypeScriptNode): ExpressionList { - val jsx = newExpressionList(this.frontend.getCodeFromRawNode(node)) + val jsx = newExpressionList(this.frontend.codeOf(node)) jsx.expressions = node.children?.mapNotNull { this.handle(it) } ?: emptyList() @@ -129,7 +129,7 @@ class ExpressionHandler(lang: TypeScriptLanguageFrontend) : // we cannot directly return a function declaration as an expression, so we // wrap it into a lambda expression - val lambda = newLambdaExpression(frontend.getCodeFromRawNode(node)) + val lambda = newLambdaExpression(frontend.codeOf(node)) lambda.function = func return lambda @@ -139,11 +139,11 @@ class ExpressionHandler(lang: TypeScriptLanguageFrontend) : val key = node.children?.first()?.let { this.handle(it) } val value = node.children?.last()?.let { this.handle(it) } - return newKeyValueExpression(key, value, this.frontend.getCodeFromRawNode(node)) + return newKeyValueExpression(key, value, this.frontend.codeOf(node)) } private fun handleObjectLiteralExpression(node: TypeScriptNode): InitializerListExpression { - val ile = newInitializerListExpression(this.frontend.getCodeFromRawNode(node)) + val ile = newInitializerListExpression(this.frontend.codeOf(node)) ile.initializers = node.children?.mapNotNull { this.handle(it) } ?: emptyList() @@ -156,24 +156,20 @@ class ExpressionHandler(lang: TypeScriptLanguageFrontend) : // https://github.com/Fraunhofer-AISEC/cpg/issues/463 val value = this.frontend - .getCodeFromRawNode(node) + .codeOf(node) ?.trim() ?.replace("\"", "") ?.replace("`", "") ?.replace("'", "") ?: "" - return newLiteral(value, parseType("String"), frontend.getCodeFromRawNode(node)) + return newLiteral(value, parseType("String"), frontend.codeOf(node)) } private fun handleIdentifier(node: TypeScriptNode): Expression { - val name = this.frontend.getCodeFromRawNode(node)?.trim() ?: "" + val name = this.frontend.codeOf(node)?.trim() ?: "" - return newDeclaredReferenceExpression( - name, - newUnknownType(), - this.frontend.getCodeFromRawNode(node) - ) + return newDeclaredReferenceExpression(name, newUnknownType(), this.frontend.codeOf(node)) } private fun handlePropertyAccessExpression(node: TypeScriptNode): Expression { @@ -181,15 +177,9 @@ class ExpressionHandler(lang: TypeScriptLanguageFrontend) : node.children?.first()?.let { this.handle(it) } ?: ProblemExpression("problem parsing base") - val name = this.frontend.getCodeFromRawNode(node.children?.last()) ?: "" + val name = node.children?.last()?.let { this.frontend.codeOf(it) } ?: "" - return newMemberExpression( - name, - base, - newUnknownType(), - ".", - this.frontend.getCodeFromRawNode(node) - ) + return newMemberExpression(name, base, newUnknownType(), ".", this.frontend.codeOf(node)) } private fun handleCallExpression(node: TypeScriptNode): Expression { @@ -198,25 +188,22 @@ class ExpressionHandler(lang: TypeScriptLanguageFrontend) : // peek at the children, to check whether it is a call expression or member call expression val propertyAccess = node.firstChild("PropertyAccessExpression") - if (propertyAccess != null) { - val memberExpression = - this.handle(propertyAccess) as? MemberExpression - ?: return ProblemExpression("node is not a member expression") + call = + if (propertyAccess != null) { + val memberExpression = + this.handle(propertyAccess) as? MemberExpression + ?: return ProblemExpression("node is not a member expression") - call = - newMemberCallExpression( - memberExpression, - code = this.frontend.getCodeFromRawNode(node) - ) - } else { - // TODO: fqn - how? - val fqn = this.frontend.getIdentifierName(node) - // regular function call + newMemberCallExpression(memberExpression, code = this.frontend.codeOf(node)) + } else { + // TODO: fqn - how? + val fqn = this.frontend.getIdentifierName(node) + // regular function call - val ref = newDeclaredReferenceExpression(fqn) + val ref = newDeclaredReferenceExpression(fqn) - call = newCallExpression(ref, fqn, this.frontend.getCodeFromRawNode(node), false) - } + newCallExpression(ref, fqn, this.frontend.codeOf(node), false) + } // parse the arguments. the first node is the identifier, so we skip that val remainingNodes = node.children?.drop(1) diff --git a/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/StatementHandler.kt b/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/StatementHandler.kt index 0d49979e57..77ae321271 100644 --- a/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/StatementHandler.kt +++ b/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/StatementHandler.kt @@ -58,7 +58,7 @@ class StatementHandler(lang: TypeScriptLanguageFrontend) : private fun handleFunctionDeclaration(node: TypeScriptNode): Statement { // typescript allows to declare function on a statement level, e.g. within a compound // statement. We can wrap it into a declaration statement - val statement = newDeclarationStatement(this.frontend.getCodeFromRawNode(node)) + val statement = newDeclarationStatement(this.frontend.codeOf(node)) val decl = this.frontend.declarationHandler.handle(node) @@ -72,7 +72,7 @@ class StatementHandler(lang: TypeScriptLanguageFrontend) : } private fun handleReturnStatement(node: TypeScriptNode): ReturnStatement { - val returnStmt = newReturnStatement(this.frontend.getCodeFromRawNode(node)) + val returnStmt = newReturnStatement(this.frontend.codeOf(node)) node.children?.first()?.let { returnStmt.returnValue = this.frontend.expressionHandler.handle(it) @@ -82,7 +82,7 @@ class StatementHandler(lang: TypeScriptLanguageFrontend) : } private fun handleBlock(node: TypeScriptNode): CompoundStatement { - val block = newCompoundStatement(this.frontend.getCodeFromRawNode(node)) + val block = newCompoundStatement(this.frontend.codeOf(node)) node.children?.forEach { this.handle(it)?.let { it1 -> block.addStatement(it1) } } @@ -98,7 +98,7 @@ class StatementHandler(lang: TypeScriptLanguageFrontend) : } private fun handleVariableStatement(node: TypeScriptNode): DeclarationStatement { - val statement = newDeclarationStatement(this.frontend.getCodeFromRawNode(node)) + val statement = newDeclarationStatement(this.frontend.codeOf(node)) // the declarations are contained in a VariableDeclarationList val nodes = node.firstChild("VariableDeclarationList")?.children diff --git a/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/TypeScriptLanguageFrontend.kt b/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/TypeScriptLanguageFrontend.kt index edaf51eb6c..8eb7e3eeda 100644 --- a/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/TypeScriptLanguageFrontend.kt +++ b/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/TypeScriptLanguageFrontend.kt @@ -35,6 +35,7 @@ import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.Annotation import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression +import de.fraunhofer.aisec.cpg.graph.types.Type import de.fraunhofer.aisec.cpg.sarif.PhysicalLocation import de.fraunhofer.aisec.cpg.sarif.Region import java.io.File @@ -58,7 +59,7 @@ import java.nio.file.StandardCopyOption class TypeScriptLanguageFrontend( language: Language, ctx: TranslationContext -) : LanguageFrontend(language, ctx) { +) : LanguageFrontend(language, ctx) { val declarationHandler = DeclarationHandler(this) val statementHandler = StatementHandler(this) @@ -107,6 +108,11 @@ class TypeScriptLanguageFrontend( return translationUnit } + override fun typeOf(type: TypeScriptNode): Type { + // reserved for future use + return newUnknownType() + } + /** * Extracts comments from the file with a regular expression and calls a best effort approach * function that matches them to the closes ast node in the cpg. @@ -146,29 +152,24 @@ class TypeScriptLanguageFrontend( } } - override fun getCodeFromRawNode(astNode: T): String? { - return (astNode as? TypeScriptNode)?.code + override fun codeOf(astNode: TypeScriptNode): String? { + return astNode.code } - override fun getLocationFromRawNode(astNode: T): PhysicalLocation? { - return if (astNode is TypeScriptNode) { - - var position = astNode.location.pos - - // Correcting node positions as we have noticed that the parser computes wrong - // positions, it is apparent when a file starts with a comment - astNode.code?.let { code -> - currentFileContent?.let { position = it.indexOf(code, position) } - } + override fun locationOf(astNode: TypeScriptNode): PhysicalLocation { + var position = astNode.location.pos - // From here on the invariant 'astNode.location.end - position != astNode.code!!.length' - // should hold, only exceptions are mispositioned empty ast elements - val region = - getRegionFromStartEnd(File(astNode.location.file), position, astNode.location.end) - PhysicalLocation(File(astNode.location.file).toURI(), region ?: Region()) - } else { - null + // Correcting node positions as we have noticed that the parser computes wrong + // positions, it is apparent when a file starts with a comment + astNode.code?.let { code -> + currentFileContent?.let { position = it.indexOf(code, position) } } + + // From here on the invariant 'astNode.location.end - position != astNode.code!!.length' + // should hold, only exceptions are mispositioned empty ast elements + val region = + getRegionFromStartEnd(File(astNode.location.file), position, astNode.location.end) + return PhysicalLocation(File(astNode.location.file).toURI(), region ?: Region()) } fun getRegionFromStartEnd(file: File, start: Int, end: Int): Region? { @@ -197,12 +198,12 @@ class TypeScriptLanguageFrontend( return region } - override fun setComment(s: S, ctx: T) { + override fun setComment(node: Node, astNode: TypeScriptNode) { // not implemented } internal fun getIdentifierName(node: TypeScriptNode) = - this.getCodeFromRawNode(node.firstChild("Identifier")) ?: "" + node.firstChild("Identifier")?.let { this.codeOf(it) } ?: "" fun processAnnotations(node: Node, astNode: TypeScriptNode) { // filter for decorators @@ -218,7 +219,7 @@ class TypeScriptLanguageFrontend( return if (callExpr != null) { val call = this.expressionHandler.handle(callExpr) as CallExpression - val annotation = newAnnotation(call.name.localName, this.getCodeFromRawNode(node) ?: "") + val annotation = newAnnotation(call.name.localName, this.codeOf(node) ?: "") annotation.members = call.arguments.map { newAnnotationMember("", it, it.code ?: "") }.toMutableList() @@ -230,7 +231,7 @@ class TypeScriptLanguageFrontend( // or a decorator just has a simple identifier val name = this.getIdentifierName(node) - newAnnotation(name, this.getCodeFromRawNode(node) ?: "") + newAnnotation(name, this.codeOf(node) ?: "") } } } From 650175bbf257a0822d0056acefd4be1058ea9ce7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 27 Jul 2023 12:09:22 +0200 Subject: [PATCH 109/143] Update dependency @types/node to v18 (#1255) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- cpg-language-typescript/src/main/nodejs/package-lock.json | 8 ++++---- cpg-language-typescript/src/main/nodejs/package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cpg-language-typescript/src/main/nodejs/package-lock.json b/cpg-language-typescript/src/main/nodejs/package-lock.json index 3f999ba2d3..5a26994aaf 100644 --- a/cpg-language-typescript/src/main/nodejs/package-lock.json +++ b/cpg-language-typescript/src/main/nodejs/package-lock.json @@ -6,7 +6,7 @@ "": { "license": "Apache-2.0", "dependencies": { - "@types/node": "^16", + "@types/node": "^18.0.0", "typescript": "5.1.3" }, "devDependencies": { @@ -128,9 +128,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "16.18.39", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.39.tgz", - "integrity": "sha512-8q9ZexmdYYyc5/cfujaXb4YOucpQxAV4RMG0himLyDUOEr8Mr79VrqsFI+cQ2M2h89YIuy95lbxuYjxT4Hk4kQ==" + "version": "18.17.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.17.1.tgz", + "integrity": "sha512-xlR1jahfizdplZYRU59JlUx9uzF1ARa8jbhM11ccpCJya8kvos5jwdm2ZAgxSCwOl0fq21svP18EVwPBXMQudw==" }, "node_modules/@types/resolve": { "version": "1.20.2", diff --git a/cpg-language-typescript/src/main/nodejs/package.json b/cpg-language-typescript/src/main/nodejs/package.json index 7b2d8d4113..75842f4954 100644 --- a/cpg-language-typescript/src/main/nodejs/package.json +++ b/cpg-language-typescript/src/main/nodejs/package.json @@ -5,7 +5,7 @@ "start": "node src/parser.js" }, "dependencies": { - "@types/node": "^16", + "@types/node": "^18.0.0", "typescript": "5.1.3" }, "license": "Apache-2.0", From 6f4ca17966b7557e5edc1ab4a23a8e4980eef648 Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Thu, 27 Jul 2023 14:56:40 +0200 Subject: [PATCH 110/143] Type system / manager overhaul (#1199) Removed Type Parser --- .../kotlin/cpg.common-conventions.gradle.kts | 2 +- .../cpg.formatting-conventions.gradle.kts | 35 +- .../aisec/cpg/analysis/ValueEvaluatorTest.kt | 182 ++--- .../aisec/cpg/graph/TypeManager.java | 754 ------------------ .../aisec/cpg/graph/types/TypeParser.java | 712 ----------------- .../aisec/cpg/TranslationContext.kt | 2 - .../aisec/cpg/TranslationManager.kt | 5 +- .../de/fraunhofer/aisec/cpg/TypeManager.kt | 643 +++++++++++++++ .../aisec/cpg/frontends/Language.kt | 16 +- .../aisec/cpg/frontends/LanguageFrontend.kt | 5 +- .../aisec/cpg/graph/DeclarationBuilder.kt | 6 +- .../aisec/cpg/graph/ExpressionBuilder.kt | 25 +- .../fraunhofer/aisec/cpg/graph/NodeBuilder.kt | 29 +- .../fraunhofer/aisec/cpg/graph/TypeBuilder.kt | 114 ++- .../aisec/cpg/graph/builder/Fluent.kt | 24 +- .../declarations/ConstructorDeclaration.kt | 4 +- .../graph/declarations/FunctionDeclaration.kt | 4 +- .../graph/declarations/RecordDeclaration.kt | 7 +- .../graph/declarations/TypedefDeclaration.kt | 5 +- .../graph/declarations/ValueDeclaration.kt | 15 +- .../statements/expressions/BinaryOperator.kt | 2 +- .../statements/expressions/CallExpression.kt | 2 +- .../statements/expressions/CastExpression.kt | 2 +- .../expressions/ConditionalExpression.kt | 2 +- .../expressions/ConstructExpression.kt | 7 +- .../statements/expressions/Expression.kt | 17 +- .../expressions/InitializerListExpression.kt | 2 +- .../aisec/cpg/graph/types/FunctionType.kt | 6 +- .../aisec/cpg/graph/types/HasType.kt | 18 +- .../aisec/cpg/graph/types/ProblemType.kt | 40 + .../aisec/cpg/graph/types/ReferenceType.kt | 4 +- .../fraunhofer/aisec/cpg/graph/types/Type.kt | 9 + .../aisec/cpg/graph/types/UnknownType.kt | 13 +- .../aisec/cpg/graph/types/WrapState.kt | 4 +- .../aisec/cpg/passes/CXXCallResolverHelper.kt | 7 +- .../aisec/cpg/passes/CallResolver.kt | 10 +- .../de/fraunhofer/aisec/cpg/passes/Pass.kt | 5 +- .../aisec/cpg/passes/SymbolResolverPass.kt | 4 +- .../aisec/cpg/passes/VariableUsageResolver.kt | 16 +- .../aisec/cpg/passes/inference/Inference.kt | 2 +- .../de/fraunhofer/aisec/cpg/GraphExamples.kt | 4 +- .../de/fraunhofer/aisec/cpg/graph/TypeTest.kt | 58 ++ .../expressions/AssignExpressionTest.kt | 4 +- .../passes/ControlDependenceGraphPassTest.kt | 1 + .../cpg/passes/scopes/ScopeManagerTest.kt | 5 +- .../aisec/cpg/frontends/TestLanguage.kt | 6 +- cpg-language-cxx/build.gradle.kts | 6 - .../aisec/cpg/frontends/cxx/CLanguage.kt | 59 +- .../aisec/cpg/frontends/cxx/CPPLanguage.kt | 52 +- .../cpg/frontends/cxx/CXXLanguageFrontend.kt | 249 ++++-- .../cpg/frontends/cxx/DeclarationHandler.kt | 56 +- .../cpg/frontends/cxx/DeclaratorHandler.kt | 52 +- .../cpg/frontends/cxx/ExpressionHandler.kt | 84 +- .../cpg/frontends/cxx/StatementHandler.kt | 2 +- .../cpg/passes/FunctionPointerCallResolver.kt | 5 +- .../aisec/cpg/ScopeManagerCXXTest.kt | 1 - .../aisec/cpg/enhancements/types/TypeTests.kt | 243 +----- .../aisec/cpg/frontends/cxx/CXXIncludeTest.kt | 14 +- .../frontends/cxx/CXXLanguageFrontendTest.kt | 499 ++++++------ .../aisec/cpg/frontends/cxx/CXXLiteralTest.kt | 36 +- .../cxx/CXXSymbolConfigurationTest.kt | 2 +- .../aisec/cpg/passes/CallResolverTest.kt | 5 +- cpg-language-cxx/src/test/resources/c/types.c | 5 + .../src/main/golang/basic_types.go | 51 +- .../src/main/golang/declarations.go | 51 +- .../src/main/golang/expressions.go | 59 +- .../golang/frontend/declaration_builder.go | 51 +- .../golang/frontend/expression_builder.go | 51 +- .../src/main/golang/frontend/frontend.go | 51 +- .../src/main/golang/frontend/handler.go | 117 ++- .../main/golang/frontend/statement_builder.go | 51 +- .../src/main/golang/frontend/type_builder.go | 91 +++ cpg-language-go/src/main/golang/helper.go | 51 +- cpg-language-go/src/main/golang/language.go | 51 +- .../src/main/golang/lib/cpg/main.go | 51 +- cpg-language-go/src/main/golang/location.go | 51 +- cpg-language-go/src/main/golang/node.go | 51 +- cpg-language-go/src/main/golang/scope.go | 51 +- cpg-language-go/src/main/golang/statements.go | 51 +- cpg-language-go/src/main/golang/types.go | 103 +-- .../frontends/golang/GoLanguageFrontend.kt | 4 +- .../aisec/cpg/passes/GoExtraPass.kt | 4 +- .../golang/GoLanguageFrontendTest.kt | 37 +- cpg-language-go/src/test/resources/log4j2.xml | 2 +- .../cpg/frontends/java/DeclarationHandler.kt | 41 +- .../cpg/frontends/java/ExpressionHandler.kt | 90 ++- .../aisec/cpg/frontends/java/JavaLanguage.kt | 2 + .../frontends/java/JavaLanguageFrontend.kt | 64 +- .../cpg/frontends/java/StatementHandler.kt | 4 +- .../cpg/passes/JavaCallResolverHelper.kt | 4 +- .../JavaExternalTypeHierarchyResolver.kt | 2 +- .../java/JavaLanguageFrontendTest.kt | 18 +- .../aisec/cpg/graph/types/TypeTests.kt | 102 +-- .../aisec/cpg/passes/CallResolverTest.kt | 4 +- .../cpg/frontends/llvm/DeclarationHandler.kt | 6 +- .../cpg/frontends/llvm/ExpressionHandler.kt | 7 +- .../frontends/llvm/LLVMIRLanguageFrontend.kt | 23 +- .../cpg/frontends/llvm/StatementHandler.kt | 34 +- .../llvm/LLVMIRLanguageFrontendTest.kt | 17 +- .../python/PythonLanguageFrontend.kt | 4 +- .../src/main/python/CPGPython/_expressions.py | 30 +- .../src/main/python/CPGPython/_statements.py | 27 +- .../frontends/python/PythonFrontendTest.kt | 30 +- .../typescript/DeclarationHandler.kt | 12 +- .../frontends/typescript/ExpressionHandler.kt | 10 +- .../cpg/frontends/typescript/TypeHandler.kt | 23 +- .../typescript/TypeScriptLanguageFrontend.kt | 3 +- .../TypescriptLanguageFrontendTest.kt | 21 +- .../src/test/resources/log4j2.xml | 14 + .../src/test/resources/typescript/function.ts | 4 +- 110 files changed, 2610 insertions(+), 3275 deletions(-) delete mode 100644 cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/TypeManager.java delete mode 100644 cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/types/TypeParser.java create mode 100644 cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TypeManager.kt create mode 100644 cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/ProblemType.kt create mode 100644 cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/TypeTest.kt create mode 100644 cpg-language-cxx/src/test/resources/c/types.c create mode 100644 cpg-language-go/src/main/golang/frontend/type_builder.go create mode 100644 cpg-language-typescript/src/test/resources/log4j2.xml diff --git a/buildSrc/src/main/kotlin/cpg.common-conventions.gradle.kts b/buildSrc/src/main/kotlin/cpg.common-conventions.gradle.kts index 37c53ea283..dec3f4cc59 100644 --- a/buildSrc/src/main/kotlin/cpg.common-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/cpg.common-conventions.gradle.kts @@ -104,7 +104,7 @@ kotlin { tasks.withType { kotlinOptions { - freeCompilerArgs = listOf("-opt-in=kotlin.RequiresOptIn") + freeCompilerArgs = listOf("-opt-in=kotlin.RequiresOptIn", "-Xcontext-receivers") } } diff --git a/buildSrc/src/main/kotlin/cpg.formatting-conventions.gradle.kts b/buildSrc/src/main/kotlin/cpg.formatting-conventions.gradle.kts index 813189c6c8..ab526156a5 100644 --- a/buildSrc/src/main/kotlin/cpg.formatting-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/cpg.formatting-conventions.gradle.kts @@ -62,6 +62,34 @@ val headerWithStars = """/* */ """ +val headerWithSlashes = """// +// Copyright (c) ${"$"}YEAR, 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. +// +// ${'$'}${'$'}${'$'}${'$'}${'$'}${'$'}\ ${'$'}${'$'}${'$'}${'$'}${'$'}${'$'}${'$'}\ ${'$'}${'$'}${'$'}${'$'}${'$'}${'$'}\ +// ${'$'}${'$'} __${'$'}${'$'}\ ${'$'}${'$'} __${'$'}${'$'}\ ${'$'}${'$'} __${'$'}${'$'}\ +// ${'$'}${'$'} / \__|${'$'}${'$'} | ${'$'}${'$'} |${'$'}${'$'} / \__| +// ${'$'}${'$'} | ${'$'}${'$'}${'$'}${'$'}${'$'}${'$'}${'$'} |${'$'}${'$'} |${'$'}${'$'}${'$'}${'$'}\ +// ${'$'}${'$'} | ${'$'}${'$'} ____/ ${'$'}${'$'} |\_${'$'}${'$'} | +// ${'$'}${'$'} | ${'$'}${'$'}\ ${'$'}${'$'} | ${'$'}${'$'} | ${'$'}${'$'} | +// \${'$'}${'$'}${'$'}${'$'}${'$'} |${'$'}${'$'} | \${'$'}${'$'}${'$'}${'$'}${'$'} | +// \______/ \__| \______/ +// +// + +""" + val headerWithHashes = """# # Copyright (c) ${"$"}YEAR, Fraunhofer AISEC. All rights reserved. # @@ -101,11 +129,16 @@ spotless { } ) target("src/main/**/*.py") + targetExclude( + fileTree(project.projectDir) { + include("src/main/nodejs/node_modules") + } + ) licenseHeader(headerWithHashes, "from").yearSeparator(" - ") } format("golang") { target("src/main/golang/**/*.go") - licenseHeader(headerWithStars, "package").yearSeparator(" - ") + licenseHeader(headerWithSlashes, "package").yearSeparator(" - ") } } diff --git a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluatorTest.kt b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluatorTest.kt index eea8ccebcd..8f8655c787 100644 --- a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluatorTest.kt +++ b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluatorTest.kt @@ -184,59 +184,59 @@ class ValueEvaluatorTest { fun testHandlePlus() { with(TestHandler(TestLanguageFrontend())) { val binOp = newBinaryOperator("+") - binOp.lhs = newLiteral(3, parseType("int")) - binOp.rhs = newLiteral(2, parseType("int")) + binOp.lhs = newLiteral(3, primitiveType("int")) + binOp.rhs = newLiteral(2, primitiveType("int")) assertEquals(5L, ValueEvaluator().evaluate(binOp)) - binOp.rhs = newLiteral(2.4, parseType("double")) + binOp.rhs = newLiteral(2.4, primitiveType("double")) assertEquals(5.4, ValueEvaluator().evaluate(binOp)) - binOp.lhs = newLiteral(3L, parseType("long")) - binOp.rhs = newLiteral(2, parseType("int")) + binOp.lhs = newLiteral(3L, primitiveType("long")) + binOp.rhs = newLiteral(2, primitiveType("int")) assertEquals(5L, ValueEvaluator().evaluate(binOp)) - binOp.rhs = newLiteral(2.4, parseType("double")) + binOp.rhs = newLiteral(2.4, primitiveType("double")) assertEquals(5.4, ValueEvaluator().evaluate(binOp)) - binOp.lhs = newLiteral((3).toShort(), parseType("short")) - binOp.rhs = newLiteral(2, parseType("int")) + binOp.lhs = newLiteral((3).toShort(), primitiveType("short")) + binOp.rhs = newLiteral(2, primitiveType("int")) assertEquals(5L, ValueEvaluator().evaluate(binOp)) - binOp.rhs = newLiteral(2.4, parseType("double")) + binOp.rhs = newLiteral(2.4, primitiveType("double")) assertEquals(5.4, ValueEvaluator().evaluate(binOp)) - binOp.lhs = newLiteral((3).toByte(), parseType("byte")) - binOp.rhs = newLiteral(2, parseType("int")) + binOp.lhs = newLiteral((3).toByte(), primitiveType("byte")) + binOp.rhs = newLiteral(2, primitiveType("int")) assertEquals(5L, ValueEvaluator().evaluate(binOp)) - binOp.rhs = newLiteral(2.4, parseType("double")) + binOp.rhs = newLiteral(2.4, primitiveType("double")) assertEquals(5.4, ValueEvaluator().evaluate(binOp)) - binOp.lhs = newLiteral(3.0, parseType("double")) - binOp.rhs = newLiteral(2, parseType("int")) + binOp.lhs = newLiteral(3.0, primitiveType("double")) + binOp.rhs = newLiteral(2, primitiveType("int")) assertEquals(5.0, ValueEvaluator().evaluate(binOp)) - binOp.rhs = newLiteral(2.4, parseType("double")) + binOp.rhs = newLiteral(2.4, primitiveType("double")) assertEquals(5.4, ValueEvaluator().evaluate(binOp)) - binOp.lhs = newLiteral(3.0f, parseType("float")) - binOp.rhs = newLiteral(2, parseType("int")) + binOp.lhs = newLiteral(3.0f, primitiveType("float")) + binOp.rhs = newLiteral(2, primitiveType("int")) assertEquals(5.0, ValueEvaluator().evaluate(binOp)) - binOp.rhs = newLiteral(2.4, parseType("double")) + binOp.rhs = newLiteral(2.4, primitiveType("double")) assertEquals(5.4, ValueEvaluator().evaluate(binOp)) - binOp.lhs = newLiteral("Hello", parseType("String")) - binOp.rhs = newLiteral(" world", parseType("String")) + binOp.lhs = newLiteral("Hello", primitiveType("string")) + binOp.rhs = newLiteral(" world", primitiveType("string")) assertEquals("Hello world", ValueEvaluator().evaluate(binOp)) - binOp.rhs = newLiteral(2, parseType("int")) + binOp.rhs = newLiteral(2, primitiveType("int")) assertEquals("Hello2", ValueEvaluator().evaluate(binOp)) } } @@ -245,56 +245,56 @@ class ValueEvaluatorTest { fun testHandleMinus() { with(TestHandler(TestLanguageFrontend())) { val binOp = newBinaryOperator("-") - binOp.lhs = newLiteral(3, parseType("int")) - binOp.rhs = newLiteral(2, parseType("int")) + binOp.lhs = newLiteral(3, primitiveType("int")) + binOp.rhs = newLiteral(2, primitiveType("int")) assertEquals(1L, ValueEvaluator().evaluate(binOp)) - binOp.rhs = newLiteral(2.4, parseType("double")) + binOp.rhs = newLiteral(2.4, primitiveType("double")) assertEquals(3 - 2.4, ValueEvaluator().evaluate(binOp)) - binOp.lhs = newLiteral(3L, parseType("long")) - binOp.rhs = newLiteral(2, parseType("int")) + binOp.lhs = newLiteral(3L, primitiveType("long")) + binOp.rhs = newLiteral(2, primitiveType("int")) assertEquals(1L, ValueEvaluator().evaluate(binOp)) - binOp.rhs = newLiteral(2.4, parseType("double")) + binOp.rhs = newLiteral(2.4, primitiveType("double")) assertEquals(3 - 2.4, ValueEvaluator().evaluate(binOp)) - binOp.lhs = newLiteral((3).toShort(), parseType("short")) - binOp.rhs = newLiteral(2, parseType("int")) + binOp.lhs = newLiteral((3).toShort(), primitiveType("short")) + binOp.rhs = newLiteral(2, primitiveType("int")) assertEquals(1L, ValueEvaluator().evaluate(binOp)) - binOp.rhs = newLiteral(2.4, parseType("double")) + binOp.rhs = newLiteral(2.4, primitiveType("double")) assertEquals(3 - 2.4, ValueEvaluator().evaluate(binOp)) - binOp.lhs = newLiteral((3).toByte(), parseType("byte")) - binOp.rhs = newLiteral(2, parseType("int")) + binOp.lhs = newLiteral((3).toByte(), primitiveType("byte")) + binOp.rhs = newLiteral(2, primitiveType("int")) assertEquals(1L, ValueEvaluator().evaluate(binOp)) - binOp.rhs = newLiteral(2.4, parseType("double")) + binOp.rhs = newLiteral(2.4, primitiveType("double")) assertEquals(3 - 2.4, ValueEvaluator().evaluate(binOp)) - binOp.lhs = newLiteral(3.0, parseType("double")) - binOp.rhs = newLiteral(2, parseType("int")) + binOp.lhs = newLiteral(3.0, primitiveType("double")) + binOp.rhs = newLiteral(2, primitiveType("int")) assertEquals(1.0, ValueEvaluator().evaluate(binOp)) - binOp.rhs = newLiteral(2.4, parseType("double")) + binOp.rhs = newLiteral(2.4, primitiveType("double")) assertEquals(3 - 2.4, ValueEvaluator().evaluate(binOp)) - binOp.lhs = newLiteral(3.0f, parseType("float")) - binOp.rhs = newLiteral(2, parseType("int")) + binOp.lhs = newLiteral(3.0f, primitiveType("float")) + binOp.rhs = newLiteral(2, primitiveType("int")) assertEquals(1.0, ValueEvaluator().evaluate(binOp)) - binOp.rhs = newLiteral(2.4, parseType("double")) + binOp.rhs = newLiteral(2.4, primitiveType("double")) assertEquals(3 - 2.4, ValueEvaluator().evaluate(binOp)) - binOp.lhs = newLiteral("Hello", parseType("String")) - binOp.rhs = newLiteral(" world", parseType("String")) + binOp.lhs = newLiteral("Hello", primitiveType("string")) + binOp.rhs = newLiteral(" world", primitiveType("string")) assertEquals("{-}", ValueEvaluator().evaluate(binOp)) } } @@ -303,56 +303,56 @@ class ValueEvaluatorTest { fun testHandleTimes() { with(TestHandler(TestLanguageFrontend())) { val binOp = newBinaryOperator("*") - binOp.lhs = newLiteral(3, parseType("int")) - binOp.rhs = newLiteral(2, parseType("int")) + binOp.lhs = newLiteral(3, primitiveType("int")) + binOp.rhs = newLiteral(2, primitiveType("int")) assertEquals(6L, ValueEvaluator().evaluate(binOp)) - binOp.rhs = newLiteral(2.4, parseType("double")) + binOp.rhs = newLiteral(2.4, primitiveType("double")) assertEquals(3 * 2.4, ValueEvaluator().evaluate(binOp)) - binOp.lhs = newLiteral(3L, parseType("long")) - binOp.rhs = newLiteral(2, parseType("int")) + binOp.lhs = newLiteral(3L, primitiveType("long")) + binOp.rhs = newLiteral(2, primitiveType("int")) assertEquals(6L, ValueEvaluator().evaluate(binOp)) - binOp.rhs = newLiteral(2.4, parseType("double")) + binOp.rhs = newLiteral(2.4, primitiveType("double")) assertEquals(3 * 2.4, ValueEvaluator().evaluate(binOp)) - binOp.lhs = newLiteral((3).toShort(), parseType("short")) - binOp.rhs = newLiteral(2, parseType("int")) + binOp.lhs = newLiteral((3).toShort(), primitiveType("short")) + binOp.rhs = newLiteral(2, primitiveType("int")) assertEquals(6L, ValueEvaluator().evaluate(binOp)) - binOp.rhs = newLiteral(2.4, parseType("double")) + binOp.rhs = newLiteral(2.4, primitiveType("double")) assertEquals(3 * 2.4, ValueEvaluator().evaluate(binOp)) - binOp.lhs = newLiteral((3).toByte(), parseType("byte")) - binOp.rhs = newLiteral(2, parseType("int")) + binOp.lhs = newLiteral((3).toByte(), primitiveType("byte")) + binOp.rhs = newLiteral(2, primitiveType("int")) assertEquals(6L, ValueEvaluator().evaluate(binOp)) - binOp.rhs = newLiteral(2.4, parseType("double")) + binOp.rhs = newLiteral(2.4, primitiveType("double")) assertEquals(3 * 2.4, ValueEvaluator().evaluate(binOp)) - binOp.lhs = newLiteral(3.0, parseType("double")) - binOp.rhs = newLiteral(2, parseType("int")) + binOp.lhs = newLiteral(3.0, primitiveType("double")) + binOp.rhs = newLiteral(2, primitiveType("int")) assertEquals(6.0, ValueEvaluator().evaluate(binOp)) - binOp.rhs = newLiteral(2.4, parseType("double")) + binOp.rhs = newLiteral(2.4, primitiveType("double")) assertEquals(3 * 2.4, ValueEvaluator().evaluate(binOp)) - binOp.lhs = newLiteral(3.0f, parseType("float")) - binOp.rhs = newLiteral(2, parseType("int")) + binOp.lhs = newLiteral(3.0f, primitiveType("float")) + binOp.rhs = newLiteral(2, primitiveType("int")) assertEquals(6.0, ValueEvaluator().evaluate(binOp)) - binOp.rhs = newLiteral(2.4, parseType("double")) + binOp.rhs = newLiteral(2.4, primitiveType("double")) assertEquals(3 * 2.4, ValueEvaluator().evaluate(binOp)) - binOp.lhs = newLiteral("Hello", parseType("String")) - binOp.rhs = newLiteral(" world", parseType("String")) + binOp.lhs = newLiteral("Hello", primitiveType("string")) + binOp.rhs = newLiteral(" world", primitiveType("string")) assertEquals("{*}", ValueEvaluator().evaluate(binOp)) } } @@ -362,60 +362,60 @@ class ValueEvaluatorTest { with(TestHandler(TestLanguageFrontend())) { // For two integer values, we keep the result as a long. val binOp = newBinaryOperator("/") - binOp.lhs = newLiteral(3, parseType("int")) - binOp.rhs = newLiteral(0, parseType("int")) + binOp.lhs = newLiteral(3, primitiveType("int")) + binOp.rhs = newLiteral(0, primitiveType("int")) assertEquals("{/}", ValueEvaluator().evaluate(binOp)) - binOp.lhs = newLiteral(3, parseType("int")) - binOp.rhs = newLiteral(2, parseType("int")) + binOp.lhs = newLiteral(3, primitiveType("int")) + binOp.rhs = newLiteral(2, primitiveType("int")) assertEquals(1L, ValueEvaluator().evaluate(binOp)) - binOp.rhs = newLiteral(2.4, parseType("double")) + binOp.rhs = newLiteral(2.4, primitiveType("double")) assertEquals(3 / 2.4, ValueEvaluator().evaluate(binOp)) - binOp.lhs = newLiteral(3L, parseType("long")) - binOp.rhs = newLiteral(2, parseType("int")) + binOp.lhs = newLiteral(3L, primitiveType("long")) + binOp.rhs = newLiteral(2, primitiveType("int")) assertEquals(1L, ValueEvaluator().evaluate(binOp)) - binOp.rhs = newLiteral(2.4, parseType("double")) + binOp.rhs = newLiteral(2.4, primitiveType("double")) assertEquals(3 / 2.4, ValueEvaluator().evaluate(binOp)) - binOp.lhs = newLiteral((3).toShort(), parseType("short")) - binOp.rhs = newLiteral(2, parseType("int")) + binOp.lhs = newLiteral((3).toShort(), primitiveType("short")) + binOp.rhs = newLiteral(2, primitiveType("int")) assertEquals(1L, ValueEvaluator().evaluate(binOp)) - binOp.rhs = newLiteral(2.4, parseType("double")) + binOp.rhs = newLiteral(2.4, primitiveType("double")) assertEquals(3 / 2.4, ValueEvaluator().evaluate(binOp)) - binOp.lhs = newLiteral((3).toByte(), parseType("byte")) - binOp.rhs = newLiteral(2, parseType("int")) + binOp.lhs = newLiteral((3).toByte(), primitiveType("byte")) + binOp.rhs = newLiteral(2, primitiveType("int")) assertEquals(1L, ValueEvaluator().evaluate(binOp)) - binOp.rhs = newLiteral(2.4, parseType("double")) + binOp.rhs = newLiteral(2.4, primitiveType("double")) assertEquals(3 / 2.4, ValueEvaluator().evaluate(binOp)) - binOp.lhs = newLiteral(3.0, parseType("double")) - binOp.rhs = newLiteral(2, parseType("int")) + binOp.lhs = newLiteral(3.0, primitiveType("double")) + binOp.rhs = newLiteral(2, primitiveType("int")) assertEquals(1.5, ValueEvaluator().evaluate(binOp)) - binOp.rhs = newLiteral(2.4, parseType("double")) + binOp.rhs = newLiteral(2.4, primitiveType("double")) assertEquals(3 / 2.4, ValueEvaluator().evaluate(binOp)) - binOp.lhs = newLiteral(3.0f, parseType("float")) - binOp.rhs = newLiteral(2, parseType("int")) + binOp.lhs = newLiteral(3.0f, primitiveType("float")) + binOp.rhs = newLiteral(2, primitiveType("int")) assertEquals(1.5, ValueEvaluator().evaluate(binOp)) - binOp.rhs = newLiteral(2.4, parseType("double")) + binOp.rhs = newLiteral(2.4, primitiveType("double")) assertEquals(3 / 2.4, ValueEvaluator().evaluate(binOp)) - binOp.lhs = newLiteral("Hello", parseType("String")) - binOp.rhs = newLiteral(" world", parseType("String")) + binOp.lhs = newLiteral("Hello", primitiveType("string")) + binOp.rhs = newLiteral(" world", primitiveType("string")) assertEquals("{/}", ValueEvaluator().evaluate(binOp)) } } @@ -424,30 +424,30 @@ class ValueEvaluatorTest { fun testHandleUnary() { with(TestHandler(TestLanguageFrontend())) { val neg = newUnaryOperator("-", false, true) - neg.input = newLiteral(3, parseType("int")) + neg.input = newLiteral(3, primitiveType("int")) assertEquals(-3, ValueEvaluator().evaluate(neg)) - neg.input = newLiteral(3.5, parseType("double")) + neg.input = newLiteral(3.5, primitiveType("double")) assertEquals(-3.5, ValueEvaluator().evaluate(neg)) val plusplus = newUnaryOperator("++", true, false) - plusplus.input = newLiteral(3, parseType("int")) + plusplus.input = newLiteral(3, primitiveType("int")) assertEquals(4, ValueEvaluator().evaluate(plusplus)) - plusplus.input = newLiteral(3.5, parseType("double")) + plusplus.input = newLiteral(3.5, primitiveType("double")) assertEquals(4.5, ValueEvaluator().evaluate(plusplus)) - plusplus.input = newLiteral(3.5f, parseType("float")) + plusplus.input = newLiteral(3.5f, primitiveType("float")) assertEquals(4.5f, ValueEvaluator().evaluate(plusplus)) val minusminus = newUnaryOperator("--", true, false) - minusminus.input = newLiteral(3, parseType("int")) + minusminus.input = newLiteral(3, primitiveType("int")) assertEquals(2, ValueEvaluator().evaluate(minusminus)) - minusminus.input = newLiteral(3.5, parseType("double")) + minusminus.input = newLiteral(3.5, primitiveType("double")) assertEquals(2.5, ValueEvaluator().evaluate(minusminus)) - minusminus.input = newLiteral(3.5f, parseType("float")) + minusminus.input = newLiteral(3.5f, primitiveType("float")) assertEquals(2.5f, ValueEvaluator().evaluate(minusminus)) } } diff --git a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/TypeManager.java b/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/TypeManager.java deleted file mode 100644 index f5a92b99eb..0000000000 --- a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/TypeManager.java +++ /dev/null @@ -1,754 +0,0 @@ -/* - * Copyright (c) 2019, 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.graph; - -import static de.fraunhofer.aisec.cpg.graph.DeclarationBuilderKt.newTypedefDeclaration; - -import de.fraunhofer.aisec.cpg.ScopeManager; -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.graph.declarations.*; -import de.fraunhofer.aisec.cpg.graph.scopes.NameScope; -import de.fraunhofer.aisec.cpg.graph.scopes.RecordScope; -import de.fraunhofer.aisec.cpg.graph.scopes.Scope; -import de.fraunhofer.aisec.cpg.graph.scopes.TemplateScope; -import de.fraunhofer.aisec.cpg.graph.types.*; -import de.fraunhofer.aisec.cpg.helpers.Util; -import java.util.*; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.stream.Collectors; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.builder.ToStringBuilder; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class TypeManager { - - private static final Logger log = LoggerFactory.getLogger(TypeManager.class); - - // TODO: document/remove this regexp, merge with other pattern - private static final Pattern funPointerPattern = - Pattern.compile("\\(?\\*(?[^()]+)\\)?\\(.*\\)"); - - private static boolean typeSystemActive = true; - - @NotNull - private final Map> typeCache = - Collections.synchronizedMap(new IdentityHashMap<>()); - - @NotNull - private final Map typeToRecord = - Collections.synchronizedMap(new HashMap<>()); - - /** - * Stores the relationship between parameterized RecordDeclarations (e.g. Classes using Generics) - * to the ParameterizedType to be able to resolve the Type of the fields, since ParameterizedTypes - * are unique to the RecordDeclaration and are not merged. - */ - @NotNull - private final Map> recordToTypeParameters = - Collections.synchronizedMap(new HashMap<>()); - - @NotNull - private final Map> templateToTypeParameters = - Collections.synchronizedMap(new HashMap<>()); - - @NotNull - private final Map> typeState = - Collections.synchronizedMap(new HashMap<>()); // Stores all the unique types ObjectType as - // Key and - // Reference-/PointerTypes - // as Values - - private final Set firstOrderTypes = Collections.synchronizedSet(new HashSet<>()); - private final Set secondOrderTypes = Collections.synchronizedSet(new HashSet<>()); - - /** - * @param recordDeclaration that is instantiated by a template containing parameterizedtypes - * @param name of the ParameterizedType we want to get - * @return ParameterizedType if there is a parameterized type defined in the recordDeclaration - * with matching name, null instead - */ - @Nullable - public ParameterizedType getTypeParameter(RecordDeclaration recordDeclaration, String name) { - if (this.recordToTypeParameters.containsKey(recordDeclaration)) { - for (ParameterizedType parameterizedType : - this.recordToTypeParameters.get(recordDeclaration)) { - if (parameterizedType.getName().toString().equals(name)) { - return parameterizedType; - } - } - } - - return null; - } - - /** - * Adds a List of ParameterizedType to {@link TypeManager#recordToTypeParameters} - * - * @param recordDeclaration will be stored as key for the map - * @param typeParameters List containing all ParameterizedTypes used by the recordDeclaration and - * will be stored as value in the map - */ - public void addTypeParameter( - RecordDeclaration recordDeclaration, List typeParameters) { - this.recordToTypeParameters.put(recordDeclaration, typeParameters); - } - - /** - * Searches {@link TypeManager#templateToTypeParameters} for ParameterizedTypes that were defined - * in a template matching the provided name - * - * @param templateDeclaration that includes the ParameterizedType we are looking for - * @param name name of the ParameterizedType we are looking for - * @return - */ - @Nullable - public ParameterizedType getTypeParameter(TemplateDeclaration templateDeclaration, String name) { - if (this.templateToTypeParameters.containsKey(templateDeclaration)) { - for (ParameterizedType parameterizedType : - this.templateToTypeParameters.get(templateDeclaration)) { - if (parameterizedType.getName().toString().equals(name)) { - return parameterizedType; - } - } - } - return null; - } - - /** - * @param templateDeclaration - * @return List containing all ParameterizedTypes the templateDeclaration defines. If the - * templateDeclaration is not registered, an empty list is returned. - */ - @NotNull - public List getAllParameterizedType(TemplateDeclaration templateDeclaration) { - if (this.templateToTypeParameters.containsKey(templateDeclaration)) { - return this.templateToTypeParameters.get(templateDeclaration); - } - return new ArrayList<>(); - } - - /** - * Searches for ParameterizedType if the scope is a TemplateScope. If not we search the parent - * scope until we reach the top. - * - * @param scope in which we are searching for the defined ParameterizedTypes - * @param name of the ParameterizedType - * @return ParameterizedType that is found within the scope (or any parent scope) and matches the - * provided name. Null if we reach the top of the scope without finding a matching - * ParameterizedType - */ - public ParameterizedType searchTemplateScopeForDefinedParameterizedTypes( - Scope scope, String name) { - if (scope instanceof TemplateScope) { - var node = scope.getAstNode(); - - // We need an additional check here, because of parsing or other errors, the AST node might - // not necessarily be a template declaration. - if (node instanceof TemplateDeclaration templateDeclaration) { - ParameterizedType parameterizedType = getTypeParameter(templateDeclaration, name); - if (parameterizedType != null) { - return parameterizedType; - } - } - } - - return scope.getParent() != null - ? searchTemplateScopeForDefinedParameterizedTypes(scope.getParent(), name) - : null; - } - - /** - * Adds ParameterizedType to the {@link TypeManager#templateToTypeParameters} to be able to - * resolve this type when it is used - * - * @param templateDeclaration key for {@link TypeManager#templateToTypeParameters} - * @param typeParameter ParameterizedType we want to register - */ - public void addTypeParameter( - TemplateDeclaration templateDeclaration, ParameterizedType typeParameter) { - if (this.templateToTypeParameters.containsKey(templateDeclaration)) { - this.templateToTypeParameters.get(templateDeclaration).add(typeParameter); - } else { - List typeParameters = new ArrayList<>(); - typeParameters.add(typeParameter); - this.templateToTypeParameters.put(templateDeclaration, typeParameters); - } - } - - /** - * Check if a ParameterizedType with name typeName is already registered. If so we return the - * already created ParameterizedType. If not, we create and return a new ParameterizedType - * - * @param templateDeclaration in which the ParameterizedType is defined - * @param typeName name of the ParameterizedType - * @return - */ - public ParameterizedType createOrGetTypeParameter( - TemplateDeclaration templateDeclaration, - String typeName, - Language language) { - ParameterizedType parameterizedType = getTypeParameter(templateDeclaration, typeName); - if (parameterizedType == null) { - parameterizedType = new ParameterizedType(typeName, language); - addTypeParameter(templateDeclaration, parameterizedType); - } - return parameterizedType; - } - - @NotNull - public Map> getTypeState() { - return typeState; - } - - @NotNull - public T registerType(T t) { - if (t.isFirstOrderType()) { - this.firstOrderTypes.add(t); - } else { - this.secondOrderTypes.add(t); - registerType(((SecondOrderType) t).getElementType()); - } - return t; - } - - public Set getFirstOrderTypes() { - return firstOrderTypes; - } - - public Set getSecondOrderTypes() { - return secondOrderTypes; - } - - public boolean typeExists(String name) { - return firstOrderTypes.stream() - .anyMatch(type -> type.getRoot().getName().toString().equals(name)); - } - - public TypeManager() {} - - public static boolean isTypeSystemActive() { - return typeSystemActive; - } - - public static void setTypeSystemActive(boolean active) { - typeSystemActive = active; - } - - @NotNull - public Map> getTypeCache() { - return typeCache; - } - - public synchronized void cacheType(HasType node, Type type) { - if (!isUnknown(type)) { - List types = typeCache.computeIfAbsent(node, n -> new ArrayList<>()); - if (!types.contains(type)) { - types.add(type); - } - } - } - - public static boolean isPrimitive(Type type, Language language) { - return language.getPrimitiveTypeNames().contains(type.getTypeName()); - } - - public boolean isUnknown(Type type) { - return type instanceof UnknownType; - } - - /** - * @param generics the list of parameter types - * @return true if the generics contain parameterized Types - */ - public boolean containsParameterizedType(List generics) { - for (Type t : generics) { - if (t instanceof ParameterizedType) { - return true; - } - } - return false; - } - - /** - * @param type oldType that we want to replace - * @param newType newType - * @return true if an objectType with instantiated generics is replaced by the same objectType - * with parameterizedTypes as generics false otherwise - */ - public boolean stopPropagation(Type type, Type newType) { - if (type instanceof ObjectType typeObjectType - && newType instanceof ObjectType newObjectType - && type.getName().equals(newType.getName())) { - return containsParameterizedType(newObjectType.getGenerics()) - && !(containsParameterizedType(typeObjectType.getGenerics())); - } - return false; - } - - private Optional rewrapType( - Type type, - int depth, - PointerType.PointerOrigin[] pointerOrigins, - boolean reference, - ReferenceType referenceType) { - if (depth > 0) { - for (int i = depth - 1; i >= 0; i--) { - type = type.reference(pointerOrigins[i]); - } - } - if (reference) { - referenceType.setElementType(type); - return Optional.of(referenceType); - } - return Optional.of(type); - } - - private Set unwrapTypes(Collection types, WrapState wrapState) { - // TODO Performance: This method is called very often (for each setType()) and does four - // iterations over "types". Reduce number of iterations. - Set original = new HashSet<>(types); - Set unwrappedTypes = new HashSet<>(); - PointerType.PointerOrigin[] pointerOrigins = new PointerType.PointerOrigin[0]; - int depth = 0; - int counter = 0; - boolean reference = false; - ReferenceType referenceType = null; - - Type t1 = types.stream().findAny().orElse(null); - - if (t1 instanceof ReferenceType) { - for (Type t : types) { - referenceType = (ReferenceType) t; - if (!referenceType.isSimilar(t)) { - return Collections.emptySet(); - } - unwrappedTypes.add(((ReferenceType) t).getElementType()); - reference = true; - } - types = unwrappedTypes; - } - - Type t2 = types.stream().findAny().orElse(null); - - if (t2 instanceof PointerType) { - for (Type t : types) { - if (counter == 0) { - depth = t.getReferenceDepth(); - counter++; - } - if (t.getReferenceDepth() != depth) { - return Collections.emptySet(); - } - unwrappedTypes.add(t.getRoot()); - - pointerOrigins = new PointerType.PointerOrigin[depth]; - var containedType = t2; - int i = 0; - pointerOrigins[i] = ((PointerType) containedType).getPointerOrigin(); - while (containedType instanceof PointerType) { - containedType = ((PointerType) containedType).getElementType(); - if (containedType instanceof PointerType) { - pointerOrigins[++i] = ((PointerType) containedType).getPointerOrigin(); - } - } - } - } - - wrapState.depth = depth; - wrapState.setPointerOrigin(pointerOrigins); - wrapState.setReference(reference); - wrapState.referenceType = referenceType; - - if (unwrappedTypes.isEmpty() && !original.isEmpty()) { - return original; - } else { - return unwrappedTypes; - } - } - - /** - * This function is a relict from the old ages. It iterates through a collection of types and - * returns the type they have in *common*. For example, if two types `A` and `B` both derive from - * the interface `C`` then `C` would be returned. Because this contains some legacy code that does - * crazy stuff, we need access to scope information, so we can build a map between type - * information and their record declarations. We want to get rid of that in the future. - * - * @param types the types to compare - * @param ctx a {@link TranslationContext}. - * @return the common type - */ - @NotNull - public Optional getCommonType(@NotNull Collection types, TranslationContext ctx) { - var provider = ctx.getScopeManager(); - - // TODO: Documentation needed. - boolean sameType = - types.stream().map(t -> t.getClass().getCanonicalName()).collect(Collectors.toSet()).size() - == 1; - if (!sameType) { - // No commonType for different Types - return Optional.empty(); - } - WrapState wrapState = new WrapState(); - - types = unwrapTypes(types, wrapState); - - if (types.isEmpty()) { - return Optional.empty(); - } else if (types.size() == 1) { - return rewrapType( - types.iterator().next(), - wrapState.depth, - wrapState.pointerOrigins, - wrapState.isReference(), - wrapState.referenceType); - } - - var scope = provider.getScope(); - - if (scope == null) { - return Optional.empty(); - } - - // We need to find the global scope - var globalScope = provider.getScope().getGlobalScope(); - if (globalScope == null) { - return Optional.empty(); - } - - for (var child : globalScope.getChildren()) { - if (child instanceof RecordScope && child.getAstNode() instanceof RecordDeclaration) { - typeToRecord.put( - ((RecordDeclaration) child.getAstNode()).toType(), - (RecordDeclaration) child.getAstNode()); - } - - // HACKY HACK HACK - if (child instanceof NameScope) { - for (var child2 : child.getChildren()) { - if (child2 instanceof RecordScope && child2.getAstNode() instanceof RecordDeclaration) { - typeToRecord.put( - ((RecordDeclaration) child2.getAstNode()).toType(), - (RecordDeclaration) child2.getAstNode()); - } - } - } - } - - List> allAncestors = - types.stream() - .map(t -> typeToRecord.getOrDefault(t, null)) - .filter(Objects::nonNull) - .map(r -> getAncestors(r, 0)) - .toList(); - - // normalize/reverse depth: roots start at 0, increasing on each level - for (Set ancestors : allAncestors) { - Optional farthest = - ancestors.stream().max(Comparator.comparingInt(Ancestor::getDepth)); - if (farthest.isPresent()) { - int maxDepth = farthest.get().getDepth(); - ancestors.forEach(a -> a.setDepth(maxDepth - a.getDepth())); - } - } - - Set commonAncestors = new HashSet<>(); - for (int i = 0; i < allAncestors.size(); i++) { - if (i == 0) { - commonAncestors.addAll(allAncestors.get(i)); - } else { - Set others = allAncestors.get(i); - Set newCommonAncestors = new HashSet<>(); - // like Collection#retainAll but swaps relevant items out if the other set's matching - // ancestor has a higher depth - for (Ancestor curr : commonAncestors) { - Optional toRetain = - others.stream() - .filter(a -> a.equals(curr)) - .map(a -> curr.getDepth() >= a.getDepth() ? curr : a) - .findFirst(); - toRetain.ifPresent(newCommonAncestors::add); - } - commonAncestors = newCommonAncestors; - } - } - - Optional lca = - commonAncestors.stream().max(Comparator.comparingInt(Ancestor::getDepth)); - Optional commonType = - lca.map( - a -> - TypeParser.createFrom( - a.getRecord().getName().toString(), a.getRecord().getLanguage(), false, ctx)); - - Type finalType; - if (commonType.isPresent()) { - finalType = commonType.get(); - } else { - return commonType; - } - - return rewrapType( - finalType, - wrapState.depth, - wrapState.pointerOrigins, - wrapState.isReference(), - wrapState.referenceType); - } - - private Set getAncestors(RecordDeclaration recordDeclaration, int depth) { - if (recordDeclaration.getSuperTypes().isEmpty()) { - HashSet ret = new HashSet<>(); - ret.add(new Ancestor(recordDeclaration, depth)); - return ret; - } - Set ancestors = - recordDeclaration.getSuperTypes().stream() - .map(s -> typeToRecord.getOrDefault(s, null)) - .filter(Objects::nonNull) - .map(s -> getAncestors(s, depth + 1)) - .flatMap(Collection::stream) - .collect(Collectors.toSet()); - ancestors.add(new Ancestor(recordDeclaration, depth)); - return ancestors; - } - - public boolean isSupertypeOf(Type superType, Type subType, MetadataProvider provider) { - Language language = null; - TranslationContext ctx; - - if (superType instanceof UnknownType && subType instanceof UnknownType) return true; - - if (superType.getReferenceDepth() != subType.getReferenceDepth()) { - return false; - } - - if (provider instanceof LanguageProvider languageProvider) { - language = languageProvider.getLanguage(); - } - - if (provider instanceof ContextProvider contextProvider) { - ctx = contextProvider.getCtx(); - } else { - log.error("Missing context provider"); - return false; - } - - // arrays and pointers match in C/C++ - // TODO: Make this independent from the specific language - if (isCXX(language) && checkArrayAndPointer(superType, subType)) { - return true; - } - - // ObjectTypes can be passed as ReferenceTypes - if (superType instanceof ReferenceType referenceType) { - return isSupertypeOf(referenceType.getElementType(), subType, provider); - } - - // We cannot proceed without a scope provider - if (!(provider instanceof ScopeProvider scopeProvider)) { - return false; - } - - Optional commonType = getCommonType(new HashSet<>(List.of(superType, subType)), ctx); - if (commonType.isPresent()) { - return commonType.get().equals(superType); - } else { - // If array depth matches: check whether these are types from the standard library - try { - Class superCls = Class.forName(superType.getTypeName()); - Class subCls = Class.forName(subType.getTypeName()); - return superCls.isAssignableFrom(subCls); - } catch (ClassNotFoundException | NoClassDefFoundError e) { - // Not in the class path or other linkage exception, can't help here - return false; - } - } - } - - private boolean isCXX(Language language) { - return language != null - && (language.getClass().getSimpleName().equals("CLanguage") - || language.getClass().getSimpleName().equals("CPPLanguage")); - } - - public boolean checkArrayAndPointer(Type first, Type second) { - int firstDepth = first.getReferenceDepth(); - int secondDepth = second.getReferenceDepth(); - if (firstDepth == secondDepth) { - return first.getRoot().getName().equals(second.getRoot().getName()) - && first.isSimilar(second); - } else { - return false; - } - } - - public void cleanup() { - this.typeToRecord.clear(); - } - - private Type getTargetType(Type currTarget, String alias, TranslationContext ctx) { - if (alias.contains("(") && alias.contains("*")) { - // function pointer - return TypeParser.createFrom( - currTarget.getName() + " " + alias, currTarget.getLanguage(), false, ctx); - } else if (alias.endsWith("]")) { - // array type - return currTarget.reference(PointerType.PointerOrigin.ARRAY); - } else if (alias.contains("*")) { - // pointer - int depth = StringUtils.countMatches(alias, '*'); - for (int i = 0; i < depth; i++) { - currTarget = currTarget.reference(PointerType.PointerOrigin.POINTER); - } - return currTarget; - } else { - return currTarget; - } - } - - private Type getAlias( - String alias, - @NotNull Language language, - TranslationContext ctx) { - if (alias.contains("(") && alias.contains("*")) { - // function pointer - Matcher matcher = funPointerPattern.matcher(alias); - if (matcher.find()) { - return TypeParser.createIgnoringAlias(matcher.group("alias"), language, ctx); - } else { - log.error("Could not find alias name in function pointer typedef: {}", alias); - return TypeParser.createIgnoringAlias(alias, language, ctx); - } - } else { - alias = alias.split("\\[")[0]; - alias = alias.replace("*", ""); - return TypeParser.createIgnoringAlias(alias, language, ctx); - } - } - - /** - * Creates a typedef / type alias in the form of a {@link TypedefDeclaration} to the scope manager - * and returns it. - * - * @param frontend the language frontend - * @param rawCode the raw code - * @param target the target type - * @param aliasString the alias / name of the typedef - * @return the typedef declaration - */ - @NotNull - public Declaration createTypeAlias( - @NotNull LanguageFrontend frontend, String rawCode, Type target, String aliasString) { - String cleanedPart = Util.removeRedundantParentheses(aliasString); - Type currTarget = getTargetType(target, cleanedPart, frontend.getCtx()); - Type alias; - alias = getAlias(cleanedPart, frontend.getLanguage(), frontend.getCtx()); - - if (alias instanceof SecondOrderType) { - Type chain = alias.duplicate(); - chain.setRoot(currTarget); - currTarget = chain; - currTarget.refreshNames(); - alias = alias.getRoot(); - } - - TypedefDeclaration typedef = newTypedefDeclaration(frontend, currTarget, alias, rawCode); - - frontend.getScopeManager().addTypedef(typedef); - - return typedef; - } - - public Type resolvePossibleTypedef(Type alias, ScopeManager scopeManager) { - Type finalToCheck = alias.getRoot(); - Optional applicable = - scopeManager.getCurrentTypedefs().stream() - .filter(t -> t.getAlias().getRoot().equals(finalToCheck)) - .findAny() - .map(TypedefDeclaration::getType); - - if (applicable.isEmpty()) { - return alias; - } else { - return TypeParser.reWrapType(alias, applicable.get()); - } - } - - private static class Ancestor { - - private final RecordDeclaration recordDeclaration; - private int depth; - - public Ancestor(RecordDeclaration recordDeclaration, int depth) { - this.recordDeclaration = recordDeclaration; - this.depth = depth; - } - - public RecordDeclaration getRecord() { - return recordDeclaration; - } - - public int getDepth() { - return depth; - } - - public void setDepth(int depth) { - this.depth = depth; - } - - @Override - public int hashCode() { - return Objects.hash(recordDeclaration); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (!(o instanceof Ancestor)) { - return false; - } - Ancestor ancestor = (Ancestor) o; - return Objects.equals(recordDeclaration, ancestor.recordDeclaration); - } - - @Override - public String toString() { - return new ToStringBuilder(this, Node.TO_STRING_STYLE) - .append("record", recordDeclaration.getName()) - .append("depth", depth) - .toString(); - } - } -} diff --git a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/types/TypeParser.java b/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/types/TypeParser.java deleted file mode 100644 index 7a25eea063..0000000000 --- a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/types/TypeParser.java +++ /dev/null @@ -1,712 +0,0 @@ -/* - * Copyright (c) 2019, 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.graph.types; - -import de.fraunhofer.aisec.cpg.ScopeManager; -import de.fraunhofer.aisec.cpg.TranslationContext; -import de.fraunhofer.aisec.cpg.frontends.*; -import de.fraunhofer.aisec.cpg.graph.TypeManager; -import java.util.ArrayList; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Class responsible for parsing the type definition and create the same Type as described by the - * type string, but complying to the CPG TypeSystem - */ -public class TypeParser { - - private static final Logger log = LoggerFactory.getLogger(TypeParser.class); - - // TODO: document/remove this regexp - private static final Pattern functionPtrRegex = - Pattern.compile( - "(?:(?[\\h(]+[a-zA-Z0-9_$.<>:]*\\*\\h*[a-zA-Z0-9_$.<>:]*[\\h)]+)\\h*)(?\\(+[a-zA-Z0-9_$.<>,\\*\\&\\h]*\\))"); - private static final List potentialKeywords = - List.of( - "STATIC", - "EXTERN", - "REGISTER", - "AUTO", - "FINAL", - "CONST", - "RESTRICT", - "VOLATILE", - "ATOMIC"); - - private TypeParser() { - throw new IllegalStateException("Do not instantiate the TypeParser"); - } - - /** - * Returns whether the specifier is part of an elaborated type specifier. This only applies to C++ - * and can be used to declare that a type is a class / struct or union even though the type is not - * visible in the scope. - * - * @param specifier the specifier - * @return true, if it is part of an elaborated type. false, otherwise - */ - public static boolean isElaboratedTypeSpecifier( - String specifier, Language language) { - return language instanceof HasElaboratedTypeSpecifier hasElaboratedTypeSpecifier - && hasElaboratedTypeSpecifier.getElaboratedTypeSpecifier().contains(specifier); - } - - /** - * searching closing bracket - * - * @param openBracket opening bracket char - * @param closeBracket closing bracket char - * @param string substring without the openening bracket - * @return position of the closing bracket - */ - private static int findMatching(char openBracket, char closeBracket, String string) { - int counter = 1; - int i = 0; - - while (counter != 0) { - if (i >= string.length()) { - // dirty hack for now - return string.length(); - } - - char actualChar = string.charAt(i); - if (actualChar == openBracket) { - counter++; - } else if (actualChar == closeBracket) { - counter--; - } - i++; - } - - return i; - } - - /** - * Matches the type blocks and checks if the typeString has the structure of a function pointer - * - * @param type separated type string - * @return the Matcher of the functionPointer or null - */ - @Nullable - private static Matcher getFunctionPtrMatcher(@NotNull List type) { - - StringBuilder typeStringBuilder = new StringBuilder(); - for (String typePart : type) { - typeStringBuilder.append(typePart); - } - - String typeString = typeStringBuilder.toString().trim(); - - Matcher matcher = functionPtrRegex.matcher(typeString); - if (matcher.find()) { - return matcher; - } - return null; - } - - /** - * Right now IncompleteTypes are only defined as void {@link IncompleteType} - * - * @param typeName String with the type - * @return true if the type is void, false otherwise - */ - private static boolean isIncompleteType(String typeName) { - return typeName.trim().equals("void"); - } - - private static boolean isUnknownType( - String typeName, @NotNull Language language) { - return typeName.equals(Type.UNKNOWN_TYPE_STRING) - || (language instanceof HasUnknownType hasUnknownType - && hasUnknownType.getUnknownTypeString().contains(typeName)); - } - - /** - * Removes spaces between a generics Expression, i.e. between "<" and ">" and the preceding Type - * information Required since, afterwards typeString get split by spaces - * - * @param type typeString - * @return typeString without spaces in the generic Expression - */ - @NotNull - private static String fixGenerics( - @NotNull String type, @NotNull Language language) { - if (language instanceof HasGenerics hasGenerics - && type.indexOf(hasGenerics.getStartCharacter()) > -1 - && type.indexOf(hasGenerics.getEndCharacter()) > -1) { - - char startCharacter = hasGenerics.getStartCharacter(); - char endCharacter = hasGenerics.getEndCharacter(); - - // Get the generic string between startCharacter and endCharacter. - String generics = - type.substring(type.indexOf(startCharacter) + 1, type.lastIndexOf(endCharacter)); - if (language instanceof HasElaboratedTypeSpecifier hasElaboratedTypeSpecifier) { - /* We can have elaborate type specifiers (e.g. struct) inside this string. We want to remove it. - * We remove this specifier from the generic string. - * To do so, this regex checks that a specifier is preceded by "<" (or whatever is the startCharacter), "," or a whitespace and is also followed by a whitespace (to avoid removing other strings by mistake). - */ - generics = - generics.replaceAll( - "((^|[\\h," - + hasGenerics.getStartCharacter() - + "])\\h*)((" - + String.join("|", hasElaboratedTypeSpecifier.getElaboratedTypeSpecifier()) - + ")\\h+)", - "$1"); - } - // Add the generic to the original string again but also remove whitespaces in the generic. - type = - type.substring(0, type.indexOf(startCharacter) + 1) - + generics.replaceAll("\\h", "").trim() - + type.substring(type.lastIndexOf(endCharacter)); - // Remove unnecessary whitespace around the start and end characters. - type = type.replaceAll("\\h*(" + startCharacter + "|" + endCharacter + "\\h?)\\h*", "$1"); - } - - return type; - } - - private static void processBlockUntilLastSplit( - @NotNull String type, int lastSplit, int newPosition, @NotNull List typeBlocks) { - String substr = type.substring(lastSplit, newPosition); - if (substr.length() != 0) { - typeBlocks.add(substr); - } - } - - /** - * Separates typeString into the different Parts that make up the type information - * - * @param type string with the entire type definition - * @return list of strings in which every piece of type information is one element of the list - */ - @NotNull - public static List separate( - @NotNull String type, Language language) { - type = type.split("=")[0]; - - // Guarantee that there is no arbitrary number of whitespaces - String[] typeSubpart = type.split(" "); - type = String.join(" ", typeSubpart).trim(); - - List typeBlocks = new ArrayList<>(); - - // Splits TypeString into relevant information blocks - int lastSplit = 0; - - for (int i = 0; i < type.length(); i++) { - char ch = type.charAt(i); - switch (ch) { - case ' ' -> { - // handle space create element - processBlockUntilLastSplit(type, lastSplit, i, typeBlocks); - lastSplit = i + 1; - } - case '(' -> { - // handle ( find matching closing ignore content (not relevant type information) - processBlockUntilLastSplit(type, lastSplit, i, typeBlocks); - int finishPosition = findMatching('(', ')', type.substring(i + 1)); - typeBlocks.add(type.substring(i, i + finishPosition + 1)); - i = finishPosition + i; - lastSplit = i + 1; - } - case '[' -> { - // handle [ find matching closing ignore content (not relevant type information) - int finishPosition = findMatching('[', ']', type.substring(i + 1)); - Pattern onlyNumbers = Pattern.compile("^\\[[0-9]*\\]$"); - // If a language uses '[‘ for its generics, we want to make sure that only numbers (e.g. - // for array sizes) are between the brackets. We assume that a type cannot be a number - // here. - if (!(language instanceof HasGenerics hasGenerics - && hasGenerics.getStartCharacter() == '[') - || onlyNumbers.matcher(type.substring(i, i + finishPosition + 1)).matches()) { - processBlockUntilLastSplit(type, lastSplit, i, typeBlocks); - - typeBlocks.add("[]"); // type.substring(i, i+finishPosition+1) - i = finishPosition + i; - lastSplit = i + 1; - } - } - case '*' -> { - // handle * operator - processBlockUntilLastSplit(type, lastSplit, i, typeBlocks); - typeBlocks.add("*"); - lastSplit = i + 1; - } - case '&' -> { - // handle & operator - processBlockUntilLastSplit(type, lastSplit, i, typeBlocks); - typeBlocks.add("&"); - lastSplit = i + 1; - } - default -> { - // everything else - String substr = type.substring(lastSplit); - if (substr.length() != 0 && i == type.length() - 1) { - typeBlocks.add(substr); - } - } - } - } - - return typeBlocks; - } - - private static List getParameterList( - String parameterList, Language language, TranslationContext ctx) { - if (parameterList.startsWith("(") && parameterList.endsWith(")")) { - parameterList = parameterList.trim().substring(1, parameterList.trim().length() - 1); - } - List parameters = new ArrayList<>(); - String[] parametersSplit = parameterList.split(","); - for (String parameter : parametersSplit) { - // ignore void parameters // TODO: WHY?? - if (parameter.length() > 0 && !parameter.trim().equals("void")) { - parameters.add(createFrom(parameter.trim(), language, false, ctx)); - } - } - - return parameters; - } - - private static List getGenerics( - String typeName, Language language, TranslationContext ctx) { - List genericList = new ArrayList<>(); - if (language instanceof HasGenerics hasGenerics - && typeName.indexOf(hasGenerics.getStartCharacter()) > -1 - && typeName.indexOf(hasGenerics.getEndCharacter()) > -1) { - String generics = - typeName.substring( - typeName.indexOf(hasGenerics.getStartCharacter()) + 1, - typeName.lastIndexOf(hasGenerics.getEndCharacter())); - - String[] parametersSplit = generics.split(","); - for (String parameter : parametersSplit) { - genericList.add(createFrom(parameter.trim(), language, false, ctx)); - } - } - return genericList; - } - - private static Type performBracketContentAction( - Type finalType, String part, Language language) { - if (part.equals("*")) { - return finalType.reference(PointerType.PointerOrigin.POINTER); - } - - if (part.equals("&")) { - return finalType.dereference(); - } - - if (part.startsWith("[") && part.endsWith("]")) { - return finalType.reference(PointerType.PointerOrigin.ARRAY); - } - - if (part.startsWith("(") && part.endsWith(")")) { - return resolveBracketExpression(finalType, List.of(part), language); - } - - return finalType; - } - - /** - * Makes sure to apply Expressions containing brackets that change the binding of operators e.g. - * () can change the binding order of operators - * - * @param finalType Modifications are applied to this type which is the result of the preceding - * type calculations - * @param bracketExpressions List of Strings containing bracket expressions - * @return modified finalType performing the resolution of the bracket expressions - */ - private static Type resolveBracketExpression( - @NotNull Type finalType, - @NotNull List bracketExpressions, - @NotNull Language language) { - for (String bracketExpression : bracketExpressions) { - List splitExpression = - separate(bracketExpression.substring(1, bracketExpression.length() - 1), language); - for (String part : splitExpression) { - finalType = performBracketContentAction(finalType, part, language); - } - } - - return finalType; - } - - /** - * Helper function that removes access modifier from the typeString. - * - * @param type provided typeString - * @return typeString without access modifier - */ - private static String removeAccessModifier( - @NotNull String type, @NotNull Language language) { - return type.replaceAll(String.join("|", language.getAccessModifiers()), "").trim(); - } - - /** - * Checks if the List of separated parts of the typeString contains an element indicating that it - * is a primitive type - * - * @param stringList - * @return - */ - private static boolean isPrimitiveType( - @NotNull List stringList, @NotNull Language language) { - return stringList.stream().anyMatch(s -> language.getPrimitiveTypeNames().contains(s)); - } - - /** - * Joins compound primitive data types such as long long int and consolidates those information - * blocks - * - * @param typeBlocks - * @return separated words of compound types are joined into one string - */ - @NotNull - private static List joinPrimitive( - @NotNull List typeBlocks, @NotNull Language language) { - List joinedTypeBlocks = new ArrayList<>(); - StringBuilder primitiveType = new StringBuilder(); - int index = 0; - - for (String s : typeBlocks) { - if (language.getPrimitiveTypeNames().contains(s)) { - if (primitiveType.length() > 0) { - primitiveType.append(" "); - } else { - index = joinedTypeBlocks.size(); - } - primitiveType.append(s); - } else { - joinedTypeBlocks.add(s); - } - } - - if (!primitiveType.isEmpty()) { - joinedTypeBlocks.add(index, primitiveType.toString()); - } - - return joinedTypeBlocks; - } - - /** - * Reconstructs the type chain when the root node is modified e.g. when swapping with alias - * (typedef) - * - * @param oldChain containing all types until the root - * @param newRoot root the chain is swapped with - * @return oldchain but root replaced with newRoot - */ - @NotNull - public static Type reWrapType(@NotNull Type oldChain, @NotNull Type newRoot) { - if (oldChain.isFirstOrderType()) { - newRoot.setTypeOrigin(oldChain.getTypeOrigin()); - } - - if (!newRoot.isFirstOrderType()) { - return newRoot; - } - - if (oldChain instanceof ObjectType && newRoot instanceof ObjectType) { - ((ObjectType) newRoot.getRoot()).setGenerics(((ObjectType) oldChain).getGenerics()); - return newRoot; - } else if (oldChain instanceof ReferenceType referenceType) { - Type reference = reWrapType(referenceType.getElementType(), newRoot); - ReferenceType newChain = (ReferenceType) oldChain.duplicate(); - newChain.setElementType(reference); - newChain.refreshName(); - return newChain; - } else if (oldChain instanceof PointerType) { - PointerType newChain = (PointerType) oldChain.duplicate(); - newChain.setRoot(reWrapType(oldChain.getRoot(), newRoot)); - newChain.refreshNames(); - return newChain; - } else { - return newRoot; - } - } - - /** - * Does the same as createFrom but explicitly does not use type alias resolution. This is usually - * not what you want. Use with care! - * - * @param string the string representation of the type - * @return the type - */ - @NotNull - public static Type createIgnoringAlias( - @NotNull String string, - @NotNull Language language, - TranslationContext ctx) { - return createFrom(string, language, false, ctx); - } - - @NotNull - private static Type postTypeParsing( - @NotNull List subPart, - @NotNull Type finalType, - @NotNull List bracketExpressions) { - for (String part : subPart) { - if (part.equals("*")) { - // Creates a Pointer to the finalType - finalType = finalType.reference(PointerType.PointerOrigin.POINTER); - } - - if (part.equals("&")) { - // CPP ReferenceTypes are indicated by an & at the end of the typeName e.g. int&, and are - // handled differently to a pointer - finalType = new ReferenceType(finalType); - } - - if (part.startsWith("[") && part.endsWith("]")) { - // Arrays are equal to pointer, create a reference - finalType = finalType.reference(PointerType.PointerOrigin.ARRAY); - } - - if (part.startsWith("(") && part.endsWith(")")) { - // BracketExpressions change the binding of operators they are stored in order to be - // processed afterwards - bracketExpressions.add(part); - } - } - return finalType; - } - - private static String removeGenerics( - String typeName, @NotNull Language language) { - if (language instanceof HasGenerics hasGenerics - && typeName.contains(hasGenerics.getStartCharacter() + "") - && typeName.contains(hasGenerics.getEndCharacter() + "")) { - typeName = typeName.substring(0, typeName.indexOf(hasGenerics.getStartCharacter())); - } - return typeName; - } - - private static String determineModifier(List typeBlocks, boolean primitiveType) { - // Default is signed, unless unsigned keyword is specified. For other classes that are not - // primitive this is NOT_APPLICABLE - String modifier = ""; - if (primitiveType) { - if (typeBlocks.contains("unsigned")) { - modifier = "unsigned "; - typeBlocks.remove("unsigned"); - } else if (typeBlocks.contains("signed")) { - modifier = "signed "; - typeBlocks.remove("signed"); - } - } - return modifier; - } - - private static boolean checkValidTypeString(String type) { - // Todo ? can be part of generic string -> more fine-grained analysis necessary - return !type.contains("?") - && !type.contains("org.eclipse.cdt.internal.core.dom.parser.ProblemType@") - && type.trim().length() != 0; - } - - /** - * Warning: This function might crash, when a type cannot be parsed. Use createFrom instead Use - * this function for parsing new types and obtaining a new Type the TypeParser creates from the - * typeString - * - * @param type string with type information - * @param resolveAlias should replace with original type in typedefs - * @return new type representing the type string - */ - @NotNull - private static Type createFromUnsafe( - @NotNull String type, - boolean resolveAlias, - @NotNull Language language, - TranslationContext ctx) { - var typeManager = ctx.getTypeManager(); - var scopeManager = ctx.getScopeManager(); - - // Check if Problems during Parsing - if (!checkValidTypeString(type)) { - return UnknownType.getUnknownType(language); - } - - // Preprocessing of the typeString - type = removeAccessModifier(type, language); - - // Determine if inner class - - type = fixGenerics(type, language); - - // Separate typeString into a List containing each part of the typeString - List typeBlocks = separate(type, language); - - // Depending on if the Type is primitive or not signed/unsigned must be set differently (only - // relevant for ObjectTypes) - boolean primitiveType = isPrimitiveType(typeBlocks, language); - - // Default is signed, unless unsigned keyword is specified. For other classes that are not - // primitive this is NOT_APPLICABLE - String modifier = determineModifier(typeBlocks, primitiveType); - - // Join compound primitive types into one block i.e. types consisting of more than one word e.g. - // long long int (only primitive types) - typeBlocks = joinPrimitive(typeBlocks, language); - - // Handle preceding qualifier or storage specifier to the type name e.g. static const int - int counter = 0; - - for (String part : typeBlocks) { - if (potentialKeywords.contains(part.toUpperCase()) - || isElaboratedTypeSpecifier(part, language)) { - // We only want to get rid of these parts for the remaining method. - counter++; - } else { - break; - } - } - - // Once all preceding known keywords (if any) are handled the next word must be the TypeName - if (counter >= typeBlocks.size()) { - // Note that "const auto ..." will end here with typeName="const" as auto is not supported. - return UnknownType.getUnknownType(language); - } - String typeName = typeBlocks.get(counter); - counter++; - - Type finalType; - - // Check if type is FunctionPointer - Matcher funcptr = getFunctionPtrMatcher(typeBlocks.subList(counter, typeBlocks.size())); - - finalType = language.getSimpleTypeOf(modifier + typeName); - if (finalType != null) { - // Nothing to do here - } else if (funcptr != null) { - Type returnType = createFrom(typeName, language, false, ctx); - List parameterList = getParameterList(funcptr.group("args"), language, ctx); - - return typeManager.registerType(new FunctionPointerType(parameterList, language, returnType)); - } else if (isIncompleteType(typeName)) { - // IncompleteType e.g. void - finalType = new IncompleteType(); - } else if (isUnknownType(typeName, language)) { - // UnknownType -> no information on how to process this type - finalType = new UnknownType(Type.UNKNOWN_TYPE_STRING); - } else { - // ObjectType - // Obtain possible generic List from TypeString - List generics = getGenerics(typeName, language, ctx); - typeName = removeGenerics(typeName, language); - finalType = new ObjectType(typeName, generics, primitiveType, language); - } - - // Process Keywords / Operators (*, &) after typeName - List subPart = typeBlocks.subList(counter, typeBlocks.size()); - - List bracketExpressions = new ArrayList<>(); - - finalType = postTypeParsing(subPart, finalType, bracketExpressions); - - // Resolve BracketExpressions that were identified previously - finalType = resolveBracketExpression(finalType, bracketExpressions, language); - - // Make sure, that only one real instance exists for a type in order to have just one node in - // the graph representing the type - finalType = typeManager.registerType(finalType); - - if (resolveAlias) { - return typeManager.registerType(typeManager.resolvePossibleTypedef(finalType, scopeManager)); - } - - return finalType; - } - - /** - * A specialized version of the type parsing function that needs a language frontend and does - * magic with generics and typedefs. This is legacy code and currently only used for CXX frontend - * and should be removed at some point. - */ - public static Type createFrom( - @NotNull String type, boolean resolveAlias, LanguageFrontend frontend) { - Type templateType = - searchForTemplateTypes(type, frontend.getScopeManager(), frontend.getTypeManager()); - if (templateType != null) { - return templateType; - } - - Type createdType = createFrom(type, frontend.getLanguage(), resolveAlias, frontend.getCtx()); - - if (createdType instanceof SecondOrderType) { - templateType = - searchForTemplateTypes( - createdType.getRoot().getName().toString(), - frontend.getScopeManager(), - frontend.getTypeManager()); - if (templateType != null) { - createdType.setRoot(templateType); - } - } - - return createdType; - } - - private static Type searchForTemplateTypes( - @NotNull String type, ScopeManager scopeManager, TypeManager typeManager) { - return typeManager.searchTemplateScopeForDefinedParameterizedTypes( - scopeManager.getCurrentScope(), type); - } - - /** - * Use this function for parsing new types and obtaining a new Type the TypeParser creates from - * the typeString. - * - * @param type string with type information - * @param language the language in which the type exists. - * @param resolveAlias should replace with original type in typedefs - * @param ctx the translation context - * @return new type representing the type string. If an exception occurs during the parsing, - * UnknownType is returned - */ - @NotNull - public static Type createFrom( - @NotNull String type, - Language language, - boolean resolveAlias, - TranslationContext ctx) { - try { - return createFromUnsafe(type, resolveAlias, language, ctx); - } catch (Exception e) { - log.error("Could not parse the type correctly", e); - return UnknownType.getUnknownType(language); - } - } -} diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationContext.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationContext.kt index 0cdfc1333c..c309225e62 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationContext.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationContext.kt @@ -25,8 +25,6 @@ */ package de.fraunhofer.aisec.cpg -import de.fraunhofer.aisec.cpg.graph.TypeManager - /** * The translation context holds all necessary managers and configurations needed during the * translation process. diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationManager.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationManager.kt index da2eccc9cb..9e92b11164 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationManager.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationManager.kt @@ -31,7 +31,6 @@ import de.fraunhofer.aisec.cpg.frontends.SupportsParallelParsing import de.fraunhofer.aisec.cpg.frontends.TranslationException import de.fraunhofer.aisec.cpg.graph.Component import de.fraunhofer.aisec.cpg.graph.Name -import de.fraunhofer.aisec.cpg.graph.TypeManager import de.fraunhofer.aisec.cpg.helpers.Benchmark import de.fraunhofer.aisec.cpg.helpers.Util import de.fraunhofer.aisec.cpg.passes.* @@ -220,7 +219,7 @@ private constructor( sourceLocations = list } - TypeManager.setTypeSystemActive(ctx.config.typeSystemActiveInFrontend) + TypeManager.isTypeSystemActive = ctx.config.typeSystemActiveInFrontend usedFrontends.addAll( if (useParallelFrontends) { @@ -231,7 +230,7 @@ private constructor( ) if (!config.typeSystemActiveInFrontend) { - TypeManager.setTypeSystemActive(true) + TypeManager.isTypeSystemActive = true result.components.forEach { s -> s.translationUnits.forEach { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TypeManager.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TypeManager.kt new file mode 100644 index 0000000000..ffed50cca9 --- /dev/null +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TypeManager.kt @@ -0,0 +1,643 @@ +/* + * Copyright (c) 2019, 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 + +import de.fraunhofer.aisec.cpg.frontends.Language +import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend +import de.fraunhofer.aisec.cpg.graph.* +import de.fraunhofer.aisec.cpg.graph.declarations.Declaration +import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration +import de.fraunhofer.aisec.cpg.graph.declarations.TemplateDeclaration +import de.fraunhofer.aisec.cpg.graph.declarations.TypedefDeclaration +import de.fraunhofer.aisec.cpg.graph.scopes.NameScope +import de.fraunhofer.aisec.cpg.graph.scopes.RecordScope +import de.fraunhofer.aisec.cpg.graph.scopes.Scope +import de.fraunhofer.aisec.cpg.graph.scopes.TemplateScope +import de.fraunhofer.aisec.cpg.graph.types.* +import de.fraunhofer.aisec.cpg.graph.types.PointerType.PointerOrigin +import java.util.* +import java.util.function.Consumer +import java.util.stream.Collectors +import org.apache.commons.lang3.builder.ToStringBuilder +import org.slf4j.LoggerFactory + +class TypeManager { + val typeCache: MutableMap> = + Collections.synchronizedMap(IdentityHashMap()) + + private val typeToRecord = Collections.synchronizedMap(HashMap()) + + /** + * Stores the relationship between parameterized RecordDeclarations (e.g. Classes using + * Generics) to the ParameterizedType to be able to resolve the Type of the fields, since + * ParameterizedTypes are unique to the RecordDeclaration and are not merged. + */ + private val recordToTypeParameters = + Collections.synchronizedMap(mutableMapOf>()) + private val templateToTypeParameters = + Collections.synchronizedMap( + mutableMapOf>() + ) + + val firstOrderTypes = Collections.synchronizedSet(HashSet()) + val secondOrderTypes = Collections.synchronizedSet(HashSet()) + + /** + * @param recordDeclaration that is instantiated by a template containing parameterizedtypes + * @param name of the ParameterizedType we want to get + * @return ParameterizedType if there is a parameterized type defined in the recordDeclaration + * with matching name, null instead + */ + fun getTypeParameter(recordDeclaration: RecordDeclaration?, name: String): ParameterizedType? { + if (recordToTypeParameters.containsKey(recordDeclaration)) { + for (parameterizedType in recordToTypeParameters[recordDeclaration] ?: listOf()) { + if (parameterizedType.name.toString() == name) { + return parameterizedType + } + } + } + return null + } + + /** + * Adds a List of ParameterizedType to [TypeManager.recordToTypeParameters] + * + * @param recordDeclaration will be stored as key for the map + * @param typeParameters List containing all ParameterizedTypes used by the recordDeclaration + * and will be stored as value in the map + */ + fun addTypeParameter( + recordDeclaration: RecordDeclaration, + typeParameters: List + ) { + recordToTypeParameters[recordDeclaration] = typeParameters + } + + /** + * Searches [TypeManager.templateToTypeParameters] for ParameterizedTypes that were defined in a + * template matching the provided name + * + * @param templateDeclaration that includes the ParameterizedType we are looking for + * @param name name of the ParameterizedType we are looking for + * @return + */ + fun getTypeParameter( + templateDeclaration: TemplateDeclaration, + name: String + ): ParameterizedType? { + if (templateToTypeParameters.containsKey(templateDeclaration)) { + for (parameterizedType in templateToTypeParameters[templateDeclaration] ?: listOf()) { + if (parameterizedType.name.toString() == name) { + return parameterizedType + } + } + } + return null + } + + /** + * @param templateDeclaration + * @return List containing all ParameterizedTypes the templateDeclaration defines. If the + * templateDeclaration is not registered, an empty list is returned. + */ + fun getAllParameterizedType(templateDeclaration: TemplateDeclaration): List { + return if (templateToTypeParameters.containsKey(templateDeclaration)) { + templateToTypeParameters[templateDeclaration] ?: listOf() + } else ArrayList() + } + + /** + * Searches for ParameterizedType if the scope is a TemplateScope. If not we search the parent + * scope until we reach the top. + * + * @param scope in which we are searching for the defined ParameterizedTypes + * @param name of the ParameterizedType + * @return ParameterizedType that is found within the scope (or any parent scope) and matches + * the provided name. Null if we reach the top of the scope without finding a matching + * ParameterizedType + */ + fun searchTemplateScopeForDefinedParameterizedTypes( + scope: Scope?, + name: String + ): ParameterizedType? { + if (scope is TemplateScope) { + val node = scope.astNode + + // We need an additional check here, because of parsing or other errors, the AST node + // might + // not necessarily be a template declaration. + if (node is TemplateDeclaration) { + val parameterizedType = getTypeParameter(node, name) + if (parameterizedType != null) { + return parameterizedType + } + } + } + return if (scope!!.parent != null) + searchTemplateScopeForDefinedParameterizedTypes(scope.parent, name) + else null + } + + /** + * Adds ParameterizedType to the [TypeManager.templateToTypeParameters] to be able to resolve + * this type when it is used + * + * @param templateDeclaration key for [TypeManager.templateToTypeParameters] + * @param typeParameter ParameterizedType we want to register + */ + fun addTypeParameter( + templateDeclaration: TemplateDeclaration, + typeParameter: ParameterizedType + ) { + val parameters = + templateToTypeParameters.computeIfAbsent(templateDeclaration) { mutableListOf() } + + parameters += typeParameter + } + + /** + * Check if a ParameterizedType with name typeName is already registered. If so we return the + * already created ParameterizedType. If not, we create and return a new ParameterizedType + * + * @param templateDeclaration in which the ParameterizedType is defined + * @param typeName name of the ParameterizedType + * @return + */ + fun createOrGetTypeParameter( + templateDeclaration: TemplateDeclaration, + typeName: String, + language: Language<*>? + ): ParameterizedType { + var parameterizedType = getTypeParameter(templateDeclaration, typeName) + if (parameterizedType == null) { + parameterizedType = ParameterizedType(typeName, language) + addTypeParameter(templateDeclaration, parameterizedType) + } + return parameterizedType + } + + fun registerType(t: T): T { + if (t!!.isFirstOrderType) { + firstOrderTypes.add(t) + } else { + secondOrderTypes.add(t) + registerType((t as SecondOrderType).elementType) + } + return t + } + + fun typeExists(name: String): Boolean { + return firstOrderTypes.stream().anyMatch { type: Type -> type.root.name.toString() == name } + } + + @Synchronized + fun cacheType(node: HasType, type: Type) { + if (!isUnknown(type)) { + val types = typeCache.computeIfAbsent(node) { mutableListOf() } + if (!types.contains(type)) { + types.add(type) + } + } + } + + fun isUnknown(type: Type?): Boolean { + return type is UnknownType + } + + /** + * @param generics the list of parameter types + * @return true if the generics contain parameterized Types + */ + fun containsParameterizedType(generics: List): Boolean { + for (t in generics) { + if (t is ParameterizedType) { + return true + } + } + return false + } + + /** + * @param type oldType that we want to replace + * @param newType newType + * @return true if an objectType with instantiated generics is replaced by the same objectType + * with parameterizedTypes as generics false otherwise + */ + fun stopPropagation(type: Type, newType: Type): Boolean { + return if (type is ObjectType && newType is ObjectType && type.name == newType.name) { + (containsParameterizedType(newType.generics) && + !containsParameterizedType(type.generics)) + } else false + } + + private fun rewrapType( + type: Type, + depth: Int, + pointerOrigins: Array, + reference: Boolean, + referenceType: ReferenceType? + ): Optional { + var type = type + if (depth > 0) { + for (i in depth - 1 downTo 0) { + type = type.reference(pointerOrigins[i]) + } + } + if (reference) { + referenceType!!.elementType = type + return Optional.of(referenceType) + } + return Optional.of(type) + } + + private fun unwrapTypes(types: Collection, wrapState: WrapState): Set { + // TODO Performance: This method is called very often (for each setType()) and does four + // iterations over "types". Reduce number of iterations. + var types = types + val original: Set = HashSet(types) + val unwrappedTypes = mutableSetOf() + var pointerOrigins = arrayOfNulls(0) + var depth = 0 + var counter = 0 + var reference = false + var referenceType: ReferenceType? = null + val t1 = types.stream().findAny().orElse(null) + if (t1 is ReferenceType) { + for (t in types) { + referenceType = t as ReferenceType? + if (!referenceType!!.isSimilar(t)) { + return emptySet() + } + unwrappedTypes.add(t.elementType) + reference = true + } + types = unwrappedTypes + } + val t2 = types.stream().findAny().orElse(null) + if (t2 is PointerType) { + for (t in types) { + if (counter == 0) { + depth = t.referenceDepth + counter++ + } + if (t.referenceDepth != depth) { + return emptySet() + } + unwrappedTypes.add(t.root) + pointerOrigins = arrayOfNulls(depth) + var containedType: Type = t2 + var i = 0 + pointerOrigins[i] = (containedType as PointerType).pointerOrigin + while (containedType is PointerType) { + containedType = containedType.elementType + if (containedType is PointerType) { + pointerOrigins[++i] = containedType.pointerOrigin + } + } + } + } + wrapState.depth = depth + wrapState.setPointerOrigin(pointerOrigins) + wrapState.isReference = reference + wrapState.referenceType = referenceType + return if (unwrappedTypes.isEmpty() && original.isNotEmpty()) { + original + } else { + unwrappedTypes + } + } + + /** + * This function is a relict from the old ages. It iterates through a collection of types and + * returns the type they have in *common*. For example, if two types `A` and `B` both derive + * from the interface `C`` then `C` would be returned. Because this contains some legacy code + * that does crazy stuff, we need access to scope information, so we can build a map between + * type information and their record declarations. We want to get rid of that in the future. + * + * @param types the types to compare + * @param ctx a [TranslationContext]. + * @return the common type + */ + fun getCommonType(types: Collection, ctx: TranslationContext?): Optional { + var types = types + val provider = ctx!!.scopeManager + + // TODO: Documentation needed. + val sameType = + (types + .stream() + .map { t: Type -> t.javaClass.canonicalName } + .collect(Collectors.toSet()) + .size == 1) + if (!sameType) { + // No commonType for different Types + return Optional.empty() + } + val wrapState = WrapState() + types = unwrapTypes(types, wrapState) + if (types.isEmpty()) { + return Optional.empty() + } else if (types.size == 1) { + return rewrapType( + types.iterator().next(), + wrapState.depth, + wrapState.pointerOrigins, + wrapState.isReference, + wrapState.referenceType + ) + } + val scope = provider.scope ?: return Optional.empty() + + // We need to find the global scope + val globalScope = scope.globalScope ?: return Optional.empty() + for (child in globalScope.children) { + if (child is RecordScope && child.astNode is RecordDeclaration) { + typeToRecord[(child.astNode as RecordDeclaration?)!!.toType()] = + child.astNode as RecordDeclaration? + } + + // HACKY HACK HACK + if (child is NameScope) { + for (child2 in child.children) { + if (child2 is RecordScope && child2.astNode is RecordDeclaration) { + typeToRecord[(child2.astNode as RecordDeclaration?)!!.toType()] = + child2.astNode as RecordDeclaration? + } + } + } + } + val allAncestors = + types + .map { t: Type? -> typeToRecord.getOrDefault(t, null) } + .filter { obj: RecordDeclaration? -> Objects.nonNull(obj) } + .map { r: RecordDeclaration? -> getAncestors(r, 0) } + + // normalize/reverse depth: roots start at 0, increasing on each level + for (ancestors in allAncestors) { + val farthest = ancestors.stream().max(Comparator.comparingInt(Ancestor::depth)) + if (farthest.isPresent) { + val maxDepth: Int = farthest.get().depth + ancestors.forEach(Consumer { a: Ancestor -> a.depth = (maxDepth - a.depth) }) + } + } + var commonAncestors: MutableSet = HashSet() + for (i in allAncestors.indices) { + if (i == 0) { + commonAncestors.addAll(allAncestors[i]) + } else { + val others = allAncestors[i] + val newCommonAncestors = mutableSetOf() + // like Collection#retainAll but swaps relevant items out if the other set's + // matching ancestor has a higher depth + for (curr in commonAncestors) { + val toRetain = + others + .filter { a: Ancestor -> a == curr } + .map { a: Ancestor -> if (curr.depth >= a.depth) curr else a } + .firstOrNull() + toRetain?.let { newCommonAncestors.add(it) } + } + commonAncestors = newCommonAncestors + } + } + val lca = commonAncestors.stream().max(Comparator.comparingInt(Ancestor::depth)) + val commonType = lca.map { it.record?.toType() ?: it.record.unknownType() } + val finalType: Type + finalType = + if (commonType.isPresent) { + commonType.get() + } else { + return commonType + } + return rewrapType( + finalType, + wrapState.depth, + wrapState.pointerOrigins, + wrapState.isReference, + wrapState.referenceType + ) + } + + private fun getAncestors(recordDeclaration: RecordDeclaration?, depth: Int): Set { + if (recordDeclaration!!.superTypes.isEmpty()) { + val ret = HashSet() + ret.add(Ancestor(recordDeclaration, depth)) + return ret + } + val ancestors = + recordDeclaration.superTypes + .stream() + .map { s: Type? -> typeToRecord.getOrDefault(s, null) } + .filter { obj: RecordDeclaration? -> Objects.nonNull(obj) } + .map { s: RecordDeclaration? -> getAncestors(s, depth + 1) } + .flatMap { obj: Set -> obj.stream() } + .collect(Collectors.toSet()) + ancestors.add(Ancestor(recordDeclaration, depth)) + return ancestors + } + + fun isSupertypeOf(superType: Type, subType: Type, provider: MetadataProvider): Boolean { + var language: Language<*>? = null + val ctx: TranslationContext? + if (superType is UnknownType && subType is UnknownType) return true + if (superType.referenceDepth != subType.referenceDepth) { + return false + } + if (provider is LanguageProvider) { + language = provider.language + } + ctx = + if (provider is ContextProvider) { + provider.ctx + } else { + log.error("Missing context provider") + return false + } + + // arrays and pointers match in C/C++ + // TODO: Make this independent from the specific language + if (isCXX(language) && checkArrayAndPointer(superType, subType)) { + return true + } + + // ObjectTypes can be passed as ReferenceTypes + if (superType is ReferenceType) { + return isSupertypeOf(superType.elementType, subType, provider) + } + + // We cannot proceed without a scope provider + if (provider !is ScopeProvider) { + return false + } + val commonType = getCommonType(HashSet(java.util.List.of(superType, subType)), ctx) + return if (commonType.isPresent) { + commonType.get() == superType + } else { + // If array depth matches: check whether these are types from the standard library + try { + val superCls = Class.forName(superType.typeName) + val subCls = Class.forName(subType.typeName) + superCls.isAssignableFrom(subCls) + } catch (e: ClassNotFoundException) { + // Not in the class path or other linkage exception, can't help here + false + } catch (e: NoClassDefFoundError) { + false + } + } + } + + private fun isCXX(language: Language<*>?): Boolean { + return (language != null && + (language.javaClass.simpleName == "CLanguage" || + language.javaClass.simpleName == "CPPLanguage")) + } + + fun checkArrayAndPointer(first: Type, second: Type): Boolean { + val firstDepth = first.referenceDepth + val secondDepth = second.referenceDepth + return if (firstDepth == secondDepth) { + (first.root.name == second.root.name && first.isSimilar(second)) + } else { + false + } + } + + fun cleanup() { + typeToRecord.clear() + } + + /** + * Creates a typedef / type alias in the form of a [TypedefDeclaration] to the scope manager and + * returns it. + * + * @param frontend the language frontend + * @param rawCode the raw code + * @param target the target type + * @param alias the alias type + * @return the typedef declaration + */ + fun createTypeAlias( + frontend: LanguageFrontend<*, *>, + rawCode: String?, + target: Type, + alias: Type, + ): Declaration { + var currTarget = target + var currAlias = alias + if (alias is SecondOrderType) { + // TODO: I have NO clue what the following lines do and why they are necessary + val chain = alias.duplicate() + chain.root = currTarget + currTarget = chain + currTarget.refreshNames() + currAlias = alias.root + } + val typedef = frontend.newTypedefDeclaration(currTarget, currAlias, rawCode) + frontend.scopeManager.addTypedef(typedef) + return typedef + } + + fun resolvePossibleTypedef(alias: Type, scopeManager: ScopeManager): Type { + val finalToCheck = alias.root + val applicable = + scopeManager.currentTypedefs + .firstOrNull { t: TypedefDeclaration -> t.alias.root == finalToCheck } + ?.type + return if (applicable == null) { + alias + } else { + reWrapType(alias, applicable) + } + } + + /** + * Reconstructs the type chain when the root node is modified e.g. when swapping with alias + * (typedef) + * + * @param oldChain containing all types until the root + * @param newRoot root the chain is swapped with + * @return oldchain but root replaced with newRoot + */ + private fun reWrapType(oldChain: Type, newRoot: Type): Type { + if (oldChain.isFirstOrderType) { + newRoot.typeOrigin = oldChain.typeOrigin + } + if (!newRoot.isFirstOrderType) { + return newRoot + } + return when { + oldChain is ObjectType && newRoot is ObjectType -> { + (newRoot.root as ObjectType).generics = oldChain.generics + newRoot + } + oldChain is ReferenceType -> { + val reference = reWrapType(oldChain.elementType, newRoot) + val newChain = oldChain.duplicate() as ReferenceType + newChain.elementType = reference + newChain.refreshName() + newChain + } + oldChain is PointerType -> { + val newChain = oldChain.duplicate() as PointerType + newChain.root = reWrapType(oldChain.root, newRoot) + newChain.refreshNames() + newChain + } + else -> newRoot + } + } + + private class Ancestor(val record: RecordDeclaration?, var depth: Int) { + + override fun hashCode(): Int { + return Objects.hash(record) + } + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + if (other !is Ancestor) { + return false + } + return record == other.record + } + + override fun toString(): String { + return ToStringBuilder(this, Node.TO_STRING_STYLE) + .append("record", record!!.name) + .append("depth", depth) + .toString() + } + } + + companion object { + private val log = LoggerFactory.getLogger(TypeManager::class.java) + + var isTypeSystemActive = true + } +} diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/Language.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/Language.kt index 8fef40a6fc..b157d8447b 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/Language.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/Language.kt @@ -31,11 +31,12 @@ import com.fasterxml.jackson.databind.SerializerProvider import com.fasterxml.jackson.databind.annotation.JsonSerialize import com.fasterxml.jackson.databind.ser.std.StdSerializer import de.fraunhofer.aisec.cpg.TranslationContext +import de.fraunhofer.aisec.cpg.graph.Name import de.fraunhofer.aisec.cpg.graph.Node -import de.fraunhofer.aisec.cpg.graph.newUnknownType import de.fraunhofer.aisec.cpg.graph.statements.expressions.BinaryOperator import de.fraunhofer.aisec.cpg.graph.types.* import de.fraunhofer.aisec.cpg.graph.types.Type +import de.fraunhofer.aisec.cpg.graph.unknownType import java.io.File import kotlin.reflect.KClass import kotlin.reflect.full.primaryConstructor @@ -114,7 +115,10 @@ abstract class Language> : Node() { } init { - this.also { this.language = it } + this.also { language -> + this.language = language + language::class.simpleName?.let { this.name = Name(it) } + } } private fun arithmeticOpTypePropagation(lhs: Type, rhs: Type): Type { @@ -129,7 +133,7 @@ abstract class Language> : Node() { } else { rhs } - else -> newUnknownType() + else -> unknownType() } } @@ -142,7 +146,7 @@ abstract class Language> : Node() { // A comparison, so we return the type "boolean" return this.builtInTypes.values.firstOrNull { it is BooleanType } ?: this.builtInTypes.values.firstOrNull { it.name.localName.startsWith("bool") } - ?: newUnknownType() + ?: unknownType() } return when (operation.operatorCode) { @@ -175,9 +179,9 @@ abstract class Language> : Node() { // primitive type 1 OP primitive type 2 => primitive type 1 operation.lhs.propagationType } else { - newUnknownType() + unknownType() } - else -> newUnknownType() // We don't know what is this thing + else -> unknownType() // We don't know what is this thing } } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/LanguageFrontend.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/LanguageFrontend.kt index d1e8e12453..6ecb61fcf7 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/LanguageFrontend.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/LanguageFrontend.kt @@ -25,10 +25,7 @@ */ package de.fraunhofer.aisec.cpg.frontends -import de.fraunhofer.aisec.cpg.ScopeManager -import de.fraunhofer.aisec.cpg.TranslationConfiguration -import de.fraunhofer.aisec.cpg.TranslationContext -import de.fraunhofer.aisec.cpg.TranslationResult +import de.fraunhofer.aisec.cpg.* import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration import de.fraunhofer.aisec.cpg.graph.scopes.Scope diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/DeclarationBuilder.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/DeclarationBuilder.kt index 70b77bf692..ad95413862 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/DeclarationBuilder.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/DeclarationBuilder.kt @@ -131,7 +131,7 @@ fun MetadataProvider.newConstructorDeclaration( @JvmOverloads fun MetadataProvider.newParamVariableDeclaration( name: CharSequence?, - type: Type = newUnknownType(), + type: Type = unknownType(), variadic: Boolean = false, code: String? = null, rawNode: Any? = null @@ -155,7 +155,7 @@ fun MetadataProvider.newParamVariableDeclaration( @JvmOverloads fun MetadataProvider.newVariableDeclaration( name: CharSequence?, - type: Type = newUnknownType(), + type: Type = unknownType(), code: String? = null, implicitInitializerAllowed: Boolean = false, rawNode: Any? = null @@ -325,7 +325,7 @@ fun MetadataProvider.newEnumConstantDeclaration( @JvmOverloads fun MetadataProvider.newFieldDeclaration( name: CharSequence?, - type: Type = newUnknownType(), + type: Type = unknownType(), modifiers: List? = listOf(), code: String? = null, location: PhysicalLocation? = null, diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/ExpressionBuilder.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/ExpressionBuilder.kt index 230524e9f7..6b401d0521 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/ExpressionBuilder.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/ExpressionBuilder.kt @@ -32,6 +32,7 @@ import de.fraunhofer.aisec.cpg.graph.Node.Companion.EMPTY_NAME import de.fraunhofer.aisec.cpg.graph.NodeBuilder.log import de.fraunhofer.aisec.cpg.graph.statements.expressions.* import de.fraunhofer.aisec.cpg.graph.statements.expressions.AssignExpression +import de.fraunhofer.aisec.cpg.graph.types.ProblemType import de.fraunhofer.aisec.cpg.graph.types.Type /** @@ -43,7 +44,7 @@ import de.fraunhofer.aisec.cpg.graph.types.Type @JvmOverloads fun MetadataProvider.newLiteral( value: T, - type: Type = newUnknownType(), + type: Type = unknownType(), code: String? = null, rawNode: Any? = null, ): Literal { @@ -151,7 +152,7 @@ fun MetadataProvider.newAssignExpression( @JvmOverloads fun MetadataProvider.newNewExpression( code: String? = null, - type: Type = newUnknownType(), + type: Type = unknownType(), rawNode: Any? = null ): NewExpression { val node = NewExpression() @@ -193,7 +194,7 @@ fun MetadataProvider.newConditionalExpression( condition: Expression, thenExpr: Expression?, elseExpr: Expression?, - type: Type = newUnknownType(), + type: Type = unknownType(), code: String? = null, rawNode: Any? = null ): ConditionalExpression { @@ -351,7 +352,7 @@ fun MetadataProvider.newMemberCallExpression( fun MetadataProvider.newMemberExpression( name: CharSequence?, base: Expression, - memberType: Type = newUnknownType(), + memberType: Type = unknownType(), operatorCode: String? = ".", code: String? = null, rawNode: Any? = null @@ -391,8 +392,8 @@ fun MetadataProvider.newCastExpression(code: String? = null, rawNode: Any? = nul @JvmOverloads fun MetadataProvider.newTypeIdExpression( operatorCode: String, - type: Type = newUnknownType(), - referencedType: Type = newUnknownType(), + type: Type = unknownType(), + referencedType: Type = unknownType(), code: String? = null, rawNode: Any? = null ): TypeIdExpression { @@ -475,7 +476,7 @@ fun MetadataProvider.newArrayCreationExpression( @JvmOverloads fun MetadataProvider.newDeclaredReferenceExpression( name: CharSequence?, - type: Type = newUnknownType(), + type: Type = unknownType(), code: String? = null, rawNode: Any? = null ): DeclaredReferenceExpression { @@ -567,7 +568,7 @@ fun MetadataProvider.newDesignatedInitializerExpression( @JvmOverloads fun MetadataProvider.newTypeExpression( name: CharSequence?, - type: Type = newUnknownType(), + type: Type = unknownType(), rawNode: Any? = null ): TypeExpression { val node = TypeExpression() @@ -599,6 +600,14 @@ fun MetadataProvider.newProblemExpression( return node } +fun MetadataProvider.newProblemType(code: String? = null, rawNode: Any? = null): ProblemType { + val node = ProblemType() + node.applyMetadata(this, EMPTY_NAME, rawNode, code, true) + + log(node) + return node +} + fun Literal.duplicate(implicit: Boolean): Literal { val duplicate = Literal() duplicate.ctx = this.ctx diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/NodeBuilder.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/NodeBuilder.kt index 6d05808cf4..8561f50d8a 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/NodeBuilder.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/NodeBuilder.kt @@ -31,9 +31,7 @@ import de.fraunhofer.aisec.cpg.graph.Node.Companion.EMPTY_NAME import de.fraunhofer.aisec.cpg.graph.NodeBuilder.log import de.fraunhofer.aisec.cpg.graph.scopes.Scope import de.fraunhofer.aisec.cpg.graph.statements.expressions.* -import de.fraunhofer.aisec.cpg.graph.types.Type -import de.fraunhofer.aisec.cpg.graph.types.TypeParser -import de.fraunhofer.aisec.cpg.graph.types.UnknownType +import de.fraunhofer.aisec.cpg.graph.types.* import de.fraunhofer.aisec.cpg.passes.inference.IsInferredProvider import org.slf4j.LoggerFactory @@ -219,31 +217,6 @@ fun MetadataProvider.newAnnotationMember( return node } -/** - * Creates a new [UnknownType] and sets the appropriate language, if this [MetadataProvider] - * includes a [LanguageProvider]. - */ -fun MetadataProvider?.newUnknownType(): UnknownType { - return if (this is LanguageProvider) { - UnknownType.getUnknownType(language) - } else { - UnknownType.getUnknownType(null) - } -} - -/** - * Provides a nice alias to [TypeParser.createFrom]. In the future, this should not be used anymore - * since we are moving away from the [TypeParser] altogether. - */ -@JvmOverloads -fun LanguageProvider.parseType(name: CharSequence, resolveAlias: Boolean = false): Type { - return if (this is ContextProvider) { - TypeParser.createFrom(name.toString(), language, resolveAlias, this.ctx) - } else { - throw TranslationException("Cannot parse type without translation context") - } -} - /** Returns a new [Name] based on the [localName] and the current namespace as parent. */ fun NamespaceProvider.fqn(localName: String): Name { return this.namespace.fqn(localName) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/TypeBuilder.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/TypeBuilder.kt index e672552104..ca7e9b312c 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/TypeBuilder.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/TypeBuilder.kt @@ -25,8 +25,116 @@ */ package de.fraunhofer.aisec.cpg.graph -import de.fraunhofer.aisec.cpg.graph.types.ObjectType +import de.fraunhofer.aisec.cpg.TypeManager +import de.fraunhofer.aisec.cpg.frontends.Language +import de.fraunhofer.aisec.cpg.frontends.TranslationException +import de.fraunhofer.aisec.cpg.graph.types.* -fun ObjectType.const(): ObjectType { - return ObjectType(this, listOf(), this.isPrimitive, this.language) +/** + * Creates a new [UnknownType] and sets the appropriate language, if this [MetadataProvider] + * includes a [LanguageProvider]. + */ +fun MetadataProvider?.unknownType(): Type { + return if (this is LanguageProvider) { + UnknownType.getUnknownType(language) + } else { + UnknownType.getUnknownType(null) + } +} + +fun MetadataProvider?.incompleteType(): Type { + return IncompleteType() +} + +/** Returns a [PointerType] that describes an array reference to the current type. */ +context(ContextProvider) + +fun Type.array(): Type { + val c = + (this@ContextProvider).ctx + ?: throw TranslationException( + "Could not create type: translation context not available" + ) + val type = this.reference(PointerType.PointerOrigin.ARRAY) + + return c.typeManager.registerType(type) +} + +/** Returns a [PointerType] that describes a pointer reference to the current type. */ +context(ContextProvider) + +fun Type.pointer(): Type { + val c = + (this@ContextProvider).ctx + ?: throw TranslationException( + "Could not create type: translation context not available" + ) + val type = this.reference(PointerType.PointerOrigin.POINTER) + + return c.typeManager.registerType(type) +} + +context(ContextProvider) + +fun Type.ref(): Type { + val c = + (this@ContextProvider).ctx + ?: throw TranslationException( + "Could not create type: translation context not available" + ) + val type = ReferenceType(this) + + return c.typeManager.registerType(type) +} + +/** + * This function creates a new [Type] with the given [name]. In order to avoid unnecessary + * allocation of simple types, we do a pre-check within this function, whether a built-in type exist + * with the particular name. If it not exists, a new [ObjectType] is created and registered with the + * [TypeManager]. + */ +@JvmOverloads +fun LanguageProvider.objectType(name: CharSequence, generics: List = listOf()): Type { + // First, we check, whether this is a built-in type, to avoid necessary allocations of simple + // types + val builtIn = language?.getSimpleTypeOf(name.toString()) + if (builtIn != null) { + return builtIn + } + + // Otherwise, we need to create a new type and register it at the type manager + val c = + (this as? ContextProvider)?.ctx + ?: throw TranslationException( + "Could not create type: translation context not available" + ) + val type = ObjectType(name, generics, false, language) + + return c.typeManager.registerType(type) +} + +/** + * This function constructs a new primitive [Type]. Primitive or built-in types are defined in + * [Language.builtInTypes]. This function will look up the type by its name, if it fails to find an + * appropriate build-in type, a [TranslationException] is thrown. Therefore, this function should + * primarily be called by language frontends if they are sure that this type is a built-in type, + * e.g., when constructing literals. It can be useful, if frontends want to check, whether all + * literal types are correctly registered as built-in types. + * + * If the frontend is not sure, what kind of type it is, it should call [objectType], which also + * does a check, whether it is a known built-in type. + */ +fun LanguageProvider.primitiveType(name: CharSequence): Type { + return language?.getSimpleTypeOf(name.toString()) + ?: throw TranslationException( + "Cannot find primitive type $name in language ${language?.name}. This is either an error in the language frontend or the language definition is missing a type definition." + ) +} + +/** + * Checks, whether the given [Type] is a primitive in the language specified in the + * [LanguageProvider]. + */ +fun LanguageProvider.isPrimitive(type: Type): Boolean { + return language?.primitiveTypeNames?.contains(type.typeName) == true } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt index 5f42bdb53b..24af1fcb35 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt @@ -123,7 +123,7 @@ context(DeclarationHolder) fun LanguageFrontend<*, *>.field( name: CharSequence, - type: Type = newUnknownType(), + type: Type = unknownType(), init: FieldDeclaration.() -> Unit ): FieldDeclaration { val node = newFieldDeclaration(name) @@ -145,7 +145,7 @@ context(DeclarationHolder) fun LanguageFrontend<*, *>.function( name: CharSequence, - returnType: Type = newUnknownType(), + returnType: Type = unknownType(), returnTypes: List? = null, init: (FunctionDeclaration.() -> Unit)? = null ): FunctionDeclaration { @@ -175,7 +175,7 @@ context(RecordDeclaration) fun LanguageFrontend<*, *>.method( name: CharSequence, - returnType: Type = newUnknownType(), + returnType: Type = unknownType(), init: MethodDeclaration.() -> Unit ): MethodDeclaration { val node = newMethodDeclaration(name) @@ -242,7 +242,7 @@ context(FunctionDeclaration) fun LanguageFrontend<*, *>.param( name: CharSequence, - type: Type = newUnknownType(), + type: Type = unknownType(), init: (ParamVariableDeclaration.() -> Unit)? = null ): ParamVariableDeclaration { val node = @@ -298,7 +298,7 @@ context(DeclarationStatement) fun LanguageFrontend<*, *>.variable( name: String, - type: Type = newUnknownType(), + type: Type = unknownType(), init: (VariableDeclaration.() -> Unit)? = null ): VariableDeclaration { val node = newVariableDeclaration(name, type) @@ -435,12 +435,12 @@ fun LanguageFrontend<*, *>.new(init: (NewExpression.() -> Unit)? = null): NewExp return node } -fun LanguageFrontend<*, *>.memberOrRef(name: Name, type: Type = newUnknownType()): Expression { +fun LanguageFrontend<*, *>.memberOrRef(name: Name, type: Type = unknownType()): Expression { val node = if (name.parent != null) { newMemberExpression(name.localName, memberOrRef(name.parent)) } else { - newDeclaredReferenceExpression(name.localName, parseType(name.localName)) + newDeclaredReferenceExpression(name.localName, objectType(name.localName)) } node.type = type @@ -748,7 +748,7 @@ fun LanguageFrontend<*, *>.default(): DefaultStatement { */ context(Holder) -fun LanguageFrontend<*, *>.literal(value: N, type: Type = newUnknownType()): Literal { +fun LanguageFrontend<*, *>.literal(value: N, type: Type = unknownType()): Literal { val node = newLiteral(value, type) // Only add this to an argument holder if the nearest holder is an argument holder @@ -769,7 +769,7 @@ context(Holder) fun LanguageFrontend<*, *>.ref( name: CharSequence, - type: Type = newUnknownType(), + type: Type = unknownType(), init: (DeclaredReferenceExpression.() -> Unit)? = null ): DeclaredReferenceExpression { val node = newDeclaredReferenceExpression(name) @@ -803,13 +803,13 @@ fun LanguageFrontend<*, *>.member( val parsedName = parseName(name) val type = if (parsedName.parent != null) { - newUnknownType() + unknownType() } else { var scope = ((this@Holder) as? ScopeProvider)?.scope while (scope != null && scope !is RecordScope) { scope = scope.parent } - val scopeType = scope?.name?.let { t(it) } ?: newUnknownType() + val scopeType = scope?.name?.let { t(it) } ?: unknownType() scopeType } val memberBase = base ?: memberOrRef(parsedName.parent ?: parseName("this"), type) @@ -1072,7 +1072,7 @@ infix fun Expression.assign(rhs: Expression): BinaryOperator { /** Creates a new [Type] with the given [name] in the Fluent Node DSL. */ fun LanguageFrontend<*, *>.t(name: CharSequence, init: (Type.() -> Unit)? = null): Type { - val type = parseType(name) + val type = objectType(name) if (init != null) { init(type) } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/ConstructorDeclaration.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/ConstructorDeclaration.kt index 98ce6fe469..e870063f57 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/ConstructorDeclaration.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/ConstructorDeclaration.kt @@ -25,8 +25,6 @@ */ package de.fraunhofer.aisec.cpg.graph.declarations -import de.fraunhofer.aisec.cpg.graph.parseType - /** * The declaration of a constructor within a [RecordDeclaration]. Is it essentially a special case * of a [MethodDeclaration]. @@ -39,7 +37,7 @@ class ConstructorDeclaration : MethodDeclaration() { super.recordDeclaration = recordDeclaration if (recordDeclaration != null) { // constructors always have implicitly the return type of their class - returnTypes = listOf(parseType(recordDeclaration.name)) + returnTypes = listOf(recordDeclaration.toType()) } } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/FunctionDeclaration.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/FunctionDeclaration.kt index fa5ab715b2..b2adfcd3c4 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/FunctionDeclaration.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/FunctionDeclaration.kt @@ -110,7 +110,7 @@ open class FunctionDeclaration : ValueDeclaration(), DeclarationHolder { targetFunctionDeclaration.signatureTypes == signatureTypes } - fun hasSignature(targetSignature: List): Boolean { + fun hasSignature(targetSignature: List): Boolean { val signature = parameters .stream() @@ -195,7 +195,7 @@ open class FunctionDeclaration : ValueDeclaration(), DeclarationHolder { if (paramVariableDeclaration.default != null) { signature.add(paramVariableDeclaration.type) } else { - signature.add(newUnknownType()) + signature.add(unknownType()) } } return signature diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/RecordDeclaration.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/RecordDeclaration.kt index 839b75cd91..f9bf682090 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/RecordDeclaration.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/RecordDeclaration.kt @@ -25,13 +25,10 @@ */ package de.fraunhofer.aisec.cpg.graph.declarations -import de.fraunhofer.aisec.cpg.graph.AST -import de.fraunhofer.aisec.cpg.graph.DeclarationHolder -import de.fraunhofer.aisec.cpg.graph.StatementHolder +import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.propertyEqualsList import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdgeDelegate -import de.fraunhofer.aisec.cpg.graph.parseType import de.fraunhofer.aisec.cpg.graph.statements.Statement import de.fraunhofer.aisec.cpg.graph.types.ObjectType import de.fraunhofer.aisec.cpg.graph.types.Type @@ -216,7 +213,7 @@ class RecordDeclaration : Declaration(), DeclarationHolder, StatementHolder { * @return the type */ fun toType(): Type { - val type = parseType(name) + val type = objectType(name) if (type is ObjectType) { // as a shortcut, directly set the record declaration. This will be otherwise done // later by a pass, but for some frontends we need this immediately, so we set diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/TypedefDeclaration.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/TypedefDeclaration.kt index a591f7810d..f9e0f711ee 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/TypedefDeclaration.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/TypedefDeclaration.kt @@ -47,6 +47,9 @@ class TypedefDeclaration : Declaration() { override fun hashCode() = Objects.hash(super.hashCode(), type, alias) override fun toString(): String { - return ToStringBuilder(this).append("type", type).append("alias", alias).toString() + return ToStringBuilder(this, TO_STRING_STYLE) + .append("type", type) + .append("alias", alias) + .toString() } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/ValueDeclaration.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/ValueDeclaration.kt index ab7b1106f6..558f7e9a5f 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/ValueDeclaration.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/ValueDeclaration.kt @@ -64,7 +64,7 @@ abstract class ValueDeclaration : Declaration(), HasType { ?.typeCache ?.computeIfAbsent(this) { mutableListOf() } ?.firstOrNull() - ?: newUnknownType() + ?: unknownType() } return result } @@ -127,7 +127,7 @@ abstract class ValueDeclaration : Declaration(), HasType { override val propagationType: Type get() { return if (type is ReferenceType) { - (type as ReferenceType?)?.elementType ?: newUnknownType() + (type as ReferenceType?)?.elementType ?: unknownType() } else type } @@ -226,17 +226,6 @@ abstract class ValueDeclaration : Declaration(), HasType { .forEach { l: HasType.TypeListener -> l.possibleSubTypesChanged(this, root) } } - override fun registerTypeListener(listener: HasType.TypeListener) { - val root = mutableListOf(this) - typeListeners.add(listener) - listener.typeChanged(this, root, type) - listener.possibleSubTypesChanged(this, root) - } - - override fun unregisterTypeListener(listener: HasType.TypeListener) { - typeListeners.remove(listener) - } - override fun refreshType() { val root = mutableListOf(this) for (l in typeListeners) { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/BinaryOperator.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/BinaryOperator.kt index 1b121af01b..7c81f0b426 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/BinaryOperator.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/BinaryOperator.kt @@ -152,7 +152,7 @@ open class BinaryOperator : // function pointer call. setType(src.propagationType, root) } else { - val resultingType = language?.propagateTypeOfBinaryOperation(this) ?: newUnknownType() + val resultingType = language?.propagateTypeOfBinaryOperation(this) ?: unknownType() if (resultingType !is UnknownType) { setType(resultingType, root) } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/CallExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/CallExpression.kt index f480bd8d4b..83d77fab03 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/CallExpression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/CallExpression.kt @@ -286,7 +286,7 @@ open class CallExpression : Expression(), HasType.TypeListener, SecondaryTypeEdg } null } - val alternative = if (types.isNotEmpty()) types[0] else newUnknownType() + val alternative = if (types.isNotEmpty()) types[0] else unknownType() val commonType = getCommonType(types).orElse(alternative) val subTypes: MutableList = ArrayList(possibleSubTypes) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/CastExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/CastExpression.kt index 30024d3bd3..e2073c202d 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/CastExpression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/CastExpression.kt @@ -34,7 +34,7 @@ import org.slf4j.LoggerFactory class CastExpression : Expression(), HasType.TypeListener { @AST var expression: Expression = ProblemExpression("could not parse inner expression") - var castType: Type = newUnknownType() + var castType: Type = unknownType() set(value) { field = value type = value diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ConditionalExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ConditionalExpression.kt index bd06015e53..6fc9049a9f 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ConditionalExpression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ConditionalExpression.kt @@ -67,7 +67,7 @@ class ConditionalExpression : Expression(), HasType.TypeListener, ArgumentHolder val subTypes: MutableList = ArrayList(possibleSubTypes) subTypes.remove(oldType) subTypes.addAll(types) - val alternative = if (types.isNotEmpty()) types[0] else newUnknownType() + val alternative = if (types.isNotEmpty()) types[0] else unknownType() setType(getCommonType(types).orElse(alternative), root) setPossibleSubTypes(subTypes, root) if (previous != type) { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ConstructExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ConstructExpression.kt index 421b0730da..47c9884711 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ConstructExpression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ConstructExpression.kt @@ -26,11 +26,8 @@ package de.fraunhofer.aisec.cpg.graph.statements.expressions import de.fraunhofer.aisec.cpg.PopulatedByPass -import de.fraunhofer.aisec.cpg.graph.AST -import de.fraunhofer.aisec.cpg.graph.HasType +import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.declarations.* -import de.fraunhofer.aisec.cpg.graph.isTypeSystemActive -import de.fraunhofer.aisec.cpg.graph.parseType import de.fraunhofer.aisec.cpg.graph.types.FunctionType import de.fraunhofer.aisec.cpg.graph.types.Type import de.fraunhofer.aisec.cpg.graph.types.UnknownType @@ -80,7 +77,7 @@ class ConstructExpression : CallExpression(), HasType.TypeListener { set(value) { field = value if (value != null && this.type is UnknownType) { - type = parseType(value.name) + type = objectType(value.name) } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/Expression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/Expression.kt index 28f7c18f49..a690bf68fc 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/Expression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/Expression.kt @@ -49,7 +49,7 @@ import org.neo4j.ogm.annotation.Transient */ abstract class Expression : Statement(), HasType { - @Relationship("TYPE") private var _type: Type = newUnknownType() + @Relationship("TYPE") private var _type: Type = unknownType() /** The type of the value after evaluation. */ override var type: Type @@ -62,7 +62,7 @@ abstract class Expression : Statement(), HasType { ?.typeCache ?.computeIfAbsent(this) { mutableListOf() } ?.firstOrNull() - ?: newUnknownType() + ?: unknownType() } return result } @@ -88,7 +88,7 @@ abstract class Expression : Statement(), HasType { override val propagationType: Type get() { return if (type is ReferenceType) { - (type as ReferenceType?)?.elementType ?: newUnknownType() + (type as ReferenceType?)?.elementType ?: unknownType() } else type } @@ -212,17 +212,6 @@ abstract class Expression : Statement(), HasType { .forEach { l: HasType.TypeListener -> l.possibleSubTypesChanged(this, root) } } - override fun registerTypeListener(listener: HasType.TypeListener) { - val root = mutableListOf(this) - typeListeners.add(listener) - listener.typeChanged(this, root, type) - listener.possibleSubTypesChanged(this, root) - } - - override fun unregisterTypeListener(listener: HasType.TypeListener) { - typeListeners.remove(listener) - } - override fun refreshType() { val root = mutableListOf(this) for (l in typeListeners) { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/InitializerListExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/InitializerListExpression.kt index c7c02ddd28..69f7858c1c 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/InitializerListExpression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/InitializerListExpression.kt @@ -70,7 +70,7 @@ class InitializerListExpression : Expression(), HasType.TypeListener { if (initializers.contains(src)) { val types = initializers.map { registerType(it.type.reference(PointerOrigin.ARRAY)) }.toSet() - val alternative = if (types.isNotEmpty()) types.iterator().next() else newUnknownType() + val alternative = if (types.isNotEmpty()) types.iterator().next() else unknownType() newType = getCommonType(types).orElse(alternative) subTypes = ArrayList(possibleSubTypes) subTypes.remove(oldType) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/FunctionType.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/FunctionType.kt index 7f10a8172c..0dda94a841 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/FunctionType.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/FunctionType.kt @@ -27,8 +27,8 @@ package de.fraunhofer.aisec.cpg.graph.types import de.fraunhofer.aisec.cpg.frontends.Language import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration -import de.fraunhofer.aisec.cpg.graph.newUnknownType import de.fraunhofer.aisec.cpg.graph.registerType +import de.fraunhofer.aisec.cpg.graph.unknownType /** * A type representing a function. It contains a list of parameters and one or more return types. @@ -51,12 +51,12 @@ constructor( return FunctionPointerType( parameters.toList(), language, - returnTypes.firstOrNull() ?: newUnknownType(), + returnTypes.firstOrNull() ?: unknownType(), ) } override fun dereference(): Type { - return newUnknownType() + return unknownType() } override fun duplicate(): Type { 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 b8c3081e9c..edf680fc7c 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 @@ -25,6 +25,7 @@ */ package de.fraunhofer.aisec.cpg.graph +import de.fraunhofer.aisec.cpg.TypeManager import de.fraunhofer.aisec.cpg.frontends.TranslationException import de.fraunhofer.aisec.cpg.graph.types.Type import java.util.Optional @@ -69,11 +70,18 @@ interface HasType : ContextProvider { */ fun setPossibleSubTypes(possibleSubTypes: List, root: MutableList) - fun registerTypeListener(listener: TypeListener) + val typeListeners: MutableSet - fun unregisterTypeListener(listener: TypeListener) + fun registerTypeListener(listener: TypeListener) { + val root = mutableListOf(this) + typeListeners.add(listener) + listener.typeChanged(this, root, type) + listener.possibleSubTypesChanged(this, root) + } - val typeListeners: Set + fun unregisterTypeListener(listener: TypeListener) { + typeListeners.remove(listener) + } fun refreshType() @@ -105,10 +113,10 @@ interface HasType : ContextProvider { val Node.isTypeSystemActive: Boolean get() { - return TypeManager.isTypeSystemActive() + return TypeManager.isTypeSystemActive } -fun Node.isSupertypeOf(superType: Type, subType: Type?): Boolean { +fun Node.isSupertypeOf(superType: Type, subType: Type): Boolean { val c = ctx ?: throw TranslationException("context not available") return c.typeManager.isSupertypeOf(superType, subType, this) } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/ProblemType.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/ProblemType.kt new file mode 100644 index 0000000000..7ca10808cc --- /dev/null +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/ProblemType.kt @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2023, 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.graph.types + +class ProblemType : Type() { + override fun reference(pointer: PointerType.PointerOrigin?): Type { + return this + } + + override fun dereference(): Type { + return this + } + + override fun duplicate(): Type { + return this + } +} diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/ReferenceType.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/ReferenceType.kt index d424518375..7c46b9ffbe 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/ReferenceType.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/ReferenceType.kt @@ -25,8 +25,8 @@ */ package de.fraunhofer.aisec.cpg.graph.types -import de.fraunhofer.aisec.cpg.graph.newUnknownType import de.fraunhofer.aisec.cpg.graph.types.PointerType.PointerOrigin +import de.fraunhofer.aisec.cpg.graph.unknownType import java.util.* import org.apache.commons.lang3.builder.ToStringBuilder @@ -37,7 +37,7 @@ import org.apache.commons.lang3.builder.ToStringBuilder * called. */ class ReferenceType : Type, SecondOrderType { - override var elementType: Type = newUnknownType() + override var elementType: Type = unknownType() constructor() : super() diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/Type.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/Type.kt index f293c99218..a43599c030 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/Type.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/Type.kt @@ -26,6 +26,7 @@ package de.fraunhofer.aisec.cpg.graph.types import de.fraunhofer.aisec.cpg.PopulatedByPass +import de.fraunhofer.aisec.cpg.TypeManager import de.fraunhofer.aisec.cpg.frontends.Language import de.fraunhofer.aisec.cpg.graph.Name import de.fraunhofer.aisec.cpg.graph.Node @@ -93,9 +94,17 @@ abstract class Type : Node { } /** + * Creates a new [Type] based on a reference of this type. The main usage is to create pointer + * and array types. This function does NOT invoke [TypeManager.registerType] and should only be + * used internally. For the public API, the extension functions, such as [Type.array] should be + * used instead. + * * @param pointer Reason for the reference (array of pointer) * @return Returns a reference to the current Type. E.g. when creating a pointer to an existing * ObjectType + * + * TODO(oxisto) Ideally, we would make this function "internal", but there is a bug in the Go + * frontend, so that we still need this function :( */ abstract fun reference(pointer: PointerOrigin?): Type diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/UnknownType.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/UnknownType.kt index 84a5947453..ea8925e75f 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/UnknownType.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/UnknownType.kt @@ -36,20 +36,11 @@ import java.util.concurrent.ConcurrentHashMap * used. E.g.: This occurs when the type is inferred by the compiler automatically when using * keywords such as auto in cpp */ -class UnknownType : Type { - private constructor() : super() { +class UnknownType private constructor() : Type() { + init { name = Name(UNKNOWN_TYPE_STRING, null, language) } - /** - * This is only intended to be used by [TypeParser] for edge cases like distinct unknown types, - * such as "UNKNOWN1", thus the package-private visibility. Other users should see - * [getUnknownType] instead - * - * @param typeName The name of this unknown type, usually a variation of UNKNOWN - */ - internal constructor(typeName: String?) : super(typeName) - /** * @return Same UnknownType, as it is makes no sense to obtain a pointer/reference to an * UnknownType diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/WrapState.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/WrapState.kt index 70dd64dad3..c84ee87448 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/WrapState.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/WrapState.kt @@ -31,14 +31,14 @@ import de.fraunhofer.aisec.cpg.graph.types.PointerType.PointerOrigin class WrapState { @JvmField var depth = 0 var isReference = false - @JvmField var pointerOrigins: Array + @JvmField var pointerOrigins: Array @JvmField var referenceType: ReferenceType? = null init { pointerOrigins = arrayOf(PointerOrigin.ARRAY) } - fun setPointerOrigin(pointerOrigin: Array) { + fun setPointerOrigin(pointerOrigin: Array) { pointerOrigins = pointerOrigin } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CXXCallResolverHelper.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CXXCallResolverHelper.kt index e58db03f34..24ba098441 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CXXCallResolverHelper.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CXXCallResolverHelper.kt @@ -26,6 +26,7 @@ package de.fraunhofer.aisec.cpg.passes 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.* import de.fraunhofer.aisec.cpg.graph.statements.expressions.* @@ -43,14 +44,14 @@ import java.util.regex.Pattern * signature by means of casting */ fun compatibleSignatures( - callSignature: List, + callSignature: List, functionSignature: List, ctx: TranslationContext ): Boolean { return if (callSignature.size == functionSignature.size) { for (i in callSignature.indices) { if ( - callSignature[i]?.isPrimitive != functionSignature[i].isPrimitive && + callSignature[i].isPrimitive != functionSignature[i].isPrimitive && !ctx.typeManager.isSupertypeOf( functionSignature[i], callSignature[i], @@ -75,7 +76,7 @@ fun compatibleSignatures( fun getCallSignatureWithDefaults( call: CallExpression, functionDeclaration: FunctionDeclaration -): List { +): List { val callSignature = mutableListOf(*call.signature.toTypedArray()) if (call.signature.size < functionDeclaration.parameters.size) { callSignature.addAll( diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolver.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolver.kt index ae2f943134..70b17a789b 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolver.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolver.kt @@ -98,7 +98,7 @@ open class CallResolver(ctx: TranslationContext) : SymbolResolverPass(ctx) { protected fun registerMethods(currentClass: RecordDeclaration?, currentNode: Node?) { if (currentNode is MethodDeclaration && currentClass != null) { - containingType[currentNode] = currentNode.parseType(currentClass.name) + containingType[currentNode] = currentNode.objectType(currentClass.name) } } @@ -559,7 +559,7 @@ open class CallResolver(ctx: TranslationContext) : SymbolResolverPass(ctx) { } protected fun getOverridingCandidates( - possibleSubTypes: Set, + possibleSubTypes: Set, declaration: FunctionDeclaration ): Set { return declaration.overriddenBy @@ -573,7 +573,7 @@ open class CallResolver(ctx: TranslationContext) : SymbolResolverPass(ctx) { * @return ConstructorDeclaration that matches the provided signature */ protected fun getConstructorDeclarationDirectMatch( - signature: List, + signature: List, recordDeclaration: RecordDeclaration ): ConstructorDeclaration? { for (constructor in recordDeclaration.constructors) { @@ -595,7 +595,7 @@ open class CallResolver(ctx: TranslationContext) : SymbolResolverPass(ctx) { constructExpression: ConstructExpression, recordDeclaration: RecordDeclaration ): ConstructorDeclaration { - val signature: List = constructExpression.signature + val signature = constructExpression.signature var constructorCandidate = getConstructorDeclarationDirectMatch(signature, recordDeclaration) if (constructorCandidate == null && constructExpression.language is HasDefaultArguments) { @@ -617,7 +617,7 @@ open class CallResolver(ctx: TranslationContext) : SymbolResolverPass(ctx) { } protected fun getConstructorDeclarationForExplicitInvocation( - signature: List, + signature: List, recordDeclaration: RecordDeclaration ): ConstructorDeclaration { return recordDeclaration.constructors.firstOrNull { it.hasSignature(signature) } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/Pass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/Pass.kt index 2ad6a0ac13..2384b3b182 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/Pass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/Pass.kt @@ -25,10 +25,7 @@ */ package de.fraunhofer.aisec.cpg.passes -import de.fraunhofer.aisec.cpg.ScopeManager -import de.fraunhofer.aisec.cpg.TranslationConfiguration -import de.fraunhofer.aisec.cpg.TranslationContext -import de.fraunhofer.aisec.cpg.TranslationResult +import de.fraunhofer.aisec.cpg.* import de.fraunhofer.aisec.cpg.frontends.Language import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend import de.fraunhofer.aisec.cpg.frontends.TranslationException diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/SymbolResolverPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/SymbolResolverPass.kt index 4ca2deb0ee..fbd440fab5 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/SymbolResolverPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/SymbolResolverPass.kt @@ -53,7 +53,7 @@ abstract class SymbolResolverPass(ctx: TranslationContext) : ComponentPass(ctx) protected fun findEnums(node: Node?) { if (node is EnumDeclaration) { // TODO: Use the name instead of the type. - val type = node.parseType(node.name) + val type = node.objectType(node.name) enumMap.putIfAbsent(type, node) } } @@ -69,7 +69,7 @@ abstract class SymbolResolverPass(ctx: TranslationContext) : ComponentPass(ctx) protected fun FunctionDeclaration.matches( name: Name, returnType: Type, - signature: List + signature: List ): Boolean { val thisReturnType = if (this.returnTypes.isEmpty()) { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/VariableUsageResolver.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/VariableUsageResolver.kt index 20249d3e8b..f59a594076 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/VariableUsageResolver.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/VariableUsageResolver.kt @@ -142,7 +142,7 @@ open class VariableUsageResolver(ctx: TranslationContext) : SymbolResolverPass(c var recordDeclType: Type? = null if (currentClass != null) { - recordDeclType = currentClass.parseType(currentClass.name) + recordDeclType = currentClass.toType() } if (current.type is FunctionPointerType && refersTo == null) { refersTo = resolveFunctionPtr(current) @@ -198,9 +198,9 @@ open class VariableUsageResolver(ctx: TranslationContext) : SymbolResolverPass(c return if (language != null && language.namespaceDelimiter.isNotEmpty()) { val parentName = (current.name.parent ?: current.name).toString() - current.parseType(parentName) + current.objectType(parentName) } else { - current.newUnknownType() + current.unknownType() } } @@ -234,7 +234,7 @@ open class VariableUsageResolver(ctx: TranslationContext) : SymbolResolverPass(c "Could not find referring super type ${superType.typeName} for ${curClass.name} in the record map. Will set the super type to java.lang.Object" ) // TODO: Should be more generic! - base.type = current.parseType(Any::class.java.name) + base.type = current.objectType(Any::class.java.name) } else { // We need to connect this super reference to the receiver of this // method @@ -254,7 +254,7 @@ open class VariableUsageResolver(ctx: TranslationContext) : SymbolResolverPass(c } else { // no explicit super type -> java.lang.Object // TODO: Should be more generic - val objectType = current.parseType(Any::class.java.name) + val objectType = current.objectType(Any::class.java.name) base.type = objectType } } else { @@ -269,13 +269,13 @@ open class VariableUsageResolver(ctx: TranslationContext) : SymbolResolverPass(c return } } else if (baseTarget is RecordDeclaration) { - var baseType = baseTarget.parseType(baseTarget.name) + var baseType = baseTarget.toType() if (baseType.name !in recordMap) { val containingT = baseType val fqnResolvedType = recordMap.keys.firstOrNull { it.lastPartsMatch(containingT.name) } if (fqnResolvedType != null) { - baseType = baseTarget.parseType(fqnResolvedType) + baseType = baseTarget.objectType(fqnResolvedType) } } current.refersTo = resolveMember(baseType, current) @@ -286,7 +286,7 @@ open class VariableUsageResolver(ctx: TranslationContext) : SymbolResolverPass(c if (baseType.name !in recordMap) { val fqnResolvedType = recordMap.keys.firstOrNull { it.lastPartsMatch(baseType.name) } if (fqnResolvedType != null) { - baseType = current.base.parseType(fqnResolvedType) + baseType = current.base.objectType(fqnResolvedType) } } current.refersTo = resolveMember(baseType, current) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/Inference.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/Inference.kt index d532673e72..76bcd8c09a 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/Inference.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/Inference.kt @@ -234,7 +234,7 @@ class Inference(val start: Node, override val ctx: TranslationContext) : name: String, ): TypeParamDeclaration { val parameterizedType = ParameterizedType(name, language) - typeManager.addTypeParameter(start as? FunctionTemplateDeclaration, parameterizedType) + typeManager.addTypeParameter(start as FunctionTemplateDeclaration, parameterizedType) val decl = newTypeParamDeclaration(name, name) decl.type = parameterizedType diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/GraphExamples.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/GraphExamples.kt index a72fba1d8a..33d664c905 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/GraphExamples.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/GraphExamples.kt @@ -28,10 +28,10 @@ package de.fraunhofer.aisec.cpg import de.fraunhofer.aisec.cpg.frontends.StructTestLanguage import de.fraunhofer.aisec.cpg.frontends.TestLanguage import de.fraunhofer.aisec.cpg.frontends.TestLanguageFrontend -import de.fraunhofer.aisec.cpg.graph.TypeManager import de.fraunhofer.aisec.cpg.graph.builder.* import de.fraunhofer.aisec.cpg.graph.newInitializerListExpression import de.fraunhofer.aisec.cpg.graph.newVariableDeclaration +import de.fraunhofer.aisec.cpg.graph.types.PointerType.PointerOrigin.POINTER import de.fraunhofer.aisec.cpg.sarif.PhysicalLocation import de.fraunhofer.aisec.cpg.sarif.Region import java.net.URI @@ -87,7 +87,7 @@ class GraphExamples { // The main method function("main", t("int")) { body { - declare { variable("node", t("T*")) } + declare { variable("node", t("T").reference(POINTER)) } member("value", ref("node"), "->") assign literal(42, t("int")) member("next", ref("node"), "->") assign ref("node") memberCall( diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/TypeTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/TypeTest.kt new file mode 100644 index 0000000000..fcc512f89e --- /dev/null +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/TypeTest.kt @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2023, 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.graph + +import de.fraunhofer.aisec.cpg.assertLocalName +import de.fraunhofer.aisec.cpg.frontends.TestLanguageFrontend +import de.fraunhofer.aisec.cpg.frontends.TranslationException +import kotlin.test.Test +import org.junit.jupiter.api.assertThrows + +class TypeTest { + @Test + fun testType() { + with(TestLanguageFrontend()) { + val tu = newTranslationUnitDeclaration("file.extension") + this.scopeManager.resetToGlobal(tu) + + val func = newFunctionDeclaration("main") + assertLocalName("main", func) + + val simpleType = objectType("SomeObject") + assertLocalName("SomeObject", simpleType) + } + } + + @Test + fun testPrimitive() { + with(TestLanguageFrontend()) { + val boolean = primitiveType("boolean") + assertLocalName("boolean", boolean) + + assertThrows { primitiveType("BOOLEAN") } + } + } +} diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/AssignExpressionTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/AssignExpressionTest.kt index c9d96d6df7..9d59e76ad2 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/AssignExpressionTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/AssignExpressionTest.kt @@ -50,7 +50,7 @@ class AssignExpressionTest { assertContains(refB.typeListeners, stmt) // Suddenly, we now we know the type of b. - refB.type = parseType("MyClass") + refB.type = objectType("MyClass") // It should now propagate to a assertLocalName("MyClass", refA.type) @@ -68,7 +68,7 @@ class AssignExpressionTest { val func = function( "func", - returnTypes = listOf(parseType("MyClass"), parseType("error")) + returnTypes = listOf(objectType("MyClass"), objectType("error")) ) function("main") { val refA = newDeclaredReferenceExpression("a") diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPassTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPassTest.kt index 2d78e0de1e..f227e6a489 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPassTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPassTest.kt @@ -28,6 +28,7 @@ package de.fraunhofer.aisec.cpg.passes import de.fraunhofer.aisec.cpg.ScopeManager import de.fraunhofer.aisec.cpg.TranslationConfiguration import de.fraunhofer.aisec.cpg.TranslationContext +import de.fraunhofer.aisec.cpg.TypeManager import de.fraunhofer.aisec.cpg.frontends.TestLanguage import de.fraunhofer.aisec.cpg.frontends.TestLanguageFrontend import de.fraunhofer.aisec.cpg.graph.* diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/scopes/ScopeManagerTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/scopes/ScopeManagerTest.kt index 2926bfe247..a3674d7583 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/scopes/ScopeManagerTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/scopes/ScopeManagerTest.kt @@ -25,10 +25,7 @@ */ package de.fraunhofer.aisec.cpg.passes.scopes -import de.fraunhofer.aisec.cpg.BaseTest -import de.fraunhofer.aisec.cpg.ScopeManager -import de.fraunhofer.aisec.cpg.TranslationConfiguration -import de.fraunhofer.aisec.cpg.TranslationContext +import de.fraunhofer.aisec.cpg.* import de.fraunhofer.aisec.cpg.frontends.TestLanguage import de.fraunhofer.aisec.cpg.frontends.TestLanguageFrontend import de.fraunhofer.aisec.cpg.graph.* diff --git a/cpg-core/src/testFixtures/kotlin/de/fraunhofer/aisec/cpg/frontends/TestLanguage.kt b/cpg-core/src/testFixtures/kotlin/de/fraunhofer/aisec/cpg/frontends/TestLanguage.kt index 9cb0346740..839c57538b 100644 --- a/cpg-core/src/testFixtures/kotlin/de/fraunhofer/aisec/cpg/frontends/TestLanguage.kt +++ b/cpg-core/src/testFixtures/kotlin/de/fraunhofer/aisec/cpg/frontends/TestLanguage.kt @@ -26,12 +26,12 @@ package de.fraunhofer.aisec.cpg.frontends import de.fraunhofer.aisec.cpg.* +import de.fraunhofer.aisec.cpg.TypeManager import de.fraunhofer.aisec.cpg.graph.Node -import de.fraunhofer.aisec.cpg.graph.TypeManager import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration -import de.fraunhofer.aisec.cpg.graph.newUnknownType import de.fraunhofer.aisec.cpg.graph.statements.expressions.ProblemExpression import de.fraunhofer.aisec.cpg.graph.types.* +import de.fraunhofer.aisec.cpg.graph.unknownType import de.fraunhofer.aisec.cpg.sarif.PhysicalLocation import java.io.File import java.util.function.Supplier @@ -89,7 +89,7 @@ open class TestLanguageFrontend( override fun typeOf(type: Any): Type { // reserved for future use - return newUnknownType() + return unknownType() } override fun codeOf(astNode: Any): String? { diff --git a/cpg-language-cxx/build.gradle.kts b/cpg-language-cxx/build.gradle.kts index 8ca370de65..ba55a29ba6 100644 --- a/cpg-language-cxx/build.gradle.kts +++ b/cpg-language-cxx/build.gradle.kts @@ -39,12 +39,6 @@ publishing { } } -tasks.withType().configureEach { - kotlinOptions { - freeCompilerArgs = listOf("-Xcontext-receivers") - } -} - dependencies { api(libs.javaparser) diff --git a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CLanguage.kt b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CLanguage.kt index 79c8538a23..c9960715ed 100644 --- a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CLanguage.kt +++ b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CLanguage.kt @@ -32,10 +32,7 @@ import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression -import de.fraunhofer.aisec.cpg.graph.types.FloatingPointType -import de.fraunhofer.aisec.cpg.graph.types.IntegerType -import de.fraunhofer.aisec.cpg.graph.types.NumericType -import de.fraunhofer.aisec.cpg.graph.types.Type +import de.fraunhofer.aisec.cpg.graph.types.* import de.fraunhofer.aisec.cpg.passes.CallResolver import de.fraunhofer.aisec.cpg.passes.resolveWithImplicitCast import java.util.regex.Pattern @@ -66,50 +63,38 @@ open class CLanguage : override val compoundAssignmentOperators = setOf("+=", "-=", "*=", "/=", "%=", "<<=", ">>=", "&=", "|=", "^=") + /** + * The list of built-in types. See https://en.cppreference.com/w/c/language/arithmetic_types for + * a reference. We only list equivalent types here and use the canonical form of integer values. + */ @Transient @JsonIgnore override val builtInTypes: Map = mapOf( - "boolean" to IntegerType("boolean", 1, this, NumericType.Modifier.SIGNED), + // Integer types "char" to IntegerType("char", 8, this, NumericType.Modifier.NOT_APPLICABLE), - "byte" to IntegerType("byte", 8, this, NumericType.Modifier.SIGNED), - "short" to IntegerType("short", 16, this, NumericType.Modifier.SIGNED), - "short int" to IntegerType("short", 16, this, NumericType.Modifier.SIGNED), - "int" to IntegerType("int", 32, this, NumericType.Modifier.SIGNED), - "long" to IntegerType("long", 64, this, NumericType.Modifier.SIGNED), - "long int" to IntegerType("long", 64, this, NumericType.Modifier.SIGNED), - "long long" to IntegerType("long long int", 64, this, NumericType.Modifier.SIGNED), - "long long int" to IntegerType("long long int", 64, this, NumericType.Modifier.SIGNED), "signed char" to IntegerType("signed char", 8, this, NumericType.Modifier.SIGNED), - "signed byte" to IntegerType("byte", 8, this, NumericType.Modifier.SIGNED), - "signed short" to IntegerType("short", 16, this, NumericType.Modifier.SIGNED), - "signed short int" to IntegerType("short", 16, this, NumericType.Modifier.SIGNED), - "signed" to IntegerType("int", 32, this, NumericType.Modifier.SIGNED), - "signed int" to IntegerType("int", 32, this, NumericType.Modifier.SIGNED), - "signed long" to IntegerType("long", 64, this, NumericType.Modifier.SIGNED), - "signed long int" to IntegerType("long", 64, this, NumericType.Modifier.SIGNED), - "signed long long" to - IntegerType("long long int", 64, this, NumericType.Modifier.SIGNED), - "signed long long int" to - IntegerType("long long int", 64, this, NumericType.Modifier.SIGNED), - "float" to FloatingPointType("float", 32, this, NumericType.Modifier.SIGNED), - "double" to FloatingPointType("double", 64, this, NumericType.Modifier.SIGNED), "unsigned char" to IntegerType("unsigned char", 8, this, NumericType.Modifier.UNSIGNED), - "unsigned byte" to IntegerType("unsigned byte", 8, this, NumericType.Modifier.UNSIGNED), - "unsigned short" to - IntegerType("unsigned short", 16, this, NumericType.Modifier.UNSIGNED), + "short int" to IntegerType("short int", 16, this, NumericType.Modifier.SIGNED), "unsigned short int" to - IntegerType("unsigned short", 16, this, NumericType.Modifier.UNSIGNED), - "unsigned" to IntegerType("unsigned int", 32, this, NumericType.Modifier.UNSIGNED), + IntegerType("unsigned short int", 16, this, NumericType.Modifier.UNSIGNED), + "int" to IntegerType("int", 32, this, NumericType.Modifier.SIGNED), "unsigned int" to IntegerType("unsigned int", 32, this, NumericType.Modifier.UNSIGNED), - "unsigned long" to - IntegerType("unsigned long", 64, this, NumericType.Modifier.UNSIGNED), + "long int" to IntegerType("long int", 64, this, NumericType.Modifier.SIGNED), "unsigned long int" to - IntegerType("unsigned long", 64, this, NumericType.Modifier.UNSIGNED), - "unsigned long long" to - IntegerType("unsigned long long int", 64, this, NumericType.Modifier.UNSIGNED), + IntegerType("unsigned long int", 64, this, NumericType.Modifier.UNSIGNED), + "long long int" to IntegerType("long long int", 64, this, NumericType.Modifier.SIGNED), "unsigned long long int" to - IntegerType("unsigned long long int", 64, this, NumericType.Modifier.UNSIGNED) + IntegerType("unsigned long long int", 64, this, NumericType.Modifier.UNSIGNED), + + // Floating-point types + "float" to FloatingPointType("float", 32, this, NumericType.Modifier.SIGNED), + "double" to FloatingPointType("double", 64, this, NumericType.Modifier.SIGNED), + "long double" to + FloatingPointType("long double", 128, this, NumericType.Modifier.SIGNED), + + // Convenience types, defined in + "bool" to IntegerType("bool", 1, this, NumericType.Modifier.SIGNED), ) override fun refineNormalCallResolution( diff --git a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CPPLanguage.kt b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CPPLanguage.kt index 3e1333927a..8c22732914 100644 --- a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CPPLanguage.kt +++ b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CPPLanguage.kt @@ -51,36 +51,44 @@ class CPPLanguage : override val elaboratedTypeSpecifier = listOf("class", "struct", "union", "enum") override val unknownTypeString = listOf("auto") + /** + * The list of built-in types. See https://en.cppreference.com/w/cpp/language/types for a + * reference. We only list equivalent types here and use the canonical form of integer values. + */ @Transient override val builtInTypes = mapOf( - "bool" to BooleanType("bool", language = this), - "char" to IntegerType("char", 8, this, NumericType.Modifier.NOT_APPLICABLE), - "byte" to IntegerType("byte", 8, this, NumericType.Modifier.SIGNED), - "short" to IntegerType("short", 16, this, NumericType.Modifier.SIGNED), + // Integer types + "short int" to IntegerType("short int", 16, this, NumericType.Modifier.SIGNED), + "unsigned short int" to + IntegerType("unsigned short int", 16, this, NumericType.Modifier.UNSIGNED), "int" to IntegerType("int", 32, this, NumericType.Modifier.SIGNED), - "long" to IntegerType("long", 64, this, NumericType.Modifier.SIGNED), + "unsigned int" to IntegerType("unsigned int", 32, this, NumericType.Modifier.UNSIGNED), + "long int" to IntegerType("long int", 64, this, NumericType.Modifier.SIGNED), + "unsigned long int" to + IntegerType("unsigned long int", 64, this, NumericType.Modifier.UNSIGNED), "long long int" to IntegerType("long long int", 64, this, NumericType.Modifier.SIGNED), + "unsigned long long int" to + IntegerType("unsigned long long int", 64, this, NumericType.Modifier.UNSIGNED), + + // Boolean type + "bool" to BooleanType("bool"), + + // Character types "signed char" to IntegerType("signed char", 8, this, NumericType.Modifier.SIGNED), - "signed byte" to IntegerType("byte", 8, this, NumericType.Modifier.SIGNED), - "signed short" to IntegerType("short", 16, this, NumericType.Modifier.SIGNED), - "signed int" to IntegerType("int", 32, this, NumericType.Modifier.SIGNED), - "signed long" to IntegerType("long", 64, this, NumericType.Modifier.SIGNED), - "signed long long int" to - IntegerType("long long int", 64, this, NumericType.Modifier.SIGNED), + "unsigned char" to IntegerType("unsigned char", 8, this, NumericType.Modifier.UNSIGNED), + "char" to IntegerType("char", 8, this, NumericType.Modifier.NOT_APPLICABLE), + "char8_t" to IntegerType("char8_t", 8, this, NumericType.Modifier.NOT_APPLICABLE), + "char16_t" to IntegerType("char16_t", 16, this, NumericType.Modifier.NOT_APPLICABLE), + "char32_t" to IntegerType("char32_t", 32, this, NumericType.Modifier.NOT_APPLICABLE), + + // Floating-point types "float" to FloatingPointType("float", 32, this, NumericType.Modifier.SIGNED), "double" to FloatingPointType("double", 64, this, NumericType.Modifier.SIGNED), - "unsigned char" to IntegerType("unsigned char", 8, this, NumericType.Modifier.UNSIGNED), - "unsigned byte" to IntegerType("unsigned byte", 8, this, NumericType.Modifier.UNSIGNED), - "unsigned short" to - IntegerType("unsigned short", 16, this, NumericType.Modifier.UNSIGNED), - "unsigned int" to IntegerType("unsigned int", 32, this, NumericType.Modifier.UNSIGNED), - "unsigned long" to - IntegerType("unsigned long", 64, this, NumericType.Modifier.UNSIGNED), - "unsigned long long" to - IntegerType("unsigned long long", 64, this, NumericType.Modifier.UNSIGNED), - "unsigned long long int" to - IntegerType("unsigned long long int", 64, this, NumericType.Modifier.UNSIGNED), + "long double" to + FloatingPointType("long double", 128, this, NumericType.Modifier.SIGNED), + + // Some convenience types "std::string" to StringType("std::string", this), ) diff --git a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXLanguageFrontend.kt b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXLanguageFrontend.kt index 891382a383..0e9d379fe9 100644 --- a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXLanguageFrontend.kt +++ b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXLanguageFrontend.kt @@ -38,8 +38,8 @@ import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression import de.fraunhofer.aisec.cpg.graph.types.* -import de.fraunhofer.aisec.cpg.graph.types.FunctionType import de.fraunhofer.aisec.cpg.helpers.Benchmark +import de.fraunhofer.aisec.cpg.helpers.Util import de.fraunhofer.aisec.cpg.passes.FunctionPointerCallResolver import de.fraunhofer.aisec.cpg.passes.order.RegisterExtraPass import de.fraunhofer.aisec.cpg.sarif.PhysicalLocation @@ -61,7 +61,10 @@ import org.eclipse.cdt.core.parser.IncludeFileContentProvider import org.eclipse.cdt.core.parser.ScannerInfo import org.eclipse.cdt.internal.core.dom.parser.ASTNode import org.eclipse.cdt.internal.core.dom.parser.ASTTranslationUnit +import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPASTLiteralExpression import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPASTQualifiedName +import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPASTTemplateId +import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPASTTypeId import org.eclipse.cdt.internal.core.model.ASTStringUtil import org.eclipse.cdt.internal.core.parser.IMacroDictionary import org.eclipse.cdt.internal.core.parser.scanner.InternalFileContent @@ -385,27 +388,16 @@ class CXXLanguageFrontend(language: Language, ctx: Translat val expression: Expression = when (token.tokenType) { 1 -> // a variable - newDeclaredReferenceExpression(code, newUnknownType(), code) + newDeclaredReferenceExpression(code, unknownType(), code) 2 -> // an integer - newLiteral( - code.toInt(), - (language.getSimpleTypeOf("int") as? ObjectType) ?: parseType("int"), - code - ) + newLiteral(code.toInt(), primitiveType("int"), code) 130 -> // a string newLiteral( if (code.length >= 2) code.substring(1, code.length - 1) else "", - (language.getSimpleTypeOf("char") as? ObjectType)?.reference() - ?: parseType("char"), - code - ) - else -> - newLiteral( - code, - (language.getSimpleTypeOf("char") as? ObjectType)?.reference() - ?: parseType("char"), + primitiveType("char").pointer(), code ) + else -> newLiteral(code, primitiveType("char").pointer(), code) } return newAnnotationMember("", expression, code) } @@ -446,7 +438,7 @@ class CXXLanguageFrontend(language: Language, ctx: Translat val loc: Pair = Pair(location.artifactLocation.uri.path, location.region.endLine) comments[loc]?.let { - // only exact match for now} + // only exact match for now node.comment = it } // TODO: handle orphanComments? i.e. comments which do not correspond to one line @@ -482,20 +474,13 @@ class CXXLanguageFrontend(language: Language, ctx: Translat hint: Declaration? = null ): Type { // Retrieve the "name" of this type, including qualifiers. - // TODO: In the future, we should parse the qualifiers, such as const here, instead of in - // the TypeParser val name = ASTStringUtil.getSignatureString(specifier, null) + var resolveAlias = false + var type = when (specifier) { - is IASTSimpleDeclSpecifier -> { - if (hint is ConstructorDeclaration && hint.name.parent != null) { - parseType(hint.name.parent!!) - } else { - // A primitive type - parseType(name) - } - } + is IASTSimpleDeclSpecifier -> typeOf(specifier, hint) is IASTNamedTypeSpecifier -> { // A reference to an object type. We need to differentiate between two cases: // a) the type name is already an FQN. In this case, we can just parse it as @@ -504,16 +489,17 @@ class CXXLanguageFrontend(language: Language, ctx: Translat // refers to a symbol in our current namespace. This means that we are doing // some resolving in the frontend, which we actually want to avoid since it // has limited view. - // - // Note: we cannot use parseType here, because of typedefs (and templates?) the - // TypeParser still needs to have access directly to the language frontend - // (meh!) - if (specifier.name is CPPASTQualifiedName) { - // Case a: FQN - TypeParser.createFrom(name, true, this) + if ( + specifier.name is CPPASTQualifiedName || specifier.name is CPPASTTemplateId + ) { + // Case a: FQN or template + resolveAlias = true + typeOf(specifier.name) } else { // Case b: Peek into our symbols. This is most likely limited to our current // translation unit + resolveAlias = true + val decl = scopeManager.currentScope?.let { scopeManager.getRecordForName(it, Name(name)) @@ -521,33 +507,131 @@ class CXXLanguageFrontend(language: Language, ctx: Translat // We found a symbol, so we can use its name if (decl != null) { - TypeParser.createFrom(decl.name.toString(), true, this) + objectType(decl.name) } else { + // It could be, that this is a parameterized type + val paramType = + typeManager.searchTemplateScopeForDefinedParameterizedTypes( + scopeManager.currentScope, + specifier.name.toString() + ) // Otherwise, we keep it as a local name and hope for the best - TypeParser.createFrom(name, true, this) + paramType ?: typeOf(specifier.name) } } } is IASTCompositeTypeSpecifier -> { // A class. This actually also declares the class. At the moment, we handle this // in handleSimpleDeclaration, but we might want to move it here - TypeParser.createFrom(name, true, this) + resolveAlias = true + + objectType(specifier.name.toString()) } is IASTElaboratedTypeSpecifier -> { + resolveAlias = true + // A class or struct - TypeParser.createFrom(name, true, this) + objectType(specifier.name.toString()) } else -> { - newUnknownType() + unknownType() } } - type = typeManager.registerType(type) + type = + if (resolveAlias) { + typeManager.registerType(typeManager.resolvePossibleTypedef(type, scopeManager)) + } else { + typeManager.registerType(type) + } type = this.adjustType(declarator, type) return type } + private fun typeOf( + specifier: IASTSimpleDeclSpecifier, + hint: Declaration? = null, + ): Type { + val name = specifier.rawSignature + + return when { + // auto type; we model this as an unknown type. Maybe in the future, we will + // differentiate between unknown types and "auto-deduced" types + specifier.type == IASTSimpleDeclSpecifier.t_auto -> { + unknownType() + } + // void type + specifier.type == IASTSimpleDeclSpecifier.t_void -> { + IncompleteType() + } + // The type of constructor declaration is always the declaration itself + specifier.type == IASTSimpleDeclSpecifier.t_unspecified && + hint is ConstructorDeclaration -> { + hint.name.parent?.let { objectType(it) } ?: unknownType() + } + // C (not C++) allows unspecified types in function declarations, they + // default to int and usually produce a warning + name == "" && language !is CPPLanguage -> { + Util.warnWithFileLocation( + this, + specifier, + log, + "Type specifier missing, defaulting to 'int'" + ) + primitiveType("int") + } + name == "" && language is CPPLanguage -> { + Util.errorWithFileLocation( + this, + specifier, + log, + "C++ does not allow unspecified type specifiers" + ) + newProblemType() + } + // In all other cases, this must be a primitive type, otherwise it's an error + else -> { + // We need to remove qualifiers such as "const" from the name here, because + // we model them as part of the variable declaration and not the type, so use + // the "canonical" name + primitiveType(specifier.canonicalName) + } + } + } + + fun typeOf(name: IASTName, prefix: String? = null): Type { + if (name is CPPASTQualifiedName) { + val last = name.lastName + if (last is CPPASTTemplateId) { + return typeOf(last, name.qualifier.joinToString("::", postfix = "::")) + } + } else if (name is CPPASTTemplateId) { + // Build fqn + val fqn = + if (prefix != null) { + prefix + name.templateName.toString() + } else { + name.templateName.toString() + } + val generics = mutableListOf() + + // Loop through template arguments + for (arg in name.templateArguments) { + if (arg is CPPASTTypeId) { + generics += typeOf(arg) + } else if (arg is CPPASTLiteralExpression) { + // This is most likely a constant in a template class definition, but we need to + // model this somehow, but it seems the old code just ignored this, so we do as + // well! + } + } + + return objectType(fqn, generics) + } + return objectType(name.toString()) + } + /** * This is a little helper function, primarily used by [typeOf]. It's primary purpose is to * "adjust" the [incoming] type based on the [declarator]. This is needed because the type @@ -556,27 +640,21 @@ class CXXLanguageFrontend(language: Language, ctx: Translat private fun adjustType(declarator: IASTDeclarator, incoming: Type): Type { var type = incoming - // First, look at the declarator's pointer operator, to see whether, we need to wrap the + // First, look at the declarator's pointer operator, to see whether we need to wrap the // type into a pointer or similar for (op in declarator.pointerOperators) { type = when (op) { - is IASTPointer -> { - type.reference(PointerType.PointerOrigin.POINTER) - } - is ICPPASTReferenceOperator -> { - ReferenceType(type) - } - else -> { - type - } + is IASTPointer -> type.pointer() + is ICPPASTReferenceOperator -> ReferenceType(type) + else -> type } } // Check, if we are an array type if (declarator is IASTArrayDeclarator) { for (mod in declarator.arrayModifiers) { - type = type.reference(PointerType.PointerOrigin.ARRAY) + type = type.array() } } else if (declarator is IASTStandardFunctionDeclarator) { // Loop through the parameters @@ -622,8 +700,22 @@ class CXXLanguageFrontend(language: Language, ctx: Translat type = adjustType(declarator.nestedDeclarator, type) } + type = typeManager.registerType(type) + + // Check for parameterized types + if (type is SecondOrderType) { + val templateType = + typeManager.searchTemplateScopeForDefinedParameterizedTypes( + scopeManager.currentScope, + type.root.name.toString() + ) + if (templateType != null) { + type.root = templateType + } + } + // Make sure, the type manager knows about this type - return typeManager.registerType(type) + return type } companion object { @@ -649,3 +741,56 @@ class CXXLanguageFrontend(language: Language, ctx: Translat } } } + +/** + * Returns the type specified in the [IASTSimpleDeclSpecifier] in a "canonical" form and without any + * other specifiers or keywords such as "typedef" or "const". + */ +private val IASTSimpleDeclSpecifier.canonicalName: CharSequence + get() { + var type = this.type + var parts = mutableListOf() + // First, we specify whether it is signed or unsigned. We only need "signed" for chars + if (this.isUnsigned) { + parts += "unsigned" + } else if (this.isSigned && this.type == IASTSimpleDeclSpecifier.t_char) { + parts += "signed" + } + + // Next, we analyze the size (long, long long, ...) + if (this.isShort || this.isLong || this.isLongLong) { + parts += + if (this.isShort) { + "short" + } else if (this.isLong) { + "long" + } else { + "long long" + } + + // Also make this an int, if it is omitted + if (type == IASTSimpleDeclSpecifier.t_unspecified) { + type = IASTSimpleDeclSpecifier.t_int + } + } + + // Last part is the actual type (int, float, ...) + when (type) { + IASTSimpleDeclSpecifier.t_char -> parts += "char" + IASTSimpleDeclSpecifier.t_char16_t -> parts += "char16_t" + IASTSimpleDeclSpecifier.t_char32_t -> parts += "chat32_t" + IASTSimpleDeclSpecifier.t_int -> parts += "int" + IASTSimpleDeclSpecifier.t_float -> parts += "float" + IASTSimpleDeclSpecifier.t_double -> parts += "double" + IASTSimpleDeclSpecifier.t_bool -> parts += "bool" + IASTSimpleDeclSpecifier.t_unspecified -> { + // nothing to do + } + IASTSimpleDeclSpecifier.t_auto -> parts = mutableListOf("auto") + else -> { + LanguageFrontend.Companion.log.error("Unknown C/C++ simple type: {}", type) + } + } + + return parts.joinToString(" ") + } diff --git a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/DeclarationHandler.kt b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/DeclarationHandler.kt index 4f70039e99..88a1faafe9 100644 --- a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/DeclarationHandler.kt +++ b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/DeclarationHandler.kt @@ -132,7 +132,7 @@ class DeclarationHandler(lang: CXXLanguageFrontend) : // Retrieve the type. This should parse as a function type, otherwise it is unknown. val type = frontend.typeOf(ctx.declarator, ctx.declSpecifier, declaration) as? FunctionType - declaration.type = type ?: newUnknownType() + declaration.type = type ?: unknownType() declaration.isDefinition = true // We also need to set the return type, based on the function type. @@ -318,12 +318,7 @@ class DeclarationHandler(lang: CXXLanguageFrontend) : ) typeParamDeclaration.type = parameterizedType if (templateParameter.defaultType != null) { - val defaultType = - TypeParser.createFrom( - templateParameter.defaultType.declSpecifier.rawSignature, - false, - frontend - ) + val defaultType = frontend.typeOf(templateParameter.defaultType) typeParamDeclaration.default = defaultType } templateDeclaration.addParameter(typeParamDeclaration) @@ -487,27 +482,31 @@ class DeclarationHandler(lang: CXXLanguageFrontend) : declSpecifier } - // It is important, that we parse the type first, so that the type is known before - // parsing the declaration. - // This allows us to guess cast vs. call expression in - // ExpressionHandler.handleUnaryExpression. - var type = frontend.typeOf(declarator, declSpecifierToUse) + var type: Type + + // If this is a variable declaration with initializer, it is important, that we + // parse the type first, so that the type is known before parsing the declaration. + // This allows us to guess cast vs. call expression in the initializer. + if (declarator !is IASTFunctionDeclarator && declarator.initializer != null) { + // We only need to parse it, but we are not really storing the result. That is + // not ideal, but probably the "best" way. + frontend.typeOf(declarator, declSpecifierToUse) + } if (ctx.isTypedef) { + type = frontend.typeOf(declarator, declSpecifierToUse) + // Handle typedefs. val declaration = handleTypedef(declarator, ctx, type) sequence.addDeclaration(declaration) } else { + // Parse the declaration first, so we can supply the declaration as a hint to + // the typeOf function. val declaration = frontend.declaratorHandler.handle(declarator) as? ValueDeclaration - // We need to reparse the type, if this is a constructor declaration, so that we - // can supply this as a hint to - // the typeOf - if (declaration is ConstructorDeclaration) { - type = frontend.typeOf(declarator, declSpecifierToUse, declaration) - } + type = frontend.typeOf(declarator, declSpecifierToUse, declaration) // For function *declarations*, we need to update the return types based on the // function type. For function *definitions*, this is done in @@ -518,7 +517,7 @@ class DeclarationHandler(lang: CXXLanguageFrontend) : } if (declaration != null) { - // We also need to set the return type, based on the declrator type. + // We also need to set the return type, based on the declarator type. declaration.type = type // process attributes @@ -544,7 +543,7 @@ class DeclarationHandler(lang: CXXLanguageFrontend) : frontend, frontend.codeOf(ctx), type, - nameDecl.name.toString() + frontend.typeOf(nameDecl.name) ) // Add the declaration to the current scope @@ -574,7 +573,7 @@ class DeclarationHandler(lang: CXXLanguageFrontend) : ) // In C/C++, default enums are of type int - enumConst.type = parseType("int") + enumConst.type = primitiveType("int") // We need to make them visible to the enclosing scope. However, we do NOT // want to add it to the AST of the enclosing scope, but to the AST of the @@ -615,21 +614,11 @@ class DeclarationHandler(lang: CXXLanguageFrontend) : sequence: DeclarationSequence ) { val templateId = typeSpecifier.name as CPPASTTemplateId - val type = parseType(ctx.rawSignature) val templateParams = mutableListOf() - if (type.root !is ObjectType) { - // we cannot continue in this case - return - } - - val objectType = type.root as ObjectType - objectType.generics = emptyList() - for (templateArgument in templateId.templateArguments) { if (templateArgument is CPPASTTypeId) { - val genericInstantiation = parseType(templateArgument.getRawSignature()) - objectType.addGeneric(genericInstantiation) + val genericInstantiation = frontend.typeOf(templateArgument) templateParams.add( newTypeExpression( genericInstantiation.name.toString(), @@ -641,11 +630,12 @@ class DeclarationHandler(lang: CXXLanguageFrontend) : expression?.let { templateParams.add(it) } } } + for (declarator in ctx.declarators) { val declaration = frontend.declaratorHandler.handle(declarator) as ValueDeclaration // Update Type - declaration.type = type + declaration.type = frontend.typeOf(declarator, typeSpecifier) // Set TemplateParameters into VariableDeclaration if (declaration is VariableDeclaration) { diff --git a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/DeclaratorHandler.kt b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/DeclaratorHandler.kt index 08cbc829c2..7ed4122fb5 100644 --- a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/DeclaratorHandler.kt +++ b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/DeclaratorHandler.kt @@ -36,10 +36,8 @@ import de.fraunhofer.aisec.cpg.helpers.Util import java.util.* import java.util.function.Supplier import java.util.regex.Pattern -import java.util.stream.Collectors import org.eclipse.cdt.core.dom.ast.* import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTCompositeTypeSpecifier -import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTCompositeTypeSpecifier.ICPPASTBaseSpecifier import org.eclipse.cdt.core.dom.ast.gnu.cpp.GPPLanguage import org.eclipse.cdt.internal.core.dom.parser.cpp.* @@ -118,7 +116,7 @@ class DeclaratorHandler(lang: CXXLanguageFrontend) : val declaration = newVariableDeclaration( ctx.name.toString(), - newUnknownType(), // Type will be filled out later by + unknownType(), // Type will be filled out later by // handleSimpleDeclaration ctx.rawSignature, implicitInitializerAllowed, @@ -149,7 +147,7 @@ class DeclaratorHandler(lang: CXXLanguageFrontend) : val declaration = newFieldDeclaration( name.localName, - newUnknownType(), + unknownType(), emptyList(), ctx.rawSignature, frontend.locationOf(ctx), @@ -176,17 +174,29 @@ class DeclaratorHandler(lang: CXXLanguageFrontend) : // Retrieve the AST node for the scope we need to put the function in val holder = scope?.astNode - // Check, if it's a constructor. This is the case if the local names of the function and the - // record declaration match val func = - if (holder is RecordDeclaration && name.localName == holder.name.localName) { - newConstructorDeclaration(name, null, holder, ctx) - } else if (scope?.astNode is NamespaceDeclaration) { + when { + // Check, if it's a constructor. This is the case if the local names of the function + // and the + // record declaration match + holder is RecordDeclaration && name.localName == holder.name.localName -> { + newConstructorDeclaration(name, null, holder, ctx) + } + // It's also a constructor, if the name is in the form A::A, and it has no type + // specifier + name.localName == name.parent.toString() && + ((ctx as? IASTFunctionDefinition)?.declSpecifier as? IASTSimpleDeclSpecifier) + ?.type == IASTSimpleDeclSpecifier.t_unspecified -> { + newConstructorDeclaration(name, null, null, ctx) + } // It could also be a scoped function declaration. - newFunctionDeclaration(name, null, ctx) - } else { + scope?.astNode is NamespaceDeclaration -> { + newFunctionDeclaration(name, null, ctx) + } // Otherwise, it's a method to a known or unknown record - newMethodDeclaration(name, null, false, holder as? RecordDeclaration, ctx) + else -> { + newMethodDeclaration(name, null, false, holder as? RecordDeclaration, ctx) + } } // Also make sure to correctly set the scope of the function, regardless where we are in the @@ -331,7 +341,7 @@ class DeclaratorHandler(lang: CXXLanguageFrontend) : // is appended to the original ones. For coherent graph behaviour, we introduce an implicit // declaration that wraps this list if (ctx.takesVarArgs()) { - val varargs = newParamVariableDeclaration("va_args", newUnknownType(), true, "") + val varargs = newParamVariableDeclaration("va_args", unknownType(), true, "") varargs.isImplicit = true varargs.argumentIndex = i frontend.scopeManager.addDeclaration(varargs) @@ -374,9 +384,7 @@ class DeclaratorHandler(lang: CXXLanguageFrontend) : val recordDeclaration = declaration.recordDeclaration // Create a pointer to the class type (if we know it) - val type = - recordDeclaration?.toType()?.reference(PointerType.PointerOrigin.POINTER) - ?: newUnknownType() + val type = recordDeclaration?.toType()?.pointer() ?: unknownType() // Create the receiver. implicitInitializerAllowed must be false, otherwise fixInitializers // will create another implicit constructexpression for this variable, and we don't want @@ -403,7 +411,7 @@ class DeclaratorHandler(lang: CXXLanguageFrontend) : val recordDeclaration = frontend.scopeManager.currentRecord if (recordDeclaration == null) { // variable - result = newVariableDeclaration(name, newUnknownType(), ctx.rawSignature, true) + result = newVariableDeclaration(name, unknownType(), ctx.rawSignature, true) result.initializer = initializer } else { // field @@ -417,7 +425,7 @@ class DeclaratorHandler(lang: CXXLanguageFrontend) : result = newFieldDeclaration( fieldName, - newUnknownType(), + unknownType(), emptyList(), code, frontend.locationOf(ctx), @@ -447,14 +455,10 @@ class DeclaratorHandler(lang: CXXLanguageFrontend) : ctx.rawSignature, ) - // Handle c++ classes + // Handle C++ classes if (ctx is CPPASTCompositeTypeSpecifier) { recordDeclaration.superClasses = - Arrays.stream(ctx.baseSpecifiers) - .map { b: ICPPASTBaseSpecifier -> - TypeParser.createFrom(b.nameSpecifier.toString(), true, frontend) - } - .collect(Collectors.toList()) + ctx.baseSpecifiers.map { objectType(it.nameSpecifier.toString()) }.toMutableList() } frontend.scopeManager.addDeclaration(recordDeclaration) diff --git a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/ExpressionHandler.kt b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/ExpressionHandler.kt index 14002004a0..a4b7597780 100644 --- a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/ExpressionHandler.kt +++ b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/ExpressionHandler.kt @@ -32,7 +32,6 @@ import de.fraunhofer.aisec.cpg.graph.edge.Properties import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge import de.fraunhofer.aisec.cpg.graph.statements.expressions.* import de.fraunhofer.aisec.cpg.graph.types.* -import de.fraunhofer.aisec.cpg.graph.types.PointerType.PointerOrigin import de.fraunhofer.aisec.cpg.helpers.Util import de.fraunhofer.aisec.cpg.passes.CallResolver import java.math.BigInteger @@ -141,19 +140,19 @@ class ExpressionHandler(lang: CXXLanguageFrontend) : // there are a lot of other constants defined for type traits, but they are not really // parsed as type id expressions var operatorCode = "" - var type: Type = newUnknownType() + var type: Type = unknownType() when (ctx.operator) { IASTTypeIdExpression.op_sizeof -> { operatorCode = "sizeof" - type = parseType("std::size_t") + type = objectType("std::size_t") } IASTTypeIdExpression.op_typeid -> { operatorCode = "typeid" - type = parseType("const std::type_info&") + type = objectType("std::type_info").ref() } IASTTypeIdExpression.op_alignof -> { operatorCode = "alignof" - type = parseType("std::size_t") + type = objectType("std::size_t") } IASTTypeIdExpression .op_typeof -> // typeof is not an official c++ keyword - not sure why eclipse @@ -174,14 +173,13 @@ class ExpressionHandler(lang: CXXLanguageFrontend) : } private fun handleNewExpression(ctx: CPPASTNewExpression): Expression { - val name = ctx.typeId.declSpecifier.toString() val code = ctx.rawSignature - val t = TypeParser.createFrom(name, true, frontend) + val t = frontend.typeOf(ctx.typeId) val init = ctx.initializer // we need to check, whether this is an array initialization or a single new expression return if (ctx.isArrayAllocation) { - t.reference(PointerOrigin.ARRAY) + t.array() val arrayMods = (ctx.typeId.abstractDeclarator as IASTArrayDeclarator).arrayModifiers val arrayCreate = newArrayCreationExpression(code) arrayCreate.type = t @@ -194,20 +192,15 @@ class ExpressionHandler(lang: CXXLanguageFrontend) : } arrayCreate } else { - // Resolve possible templates - var templateParameters: List = emptyList() + // new returns a pointer, so we need to reference the type by pointer + val newExpression = newNewExpression(code, t.pointer(), ctx) val declSpecifier = ctx.typeId.declSpecifier as? IASTNamedTypeSpecifier + // Resolve possible templates if (declSpecifier?.name is CPPASTTemplateId) { - templateParameters = getTemplateArguments(declSpecifier.name as CPPASTTemplateId) - assert(t.root is ObjectType) - val objectType = t.root as? ObjectType - val generics = templateParameters.filterIsInstance().map { it.type } - objectType?.generics = generics + newExpression.templateParameters = + getTemplateArguments(declSpecifier.name as CPPASTTemplateId) } - // new returns a pointer, so we need to reference the type by pointer - val newExpression = newNewExpression(code, t.reference(PointerOrigin.POINTER), ctx) - newExpression.templateParameters = templateParameters val initializer: Expression? if (init != null) { initializer = frontend.initializerHandler.handle(init) @@ -295,7 +288,7 @@ class ExpressionHandler(lang: CXXLanguageFrontend) : castExpression.setCastOperator(ctx.operator) castExpression.castType = frontend.typeOf(ctx.typeId) - if (TypeManager.isPrimitive(castExpression.castType, language) || ctx.operator == 4) { + if (isPrimitive(castExpression.castType) || ctx.operator == 4) { castExpression.type = castExpression.castType } else { castExpression.expression.registerTypeListener(castExpression) @@ -324,7 +317,7 @@ class ExpressionHandler(lang: CXXLanguageFrontend) : return newMemberExpression( name, base, - newUnknownType(), + unknownType(), if (ctx.isPointerDereference) "->" else ".", ctx.rawSignature ) @@ -357,8 +350,9 @@ class ExpressionHandler(lang: CXXLanguageFrontend) : val typeName = (ctx.operand as IASTIdExpression).name.toString() if (frontend.typeManager.typeExists(typeName)) { val cast = newCastExpression(frontend.codeOf(ctx)) - cast.castType = parseType(typeName) - cast.expression = input ?: newProblemExpression("could not parse input") + cast.castType = frontend.typeOf((ctx.operand as IASTIdExpression).name) + // The expression member can only be filled by the parent call + // (handleFunctionCallExpression) cast.location = frontend.locationOf(ctx) return cast } @@ -442,6 +436,9 @@ class ExpressionHandler(lang: CXXLanguageFrontend) : } reference is CastExpression -> { // this really is a cast expression in disguise + reference.expression = + handle(ctx.arguments.first()) + ?: ProblemExpression("could not parse argument for cast") return reference } else -> { @@ -468,11 +465,7 @@ class ExpressionHandler(lang: CXXLanguageFrontend) : // this expression could actually be a field / member expression, but somehow CDT only // recognizes them as a member expression if it has an explicit 'this' // TODO: handle this? convert the declared reference expression into a member expression? - return newDeclaredReferenceExpression( - ctx.name.toString(), - newUnknownType(), - ctx.rawSignature - ) + return newDeclaredReferenceExpression(ctx.name.toString(), unknownType(), ctx.rawSignature) } private fun handleExpressionList(exprList: IASTExpressionList): ExpressionList { @@ -542,18 +535,18 @@ class ExpressionHandler(lang: CXXLanguageFrontend) : return when (ctx.kind) { lk_integer_constant -> handleIntegerLiteral(ctx) lk_float_constant -> handleFloatLiteral(ctx) - lk_char_constant -> newLiteral(ctx.value[1], parseType("char"), ctx.rawSignature) + lk_char_constant -> newLiteral(ctx.value[1], primitiveType("char"), ctx.rawSignature) lk_string_literal -> newLiteral( String(ctx.value.slice(IntRange(1, ctx.value.size - 2)).toCharArray()), - parseType("const char[]"), + primitiveType("char").array(), ctx.rawSignature ) lk_this -> handleThisLiteral(ctx) - lk_true -> newLiteral(true, parseType("bool"), ctx.rawSignature) - lk_false -> newLiteral(false, parseType("bool"), ctx.rawSignature) - lk_nullptr -> newLiteral(null, parseType("nullptr_t"), ctx.rawSignature) - else -> newLiteral(String(ctx.value), newUnknownType(), ctx.rawSignature) + lk_true -> newLiteral(true, primitiveType("bool"), ctx.rawSignature) + lk_false -> newLiteral(false, primitiveType("bool"), ctx.rawSignature) + lk_nullptr -> newLiteral(null, objectType("nullptr_t"), ctx.rawSignature) + else -> newLiteral(String(ctx.value), unknownType(), ctx.rawSignature) } } @@ -591,7 +584,7 @@ class ExpressionHandler(lang: CXXLanguageFrontend) : oneLhs = newDeclaredReferenceExpression( des.name.toString(), - newUnknownType(), + unknownType(), des.getRawSignature() ) } @@ -645,7 +638,7 @@ class ExpressionHandler(lang: CXXLanguageFrontend) : oneLhs = newDeclaredReferenceExpression( des.name.toString(), - newUnknownType(), + unknownType(), des.getRawSignature() ) } @@ -759,15 +752,15 @@ class ExpressionHandler(lang: CXXLanguageFrontend) : // retrieve type based on stored Java number val type = - parseType( + primitiveType( when { // we follow the way clang/llvm handles this and this seems to always // be an unsigned long long, except if it is explicitly specified as ul // differentiate between long and long long - numberValue is BigInteger && "ul" == suffix -> "unsigned long" - numberValue is BigInteger -> "unsigned long long" - numberValue is Long && "ll" == suffix -> "long long" - numberValue is Long -> "long" + numberValue is BigInteger && "ul" == suffix -> "unsigned long int" + numberValue is BigInteger -> "unsigned long long int" + numberValue is Long && "ll" == suffix -> "long long int" + numberValue is Long -> "long int" else -> "int" } ) @@ -784,14 +777,15 @@ class ExpressionHandler(lang: CXXLanguageFrontend) : return try { when (suffix) { - "f" -> newLiteral(strippedValue.toFloat(), parseType("float"), ctx.rawSignature) + "f" -> newLiteral(strippedValue.toFloat(), primitiveType("float"), ctx.rawSignature) "l" -> newLiteral( strippedValue.toBigDecimal(), - parseType("long double"), + primitiveType("long double"), ctx.rawSignature ) - else -> newLiteral(strippedValue.toDouble(), parseType("double"), ctx.rawSignature) + else -> + newLiteral(strippedValue.toDouble(), primitiveType("double"), ctx.rawSignature) } } catch (ex: NumberFormatException) { // It could be that we cannot parse the literal, in this case we return an error @@ -807,9 +801,9 @@ class ExpressionHandler(lang: CXXLanguageFrontend) : private fun handleThisLiteral(ctx: IASTLiteralExpression): DeclaredReferenceExpression { // We should be in a record here. However since we are a fuzzy parser, maybe things went // wrong, so we might have an unknown type. - val recordType = frontend.scopeManager.currentRecord?.toType() ?: newUnknownType() + val recordType = frontend.scopeManager.currentRecord?.toType() ?: unknownType() // We do want to make sure that the type of the expression is at least a pointer. - val pointerType = recordType.reference(PointerOrigin.POINTER) + val pointerType = recordType.pointer() return newDeclaredReferenceExpression("this", pointerType, ctx.rawSignature, ctx) } diff --git a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/StatementHandler.kt b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/StatementHandler.kt index 8d4d8cc561..87d5ce18e9 100644 --- a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/StatementHandler.kt +++ b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/StatementHandler.kt @@ -233,7 +233,7 @@ class StatementHandler(lang: CXXLanguageFrontend) : // Adds true expression node where default empty condition evaluates to true, remove here // and in java StatementAnalyzer if (statement.conditionDeclaration == null && statement.condition == null) { - val literal: Literal<*> = newLiteral(true, parseType("bool"), "true") + val literal: Literal<*> = newLiteral(true, primitiveType("bool"), "true") statement.condition = literal } diff --git a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/FunctionPointerCallResolver.kt b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/FunctionPointerCallResolver.kt index 225840df5f..6f5279192e 100644 --- a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/FunctionPointerCallResolver.kt +++ b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/FunctionPointerCallResolver.kt @@ -32,9 +32,9 @@ import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration +import de.fraunhofer.aisec.cpg.graph.pointer import de.fraunhofer.aisec.cpg.graph.statements.expressions.* import de.fraunhofer.aisec.cpg.graph.types.FunctionPointerType -import de.fraunhofer.aisec.cpg.graph.types.PointerType import de.fraunhofer.aisec.cpg.helpers.IdentitySet import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker.ScopedWalker import de.fraunhofer.aisec.cpg.passes.order.DependsOn @@ -127,8 +127,7 @@ class FunctionPointerCallResolver(ctx: TranslationContext) : ComponentPass(ctx) // Even if it is a function declaration, the dataflow might just come from a // situation where the target of a fptr is passed through via a return value. Keep // searching if return type or signature don't match - val functionPointerType = - currentFunction.type.reference(PointerType.PointerOrigin.POINTER) + val functionPointerType = currentFunction.type.pointer() if ( isLambda && currentFunction.returnTypes.isEmpty() && diff --git a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/ScopeManagerCXXTest.kt b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/ScopeManagerCXXTest.kt index d040f58a10..a4155abe96 100644 --- a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/ScopeManagerCXXTest.kt +++ b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/ScopeManagerCXXTest.kt @@ -29,7 +29,6 @@ import de.fraunhofer.aisec.cpg.frontends.TranslationException import de.fraunhofer.aisec.cpg.frontends.cxx.CPPLanguage import de.fraunhofer.aisec.cpg.frontends.cxx.CXXLanguageFrontend import de.fraunhofer.aisec.cpg.graph.* -import de.fraunhofer.aisec.cpg.graph.TypeManager import de.fraunhofer.aisec.cpg.graph.declarations.ConstructorDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration import java.io.File diff --git a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/types/TypeTests.kt b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/types/TypeTests.kt index 736e749bb9..57efa21b86 100644 --- a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/types/TypeTests.kt +++ b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/types/TypeTests.kt @@ -106,214 +106,6 @@ internal class TypeTests : BaseTest() { assertEquals(functionPointerType, functionPointerType.dereference()) } - @Test - fun createFromCPP() { - var result: Type - - with( - CXXLanguageFrontend( - CPPLanguage(), - TranslationContext( - TranslationConfiguration.builder().build(), - ScopeManager(), - TypeManager() - ) - ) - ) { - // Test 1: Function pointer - var typeString = "void (*single_param)(int)" - result = parseType(typeString) - val parameterList = - listOf(IntegerType("int", 32, CPPLanguage(), NumericType.Modifier.SIGNED)) - var expected: Type = FunctionPointerType(parameterList, CPPLanguage(), IncompleteType()) - assertEquals(expected, result) - - // Test 1.1: interleaved brackets in function pointer - typeString = "void ((*single_param)(int))" - result = parseType(typeString) - assertEquals(result, expected) - - // Test 2: Stronger binding of brackets and pointer - typeString = "char (* const a)[]" - result = parseType(typeString) - expected = - PointerType( - PointerType( - IntegerType("char", 8, CPPLanguage(), NumericType.Modifier.NOT_APPLICABLE), - PointerType.PointerOrigin.ARRAY - ), - PointerType.PointerOrigin.POINTER - ) - assertEquals(expected, result) - - // Test 3: Mutable pointer to a mutable char - typeString = "char *p" - result = parseType(typeString) - expected = - PointerType( - IntegerType("char", 8, CPPLanguage(), NumericType.Modifier.NOT_APPLICABLE), - PointerType.PointerOrigin.POINTER - ) - assertEquals(expected, result) - - // Test 3.1: Different Whitespaces - typeString = "char* p" - result = parseType(typeString) - assertEquals(expected, result) - - // Test 3.2: Different Whitespaces - typeString = "char * p" - result = parseType(typeString) - assertEquals(expected, result) - - // Test 4: Mutable pointer to a constant char - typeString = "const char *p;" - result = parseType(typeString) - expected = - PointerType( - IntegerType("char", 8, CPPLanguage(), NumericType.Modifier.NOT_APPLICABLE), - PointerType.PointerOrigin.POINTER - ) - assertEquals(expected, result) - - // Test 5: Constant pointer to a mutable char - typeString = "char * const p;" - result = parseType(typeString) - expected = - PointerType( - IntegerType("char", 8, CPPLanguage(), NumericType.Modifier.NOT_APPLICABLE), - PointerType.PointerOrigin.POINTER - ) - assertEquals(expected, result) - - // Test 6: Constant pointer to a constant char - typeString = "const char * const p;" - result = parseType(typeString) - expected = - PointerType( - IntegerType("char", 8, CPPLanguage(), NumericType.Modifier.NOT_APPLICABLE), - PointerType.PointerOrigin.POINTER - ) - assertEquals(expected, result) - - // Test 7: Array of const pointer to static const char - typeString = "static const char * const somearray []" - result = parseType(typeString) - expected = - PointerType( - PointerType( - IntegerType("char", 8, CPPLanguage(), NumericType.Modifier.NOT_APPLICABLE), - PointerType.PointerOrigin.POINTER - ), - PointerType.PointerOrigin.ARRAY - ) - assertEquals(expected, result) - - // Test 7.1: Array of array of pointer to static const char - typeString = "static const char * somearray[][]" - result = parseType(typeString) - expected = - PointerType( - PointerType( - PointerType( - IntegerType( - "char", - 8, - CPPLanguage(), - NumericType.Modifier.NOT_APPLICABLE - ), - PointerType.PointerOrigin.POINTER - ), - PointerType.PointerOrigin.ARRAY - ), - PointerType.PointerOrigin.ARRAY - ) - assertEquals(expected, result) - - // Test 8: Generics - typeString = "Array array" - result = parseType(typeString) - var generics = mutableListOf() - generics.add(IntegerType("int", 32, CPPLanguage(), NumericType.Modifier.SIGNED)) - expected = ObjectType("Array", generics, false, CPPLanguage()) - assertEquals(expected, result) - - // Test 9: Compound Primitive Types - typeString = "long long int" - result = parseType(typeString) - expected = IntegerType("long long int", 64, CPPLanguage(), NumericType.Modifier.SIGNED) - assertEquals(expected, result) - - // Test 10: Unsigned/Signed Types - typeString = "unsigned int" - result = parseType(typeString) - expected = IntegerType("unsigned int", 32, CPPLanguage(), NumericType.Modifier.UNSIGNED) - assertEquals(expected, result) - typeString = "signed int" - result = parseType(typeString) - expected = IntegerType("int", 32, CPPLanguage(), NumericType.Modifier.SIGNED) - assertEquals(expected, result) - typeString = "A a" - result = parseType(typeString) - expected = ObjectType("A", ArrayList(), false, CPPLanguage()) - assertEquals(expected, result) - - // Test 11: Unsigned + const + compound primitive Types - expected = - IntegerType( - "unsigned long long int", - 64, - CPPLanguage(), - NumericType.Modifier.UNSIGNED - ) - typeString = "const unsigned long long int a = 1" - result = parseType(typeString) - assertEquals(expected, result) - - typeString = "unsigned const long long int b = 1" - result = parseType(typeString) - assertEquals(expected, result) - - typeString = "unsigned long const long int c = 1" - result = parseType(typeString) - assertEquals(expected, result) - - typeString = "unsigned long long const int d = 1" - result = parseType(typeString) - assertEquals(expected, result) - - typeString = "unsigned long long int const e = 1" - result = parseType(typeString) - assertEquals(expected, result) - - // Test 12: C++ Reference Types - typeString = "const int& ref = a" - result = parseType(typeString) - expected = - ReferenceType(IntegerType("int", 32, CPPLanguage(), NumericType.Modifier.SIGNED)) - assertEquals(expected, result) - - typeString = "int const &ref2 = a" - result = parseType(typeString) - assertEquals(expected, result) - - // Test 13: Elaborated Type in Generics - result = parseType("Array") - generics = ArrayList() - var generic = ObjectType("Node", ArrayList(), false, CPPLanguage()) - generics.add(generic) - expected = ObjectType("Array", generics, false, CPPLanguage()) - assertEquals(expected, result) - - result = parseType("Array") - generics = ArrayList() - generic = ObjectType("myclass", ArrayList(), false, CPPLanguage()) - generics.add(generic) - expected = ObjectType("Array", generics, false, CPPLanguage()) - assertEquals(expected, result) - } - } - /** * Test for usage of getTypeStringFromDeclarator to determine function pointer raw type string * @@ -328,16 +120,13 @@ internal class TypeTests : BaseTest() { val noParamType = FunctionPointerType(emptyList(), CPPLanguage(), IncompleteType()) val oneParamType = FunctionPointerType( - listOf(IntegerType("int", 32, CPPLanguage(), NumericType.Modifier.SIGNED)), + listOf(tu.primitiveType("int")), CPPLanguage(), IncompleteType() ) val twoParamType = FunctionPointerType( - listOf( - IntegerType("int", 32, CPPLanguage(), NumericType.Modifier.SIGNED), - IntegerType("unsigned long", 64, CPPLanguage(), NumericType.Modifier.UNSIGNED) - ), + listOf(tu.primitiveType("int"), tu.primitiveType("unsigned long int")), CPPLanguage(), IntegerType("int", 32, CPPLanguage(), NumericType.Modifier.SIGNED) ) @@ -383,12 +172,12 @@ internal class TypeTests : BaseTest() { val topLevel = Path.of("src", "test", "resources", "compiling", "hierarchy", "multistep") val result = analyze("simple_inheritance.cpp", topLevel, true) - val root = parseType("Root") - val level0 = parseType("Level0") - val level1 = parseType("Level1") - val level1b = parseType("Level1B") - val level2 = parseType("Level2") - val unrelated = parseType("Unrelated") + val root = objectType("Root") + val level0 = objectType("Level0") + val level1 = objectType("Level1") + val level1b = objectType("Level1B") + val level2 = objectType("Level2") + val unrelated = objectType("Unrelated") getCommonTypeTestGeneral(root, level0, level1, level1b, level2, unrelated, result) } } @@ -411,14 +200,14 @@ internal class TypeTests : BaseTest() { Path.of("src", "test", "resources", "compiling", "hierarchy", "multistep") val result = analyze("multi_inheritance.cpp", topLevel, true) - val root = parseType("Root") - val level0 = parseType("Level0") - val level0b = parseType("Level0B") - val level1 = parseType("Level1") - val level1b = parseType("Level1B") - val level1c = parseType("Level1C") - val level2 = parseType("Level2") - val level2b = parseType("Level2B") + val root = objectType("Root") + val level0 = objectType("Level0") + val level0b = objectType("Level0B") + val level1 = objectType("Level1") + val level1b = objectType("Level1B") + val level1c = objectType("Level1C") + val level2 = objectType("Level2") + val level2b = objectType("Level2B") val typeManager = result.finalCtx.typeManager /* diff --git a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXIncludeTest.kt b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXIncludeTest.kt index 11b649f78c..0e4a987c74 100644 --- a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXIncludeTest.kt +++ b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXIncludeTest.kt @@ -31,6 +31,7 @@ import de.fraunhofer.aisec.cpg.TestUtils.analyzeWithBuilder import de.fraunhofer.aisec.cpg.TranslationConfiguration import de.fraunhofer.aisec.cpg.graph.declarations.* import de.fraunhofer.aisec.cpg.graph.get +import de.fraunhofer.aisec.cpg.graph.methods import de.fraunhofer.aisec.cpg.graph.records import de.fraunhofer.aisec.cpg.graph.statements.ReturnStatement import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression @@ -240,7 +241,7 @@ internal class CXXIncludeTest : BaseTest() { @Test @Throws(Exception::class) - fun testLoadIncludes() { + fun testLoadIncludesDisabled() { val file = File("src/test/resources/include.cpp") val tus = analyzeWithBuilder( @@ -254,8 +255,15 @@ internal class CXXIncludeTest : BaseTest() { ) assertNotNull(tus) + val tu = tus.firstOrNull() + assertNotNull(tu) + // the tu should not contain any classes, since they are defined in the header (which are - // not loaded) - assertTrue(tus.firstOrNull().records.isEmpty()) + // not loaded) and inference is off. + assertTrue(tu.records.isEmpty()) + + // however, we should still have two methods (one of which is a constructor declaration) + assertEquals(2, tu.methods.size) + assertEquals(1, tu.methods.filterIsInstance().size) } } diff --git a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXLanguageFrontendTest.kt b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXLanguageFrontendTest.kt index 088206af73..d5fd09b57b 100644 --- a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXLanguageFrontendTest.kt +++ b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXLanguageFrontendTest.kt @@ -25,21 +25,20 @@ */ package de.fraunhofer.aisec.cpg.frontends.cxx -import de.fraunhofer.aisec.cpg.BaseTest +import de.fraunhofer.aisec.cpg.* import de.fraunhofer.aisec.cpg.InferenceConfiguration.Companion.builder import de.fraunhofer.aisec.cpg.TestUtils.analyze import de.fraunhofer.aisec.cpg.TestUtils.analyzeAndGetFirstTU import de.fraunhofer.aisec.cpg.TestUtils.analyzeWithBuilder import de.fraunhofer.aisec.cpg.TestUtils.assertInvokes import de.fraunhofer.aisec.cpg.TestUtils.assertRefersTo -import de.fraunhofer.aisec.cpg.TranslationConfiguration -import de.fraunhofer.aisec.cpg.assertFullName -import de.fraunhofer.aisec.cpg.assertLocalName 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.statements.expressions.* import de.fraunhofer.aisec.cpg.graph.types.* +import de.fraunhofer.aisec.cpg.graph.types.PointerType.PointerOrigin.ARRAY +import de.fraunhofer.aisec.cpg.graph.types.PointerType.PointerOrigin.POINTER import de.fraunhofer.aisec.cpg.passes.* import de.fraunhofer.aisec.cpg.processing.IVisitor import de.fraunhofer.aisec.cpg.processing.strategy.Strategy @@ -63,7 +62,7 @@ internal class CXXLanguageFrontendTest : BaseTest() { val decl = main.iterator().next() val ls = decl.variables["ls"] assertNotNull(ls) - assertEquals(tu.parseType("std::vector", true), ls.type) + assertEquals(tu.objectType("std::vector", listOf(tu.objectType("int"))), ls.type) assertLocalName("ls", ls) val forEachStatement = decl.getBodyStatementAs(1, ForEachStatement::class.java) @@ -123,33 +122,36 @@ internal class CXXLanguageFrontendTest : BaseTest() { val file = File("src/test/resources/typeidexpr.cpp") val tu = analyzeAndGetFirstTU(listOf(file), file.parentFile.toPath(), true) val main = tu.getDeclarationsByName("main", FunctionDeclaration::class.java) - assertNotNull(main) + with(tu) { + assertNotNull(main) - val funcDecl = main.iterator().next() - val i = funcDecl.variables["i"] - assertNotNull(i) + val funcDecl = main.iterator().next() + val i = funcDecl.variables["i"] + assertNotNull(i) - val sizeof = i.initializer as? TypeIdExpression - assertNotNull(sizeof) - assertLocalName("sizeof", sizeof) - assertEquals(tu.parseType("std::size_t", true), sizeof.type) + val sizeof = i.initializer as? TypeIdExpression + assertNotNull(sizeof) + assertLocalName("sizeof", sizeof) + assertEquals(tu.objectType("std::size_t"), sizeof.type) - val typeInfo = funcDecl.variables["typeInfo"] - assertNotNull(typeInfo) + val typeInfo = funcDecl.variables["typeInfo"] + assertNotNull(typeInfo) - val typeid = typeInfo.initializer as? TypeIdExpression - assertNotNull(typeid) - assertLocalName("typeid", typeid) - assertEquals(tu.parseType("const std::type_info&", true), typeid.type) + val typeid = typeInfo.initializer as? TypeIdExpression + assertNotNull(typeid) + assertLocalName("typeid", typeid) - val j = funcDecl.variables["j"] - assertNotNull(j) + assertEquals(objectType("std::type_info").ref(), typeid.type) - val alignOf = j.initializer as? TypeIdExpression - assertNotNull(sizeof) - assertNotNull(alignOf) - assertLocalName("alignof", alignOf) - assertEquals(tu.parseType("std::size_t", true), alignOf.type) + val j = funcDecl.variables["j"] + assertNotNull(j) + + val alignOf = j.initializer as? TypeIdExpression + assertNotNull(sizeof) + assertNotNull(alignOf) + assertLocalName("alignof", alignOf) + assertEquals(tu.objectType("std::size_t"), alignOf.type) + } } @Test @@ -157,44 +159,48 @@ internal class CXXLanguageFrontendTest : BaseTest() { fun testCast() { val file = File("src/test/resources/components/castexpr.cpp") val tu = analyzeAndGetFirstTU(listOf(file), file.parentFile.toPath(), true) - val main = tu.getDeclarationAs(0, FunctionDeclaration::class.java) - val e = - Objects.requireNonNull(main!!.getBodyStatementAs(0, DeclarationStatement::class.java)) - ?.singleDeclaration as VariableDeclaration - assertNotNull(e) - assertEquals(tu.parseType("ExtendedClass*", true), e.type) - - val b = - Objects.requireNonNull(main.getBodyStatementAs(1, DeclarationStatement::class.java)) - ?.singleDeclaration as VariableDeclaration - assertNotNull(b) - assertEquals(tu.parseType("BaseClass*", true), b.type) - - // initializer - var cast = b.initializer as? CastExpression - assertNotNull(cast) - assertEquals(tu.parseType("BaseClass*", true), cast.castType) - - val staticCast = main.getBodyStatementAs(2, BinaryOperator::class.java) - assertNotNull(staticCast) - cast = staticCast.rhs as CastExpression - assertNotNull(cast) - assertLocalName("static_cast", cast) - - val reinterpretCast = main.getBodyStatementAs(3, BinaryOperator::class.java) - assertNotNull(reinterpretCast) - cast = reinterpretCast.rhs as CastExpression - assertNotNull(cast) - assertLocalName("reinterpret_cast", cast) - - val d = - Objects.requireNonNull(main.getBodyStatementAs(4, DeclarationStatement::class.java)) - ?.singleDeclaration as VariableDeclaration - assertNotNull(d) - - cast = d.initializer as? CastExpression - assertNotNull(cast) - assertEquals(tu.parseType("int", true), cast.castType) + with(tu) { + val main = tu.getDeclarationAs(0, FunctionDeclaration::class.java) + val e = + Objects.requireNonNull( + main!!.getBodyStatementAs(0, DeclarationStatement::class.java) + ) + ?.singleDeclaration as VariableDeclaration + assertNotNull(e) + assertEquals(objectType("ExtendedClass").pointer(), e.type) + + val b = + Objects.requireNonNull(main.getBodyStatementAs(1, DeclarationStatement::class.java)) + ?.singleDeclaration as VariableDeclaration + assertNotNull(b) + assertEquals(objectType("BaseClass").pointer(), b.type) + + // initializer + var cast = b.initializer as? CastExpression + assertNotNull(cast) + assertEquals(objectType("BaseClass").pointer(), cast.castType) + + val staticCast = main.getBodyStatementAs(2, BinaryOperator::class.java) + assertNotNull(staticCast) + cast = staticCast.rhs as CastExpression + assertNotNull(cast) + assertLocalName("static_cast", cast) + + val reinterpretCast = main.getBodyStatementAs(3, BinaryOperator::class.java) + assertNotNull(reinterpretCast) + cast = reinterpretCast.rhs as CastExpression + assertNotNull(cast) + assertLocalName("reinterpret_cast", cast) + + val d = + Objects.requireNonNull(main.getBodyStatementAs(4, DeclarationStatement::class.java)) + ?.singleDeclaration as VariableDeclaration + assertNotNull(d) + + cast = d.initializer as? CastExpression + assertNotNull(cast) + assertEquals(primitiveType("int"), cast.castType) + } } @Test @@ -203,47 +209,47 @@ internal class CXXLanguageFrontendTest : BaseTest() { val file = File("src/test/resources/cxx/arrays.cpp") val tu = analyzeAndGetFirstTU(listOf(file), file.parentFile.toPath(), true) val main = tu.byNameOrNull("main") - assertNotNull(main) - assertNotNull(main) - - val statement = main.body as CompoundStatement - - // first statement is the variable declaration - val x = - (statement.statements[0] as DeclarationStatement).singleDeclaration - as VariableDeclaration - assertNotNull(x) - assertEquals(tu.parseType("int[]", true), x.type) - - // initializer is an initializer list expression - val ile = x.initializer as? InitializerListExpression - assertNotNull(ile) - - val initializers = ile.initializers - assertNotNull(initializers) - assertEquals(3, initializers.size) - - // second statement is an expression directly - val ase = statement.statements[1] as ArraySubscriptionExpression - assertNotNull(ase) - assertEquals(x, (ase.arrayExpression as DeclaredReferenceExpression).refersTo) - assertEquals(0, (ase.subscriptExpression as Literal<*>).value) - - // third statement declares a pointer to an array - val a = - (statement.statements[2] as? DeclarationStatement)?.singleDeclaration - as? VariableDeclaration - assertNotNull(a) - - val type = a.type - assertTrue(type is PointerType && type.pointerOrigin == PointerType.PointerOrigin.POINTER) + with(tu) { + assertNotNull(main) + + val statement = main.body as CompoundStatement + + // first statement is the variable declaration + val x = + (statement.statements[0] as DeclarationStatement).singleDeclaration + as VariableDeclaration + assertNotNull(x) + assertEquals(primitiveType("int").array(), x.type) + + // initializer is an initializer list expression + val ile = x.initializer as? InitializerListExpression + assertNotNull(ile) + + val initializers = ile.initializers + assertNotNull(initializers) + assertEquals(3, initializers.size) + + // second statement is an expression directly + val ase = statement.statements[1] as ArraySubscriptionExpression + assertNotNull(ase) + assertEquals(x, (ase.arrayExpression as DeclaredReferenceExpression).refersTo) + assertEquals(0, (ase.subscriptExpression as Literal<*>).value) + + // third statement declares a pointer to an array + val a = + (statement.statements[2] as? DeclarationStatement)?.singleDeclaration + as? VariableDeclaration + assertNotNull(a) + + val type = a.type + assertTrue( + type is PointerType && type.pointerOrigin == PointerType.PointerOrigin.POINTER + ) - val elementType = (a.type as? PointerType)?.elementType - assertNotNull(elementType) - assertTrue( - elementType is PointerType && - elementType.pointerOrigin == PointerType.PointerOrigin.ARRAY - ) + val elementType = (a.type as? PointerType)?.elementType + assertNotNull(elementType) + assertTrue(elementType is PointerType && elementType.pointerOrigin == ARRAY) + } } @Test @@ -415,85 +421,88 @@ internal class CXXLanguageFrontendTest : BaseTest() { fun testDeclarationStatement() { val file = File("src/test/resources/cxx/declstmt.cpp") val tu = analyzeAndGetFirstTU(listOf(file), file.parentFile.toPath(), true) - val function = tu.getDeclarationAs(0, FunctionDeclaration::class.java) - val statements = function?.statements - assertNotNull(statements) - statements.forEach( - Consumer { node: Statement -> - log.debug("{}", node) - assertTrue( - node is DeclarationStatement || - statements.indexOf(node) == statements.size - 1 && node is ReturnStatement - ) - } - ) - - val declFromMultiplicateExpression = - (statements[0] as DeclarationStatement).getSingleDeclarationAs( - VariableDeclaration::class.java + with(tu) { + val function = tu.getDeclarationAs(0, FunctionDeclaration::class.java) + val statements = function?.statements + assertNotNull(statements) + statements.forEach( + Consumer { node: Statement -> + log.debug("{}", node) + assertTrue( + node is DeclarationStatement || + statements.indexOf(node) == statements.size - 1 && + node is ReturnStatement + ) + } ) - assertEquals(tu.parseType("SSL_CTX*", true), declFromMultiplicateExpression.type) - assertLocalName("ptr", declFromMultiplicateExpression) - val withInitializer = - (statements[1] as DeclarationStatement).getSingleDeclarationAs( - VariableDeclaration::class.java - ) - var initializer = withInitializer.initializer - assertNotNull(initializer) - assertTrue(initializer is Literal<*>) - assertEquals(1, initializer.value) - - val twoDeclarations = statements[2].declarations - assertEquals(2, twoDeclarations.size) - - val b = twoDeclarations[0] as VariableDeclaration - assertNotNull(b) - assertLocalName("b", b) - assertEquals(tu.parseType("int*", false), b.type) - - val c = twoDeclarations[1] as VariableDeclaration - assertNotNull(c) - assertLocalName("c", c) - assertEquals(tu.parseType("int", false), c.type) + val declFromMultiplicateExpression = + (statements[0] as DeclarationStatement).getSingleDeclarationAs( + VariableDeclaration::class.java + ) + assertEquals(objectType("SSL_CTX").pointer(), declFromMultiplicateExpression.type) + assertLocalName("ptr", declFromMultiplicateExpression) - val withoutInitializer = - (statements[3] as DeclarationStatement).getSingleDeclarationAs( - VariableDeclaration::class.java - ) - initializer = withoutInitializer.initializer - assertEquals(tu.parseType("int*", true), withoutInitializer.type) - assertLocalName("d", withoutInitializer) - assertNull(initializer) - - val qualifiedType = - (statements[4] as DeclarationStatement).getSingleDeclarationAs( - VariableDeclaration::class.java - ) - assertEquals(tu.parseType("std::string", true), qualifiedType.type) - assertLocalName("text", qualifiedType) - assertTrue(qualifiedType.initializer is Literal<*>) - assertEquals("some text", (qualifiedType.initializer as? Literal<*>)?.value) - - val pointerWithAssign = - (statements[5] as DeclarationStatement).getSingleDeclarationAs( - VariableDeclaration::class.java - ) - assertEquals(tu.parseType("void*", true), pointerWithAssign.type) - assertLocalName("ptr2", pointerWithAssign) - assertLocalName("NULL", pointerWithAssign.initializer) + val withInitializer = + (statements[1] as DeclarationStatement).getSingleDeclarationAs( + VariableDeclaration::class.java + ) + var initializer = withInitializer.initializer + assertNotNull(initializer) + assertTrue(initializer is Literal<*>) + assertEquals(1, initializer.value) + + val twoDeclarations = statements[2].declarations + assertEquals(2, twoDeclarations.size) + + val b = twoDeclarations[0] as VariableDeclaration + assertNotNull(b) + assertLocalName("b", b) + assertEquals(primitiveType("int").reference(POINTER), b.type) + + val c = twoDeclarations[1] as VariableDeclaration + assertNotNull(c) + assertLocalName("c", c) + assertEquals(primitiveType("int"), c.type) + + val withoutInitializer = + (statements[3] as DeclarationStatement).getSingleDeclarationAs( + VariableDeclaration::class.java + ) + initializer = withoutInitializer.initializer + assertEquals(primitiveType("int").reference(POINTER), withoutInitializer.type) + assertLocalName("d", withoutInitializer) + assertNull(initializer) + + val qualifiedType = + (statements[4] as DeclarationStatement).getSingleDeclarationAs( + VariableDeclaration::class.java + ) + assertEquals(objectType("std::string"), qualifiedType.type) + assertLocalName("text", qualifiedType) + assertTrue(qualifiedType.initializer is Literal<*>) + assertEquals("some text", (qualifiedType.initializer as? Literal<*>)?.value) + + val pointerWithAssign = + (statements[5] as DeclarationStatement).getSingleDeclarationAs( + VariableDeclaration::class.java + ) + assertEquals(incompleteType().reference(POINTER), pointerWithAssign.type) + assertLocalName("ptr2", pointerWithAssign) + assertLocalName("NULL", pointerWithAssign.initializer) - val classWithVariable = statements[6].declarations - assertEquals(2, classWithVariable.size) + val classWithVariable = statements[6].declarations + assertEquals(2, classWithVariable.size) - val classA = classWithVariable[0] as RecordDeclaration - assertNotNull(classA) - assertLocalName("A", classA) + val classA = classWithVariable[0] as RecordDeclaration + assertNotNull(classA) + assertLocalName("A", classA) - val myA = classWithVariable[1] as VariableDeclaration - assertNotNull(myA) - assertLocalName("myA", myA) - assertEquals(classA, (myA.type as ObjectType).recordDeclaration) + val myA = classWithVariable[1] as VariableDeclaration + assertNotNull(myA) + assertLocalName("myA", myA) + assertEquals(classA, (myA.type as ObjectType).recordDeclaration) + } } @Test @@ -657,7 +666,7 @@ internal class CXXLanguageFrontendTest : BaseTest() { // syntactically no different than the previous ones val stmt = statements[4] as DeclarationStatement val decl = stmt.singleDeclaration as VariableDeclaration - assertEquals(tu.parseType("std::string*", true), decl.type) + with(tu) { assertEquals(objectType("std::string").pointer(), decl.type) } assertLocalName("notMultiplication", decl) assertTrue(decl.initializer is BinaryOperator) @@ -686,7 +695,7 @@ internal class CXXLanguageFrontendTest : BaseTest() { val constant = recordDeclaration.fields["CONSTANT"] assertNotNull(constant) - assertEquals(tu.parseType("void*", true), field.type) + assertEquals(tu.incompleteType().reference(POINTER), field.type) assertEquals(3, recordDeclaration.methods.size) val method = recordDeclaration.methods[0] @@ -704,12 +713,12 @@ internal class CXXLanguageFrontendTest : BaseTest() { val methodWithParam = recordDeclaration.methods[1] assertLocalName("method", methodWithParam) assertEquals(1, methodWithParam.parameters.size) - assertEquals(tu.parseType("int", true), methodWithParam.parameters[0].type) + assertEquals(tu.primitiveType("int"), methodWithParam.parameters[0].type) assertEquals( FunctionType( "(int)void*", - listOf(tu.parseType("int", true)), - listOf(tu.parseType("void*", true)), + listOf(tu.primitiveType("int")), + listOf(tu.incompleteType().reference(POINTER)), CPPLanguage() ), methodWithParam.type @@ -725,7 +734,12 @@ internal class CXXLanguageFrontendTest : BaseTest() { val inlineMethod = recordDeclaration.methods[2] assertLocalName("inlineMethod", inlineMethod) assertEquals( - FunctionType("()void*", listOf(), listOf(tu.parseType("void*", true)), CPPLanguage()), + FunctionType( + "()void*", + listOf(), + listOf(tu.incompleteType().reference(POINTER)), + CPPLanguage() + ), inlineMethod.type ) assertTrue(inlineMethod.hasBody()) @@ -736,7 +750,7 @@ internal class CXXLanguageFrontendTest : BaseTest() { FunctionType( "()SomeClass", listOf(), - listOf(tu.parseType("SomeClass", true)), + listOf(tu.objectType("SomeClass")), CPPLanguage() ), inlineConstructor.type @@ -746,12 +760,12 @@ internal class CXXLanguageFrontendTest : BaseTest() { val constructorDefinition = tu.getDeclarationAs(3, ConstructorDeclaration::class.java) assertNotNull(constructorDefinition) assertEquals(1, constructorDefinition.parameters.size) - assertEquals(tu.parseType("int", true), constructorDefinition.parameters[0].type) + assertEquals(tu.primitiveType("int"), constructorDefinition.parameters[0].type) assertEquals( FunctionType( "(int)SomeClass", - listOf(tu.parseType("int", false)), - listOf(tu.parseType("SomeClass", true)), + listOf(tu.primitiveType("int")), + listOf(tu.objectType("SomeClass")), CPPLanguage() ), constructorDefinition.type @@ -841,7 +855,7 @@ internal class CXXLanguageFrontendTest : BaseTest() { val hex = tu.variables["hex"] assertNotNull(hex) assertIs(hex.type) - assertLocalName("unsigned long long", hex.type) + assertLocalName("unsigned long long int", hex.type) val duration_ms = tu.variables["duration_ms"] assertNotNull(duration_ms) @@ -877,7 +891,8 @@ internal class CXXLanguageFrontendTest : BaseTest() { // int z[] = { 2, 3, 4 }; val z = tu.getDeclarationAs(2, VariableDeclaration::class.java) - assertEquals(tu.parseType("int[]", true), z!!.type) + assertNotNull(z) + with(tu) { assertEquals(primitiveType("int").array(), z.type) } initializer = z.initializer assertNotNull(initializer) @@ -893,53 +908,54 @@ internal class CXXLanguageFrontendTest : BaseTest() { val file = File("src/test/resources/cxx/objcreation.cpp") val tu = analyzeAndGetFirstTU(listOf(file), file.parentFile.toPath(), true) assertNotNull(tu) - - // get the main method - val main = tu.getDeclarationAs(3, FunctionDeclaration::class.java) - val statement = main!!.body as CompoundStatement - - // Integer i - val i = - (statement.statements[0] as DeclarationStatement).singleDeclaration - as VariableDeclaration - // type should be Integer - assertEquals(tu.parseType("Integer", true), i.type) - - // initializer should be a construct expression - var constructExpression = i.initializer as? ConstructExpression - assertNotNull(constructExpression) - // type of the construct expression should also be Integer - assertEquals(tu.parseType("Integer", true), constructExpression.type) - - // auto (Integer) m - val m = - (statement.statements[6] as DeclarationStatement).singleDeclaration - as VariableDeclaration - // type should be Integer* - assertEquals(tu.parseType("Integer*", true), m.type) - - val constructor = constructExpression.constructor - assertNotNull(constructor) - assertLocalName("Integer", constructor) - assertFalse(constructor.isImplicit) - - // initializer should be a new expression - val newExpression = m.initializer as? NewExpression - assertNotNull(newExpression) - // type of the new expression should also be Integer* - assertEquals(tu.parseType("Integer*", true), newExpression.type) - - // initializer should be a construct expression - constructExpression = newExpression.initializer as? ConstructExpression - assertNotNull(constructExpression) - // type of the construct expression should be Integer - assertEquals(tu.parseType("Integer", true), constructExpression.type) - - // argument should be named k and of type m - val k = constructExpression.arguments[0] as DeclaredReferenceExpression - assertLocalName("k", k) - // type of the construct expression should also be Integer - assertEquals(tu.parseType("int", true), k.type) + with(tu) { + // get the main method + val main = tu.getDeclarationAs(3, FunctionDeclaration::class.java) + val statement = main!!.body as CompoundStatement + + // Integer i + val i = + (statement.statements[0] as DeclarationStatement).singleDeclaration + as VariableDeclaration + // type should be Integer + assertEquals(tu.objectType("Integer"), i.type) + + // initializer should be a construct expression + var constructExpression = i.initializer as? ConstructExpression + assertNotNull(constructExpression) + // type of the construct expression should also be Integer + assertEquals(tu.objectType("Integer"), constructExpression.type) + + // auto (Integer) m + val m = + (statement.statements[6] as DeclarationStatement).singleDeclaration + as VariableDeclaration + // type should be Integer* + assertEquals(objectType("Integer").pointer(), m.type) + + val constructor = constructExpression.constructor + assertNotNull(constructor) + assertLocalName("Integer", constructor) + assertFalse(constructor.isImplicit) + + // initializer should be a new expression + val newExpression = m.initializer as? NewExpression + assertNotNull(newExpression) + // type of the new expression should also be Integer* + assertEquals(objectType("Integer").pointer(), newExpression.type) + + // initializer should be a construct expression + constructExpression = newExpression.initializer as? ConstructExpression + assertNotNull(constructExpression) + // type of the construct expression should be Integer + assertEquals(objectType("Integer"), constructExpression.type) + + // argument should be named k and of type m + val k = constructExpression.arguments[0] as DeclaredReferenceExpression + assertLocalName("k", k) + // type of the construct expression should also be Integer + assertEquals(primitiveType("int"), k.type) + } } private val FunctionDeclaration.statements: List? @@ -1241,6 +1257,7 @@ internal class CXXLanguageFrontendTest : BaseTest() { assertNotNull(initializer) assertTrue(initializer is CastExpression) assertLocalName("size_t", initializer.castType) + assertLiteralValue(42, initializer.expression) } @Test @@ -1547,4 +1564,14 @@ internal class CXXLanguageFrontendTest : BaseTest() { val tu = analyzeAndGetFirstTU(listOf(file), file.parentFile.toPath(), true) assertNotNull(tu) } + + @Test + @Throws(Exception::class) + fun testCFunctionReturnType() { + val file = File("src/test/resources/c/types.c") + val tu = analyzeAndGetFirstTU(listOf(file), file.parentFile.toPath(), true) + assertNotNull(tu) + + assertLocalName("int", tu.functions["main"]?.returnTypes?.firstOrNull()) + } } diff --git a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXLiteralTest.kt b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXLiteralTest.kt index 3f4634989d..d3761416d7 100644 --- a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXLiteralTest.kt +++ b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXLiteralTest.kt @@ -53,12 +53,12 @@ internal class CXXLiteralTest : BaseTest() { val funcDecl = zero.iterator().next() assertLocalName("zero", funcDecl) - assertLiteral(0, tu.parseType("int"), funcDecl, "i") - assertLiteral(0L, tu.parseType("long"), funcDecl, "l_with_suffix") - assertLiteral(0L, tu.parseType("long long"), funcDecl, "l_long_long_with_suffix") + assertLiteral(0, tu.primitiveType("int"), funcDecl, "i") + assertLiteral(0L, tu.primitiveType("long int"), funcDecl, "l_with_suffix") + assertLiteral(0L, tu.primitiveType("long long int"), funcDecl, "l_long_long_with_suffix") assertLiteral( BigInteger.valueOf(0), - tu.parseType("unsigned long long"), + tu.primitiveType("unsigned long long int"), funcDecl, "l_unsigned_long_long_with_suffix" ) @@ -73,31 +73,31 @@ internal class CXXLiteralTest : BaseTest() { assertFalse(decimal.isEmpty()) val funcDecl = decimal.iterator().next() assertLocalName("decimal", funcDecl) - assertLiteral(42, tu.parseType("int"), funcDecl, "i") - assertLiteral(1000, tu.parseType("int"), funcDecl, "i_with_literal") - assertLiteral(9223372036854775807L, tu.parseType("long"), funcDecl, "l") - assertLiteral(9223372036854775807L, tu.parseType("long"), funcDecl, "l_with_suffix") + assertLiteral(42, tu.primitiveType("int"), funcDecl, "i") + assertLiteral(1000, tu.primitiveType("int"), funcDecl, "i_with_literal") + assertLiteral(9223372036854775807L, tu.primitiveType("long int"), funcDecl, "l") + assertLiteral(9223372036854775807L, tu.primitiveType("long int"), funcDecl, "l_with_suffix") assertLiteral( 9223372036854775807L, - tu.parseType("long long"), + tu.primitiveType("long long int"), funcDecl, "l_long_long_with_suffix" ) assertLiteral( BigInteger("9223372036854775809"), - tu.parseType("unsigned long"), + tu.primitiveType("unsigned long int"), funcDecl, "l_unsigned_long_with_suffix" ) assertLiteral( BigInteger("9223372036854775808"), - tu.parseType("unsigned long long"), + tu.primitiveType("unsigned long long int"), funcDecl, "l_long_long_implicit" ) assertLiteral( BigInteger("9223372036854775809"), - tu.parseType("unsigned long long"), + tu.primitiveType("unsigned long long int"), funcDecl, "l_unsigned_long_long_with_suffix" ) @@ -112,11 +112,11 @@ internal class CXXLiteralTest : BaseTest() { assertFalse(octal.isEmpty()) val funcDecl = octal.iterator().next() assertLocalName("octal", funcDecl) - assertLiteral(42, tu.parseType("int"), funcDecl, "i") - assertLiteral(42L, tu.parseType("long"), funcDecl, "l_with_suffix") + assertLiteral(42, tu.primitiveType("int"), funcDecl, "i") + assertLiteral(42L, tu.primitiveType("long int"), funcDecl, "l_with_suffix") assertLiteral( BigInteger.valueOf(42), - tu.parseType("unsigned long long"), + tu.primitiveType("unsigned long long int"), funcDecl, "l_unsigned_long_long_with_suffix" ) @@ -132,11 +132,11 @@ internal class CXXLiteralTest : BaseTest() { assertFalse(hex.isEmpty()) val funcDecl = hex.iterator().next() assertLocalName("hex", funcDecl) - assertLiteral(42, tu.parseType("int"), funcDecl, "i") - assertLiteral(42L, tu.parseType("long"), funcDecl, "l_with_suffix") + assertLiteral(42, tu.primitiveType("int"), funcDecl, "i") + assertLiteral(42L, tu.primitiveType("long int"), funcDecl, "l_with_suffix") assertLiteral( BigInteger.valueOf(42), - tu.parseType("unsigned long long"), + tu.primitiveType("unsigned long long int"), funcDecl, "l_unsigned_long_long_with_suffix" ) diff --git a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXSymbolConfigurationTest.kt b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXSymbolConfigurationTest.kt index 1270cac616..8c9b894742 100644 --- a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXSymbolConfigurationTest.kt +++ b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXSymbolConfigurationTest.kt @@ -26,8 +26,8 @@ package de.fraunhofer.aisec.cpg.frontends.cxx import de.fraunhofer.aisec.cpg.* +import de.fraunhofer.aisec.cpg.TypeManager import de.fraunhofer.aisec.cpg.frontends.TranslationException -import de.fraunhofer.aisec.cpg.graph.TypeManager import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration import de.fraunhofer.aisec.cpg.graph.statements.expressions.BinaryOperator import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression diff --git a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolverTest.kt b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolverTest.kt index 51056da5b5..21b9b6eaac 100644 --- a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolverTest.kt +++ b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolverTest.kt @@ -42,6 +42,7 @@ import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.CastExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.Literal +import de.fraunhofer.aisec.cpg.graph.types.PointerType import de.fraunhofer.aisec.cpg.graph.types.Type import de.fraunhofer.aisec.cpg.graph.types.UnknownType import java.io.File @@ -158,8 +159,8 @@ class CallResolverTest : BaseTest() { val records = result.records - val intType = tu.parseType("int") - val stringType = tu.parseType("char*") + val intType = tu.primitiveType("int") + val stringType = tu.primitiveType("char").reference(PointerType.PointerOrigin.POINTER) testMethods(records, intType, stringType) testOverriding(records) diff --git a/cpg-language-cxx/src/test/resources/c/types.c b/cpg-language-cxx/src/test/resources/c/types.c new file mode 100644 index 0000000000..910affb82c --- /dev/null +++ b/cpg-language-cxx/src/test/resources/c/types.c @@ -0,0 +1,5 @@ +// C allows to omit the type specifier, which then defaults to int. +// However, usually a compiler will at least emit a warning here. +main() { + return 1; +} \ No newline at end of file diff --git a/cpg-language-go/src/main/golang/basic_types.go b/cpg-language-go/src/main/golang/basic_types.go index 3325260572..b302ff2134 100644 --- a/cpg-language-go/src/main/golang/basic_types.go +++ b/cpg-language-go/src/main/golang/basic_types.go @@ -1,28 +1,29 @@ -/* - * Copyright (c) 2021, 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. - * - * $$$$$$\ $$$$$$$\ $$$$$$\ - * $$ __$$\ $$ __$$\ $$ __$$\ - * $$ / \__|$$ | $$ |$$ / \__| - * $$ | $$$$$$$ |$$ |$$$$\ - * $$ | $$ ____/ $$ |\_$$ | - * $$ | $$\ $$ | $$ | $$ | - * \$$$$$ |$$ | \$$$$$ | - * \______/ \__| \______/ - * - */ +// +// Copyright (c) 2021, 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 cpg import ( diff --git a/cpg-language-go/src/main/golang/declarations.go b/cpg-language-go/src/main/golang/declarations.go index caadcced29..13d64c2efe 100644 --- a/cpg-language-go/src/main/golang/declarations.go +++ b/cpg-language-go/src/main/golang/declarations.go @@ -1,28 +1,29 @@ -/* - * Copyright (c) 2021, 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. - * - * $$$$$$\ $$$$$$$\ $$$$$$\ - * $$ __$$\ $$ __$$\ $$ __$$\ - * $$ / \__|$$ | $$ |$$ / \__| - * $$ | $$$$$$$ |$$ |$$$$\ - * $$ | $$ ____/ $$ |\_$$ | - * $$ | $$\ $$ | $$ | $$ | - * \$$$$$ |$$ | \$$$$$ | - * \______/ \__| \______/ - * - */ +// +// Copyright (c) 2021, 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 cpg import ( diff --git a/cpg-language-go/src/main/golang/expressions.go b/cpg-language-go/src/main/golang/expressions.go index d1716ea336..0486da9036 100644 --- a/cpg-language-go/src/main/golang/expressions.go +++ b/cpg-language-go/src/main/golang/expressions.go @@ -1,28 +1,29 @@ -/* - * Copyright (c) 2021, 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. - * - * $$$$$$\ $$$$$$$\ $$$$$$\ - * $$ __$$\ $$ __$$\ $$ __$$\ - * $$ / \__|$$ | $$ |$$ / \__| - * $$ | $$$$$$$ |$$ |$$$$\ - * $$ | $$ ____/ $$ |\_$$ | - * $$ | $$\ $$ | $$ | $$ | - * \$$$$$ |$$ | \$$$$$ | - * \______/ \__| \______/ - * - */ +// +// Copyright (c) 2021, 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 cpg import ( @@ -74,7 +75,9 @@ type LambdaExpression Expression type ProblemExpression Expression func (e *Expression) SetType(t *Type) { - (*HasType)(e).SetType(t) + if t != nil && !t.IsNil() { + (*HasType)(e).SetType(t) + } } func (c *CallExpression) SetFqn(s string) { @@ -178,7 +181,9 @@ func (u *UnaryOperator) SetOperatorCode(s string) (err error) { } func (l *Literal) SetType(t *Type) { - (*Expression)(l).SetType(t) + if t != nil && !t.IsNil() { + (*Expression)(l).SetType(t) + } } func (l *Literal) SetValue(value interface{}) { diff --git a/cpg-language-go/src/main/golang/frontend/declaration_builder.go b/cpg-language-go/src/main/golang/frontend/declaration_builder.go index 817ce7faf9..a3d3a6697d 100644 --- a/cpg-language-go/src/main/golang/frontend/declaration_builder.go +++ b/cpg-language-go/src/main/golang/frontend/declaration_builder.go @@ -1,28 +1,29 @@ -/* - * Copyright (c) 2022, 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. - * - * $$$$$$\ $$$$$$$\ $$$$$$\ - * $$ __$$\ $$ __$$\ $$ __$$\ - * $$ / \__|$$ | $$ |$$ / \__| - * $$ | $$$$$$$ |$$ |$$$$\ - * $$ | $$ ____/ $$ |\_$$ | - * $$ | $$\ $$ | $$ | $$ | - * \$$$$$ |$$ | \$$$$$ | - * \______/ \__| \______/ - * - */ +// +// Copyright (c) 2022, 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 frontend import ( diff --git a/cpg-language-go/src/main/golang/frontend/expression_builder.go b/cpg-language-go/src/main/golang/frontend/expression_builder.go index 9b248e90e1..4a457293b7 100644 --- a/cpg-language-go/src/main/golang/frontend/expression_builder.go +++ b/cpg-language-go/src/main/golang/frontend/expression_builder.go @@ -1,28 +1,29 @@ -/* - * Copyright (c) 2022, 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. - * - * $$$$$$\ $$$$$$$\ $$$$$$\ - * $$ __$$\ $$ __$$\ $$ __$$\ - * $$ / \__|$$ | $$ |$$ / \__| - * $$ | $$$$$$$ |$$ |$$$$\ - * $$ | $$ ____/ $$ |\_$$ | - * $$ | $$\ $$ | $$ | $$ | - * \$$$$$ |$$ | \$$$$$ | - * \______/ \__| \______/ - * - */ +// +// Copyright (c) 2022, 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 frontend import ( diff --git a/cpg-language-go/src/main/golang/frontend/frontend.go b/cpg-language-go/src/main/golang/frontend/frontend.go index f6d788f34a..f724f62b6a 100644 --- a/cpg-language-go/src/main/golang/frontend/frontend.go +++ b/cpg-language-go/src/main/golang/frontend/frontend.go @@ -1,28 +1,29 @@ -/* - * Copyright (c) 2021, 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. - * - * $$$$$$\ $$$$$$$\ $$$$$$\ - * $$ __$$\ $$ __$$\ $$ __$$\ - * $$ / \__|$$ | $$ |$$ / \__| - * $$ | $$$$$$$ |$$ |$$$$\ - * $$ | $$ ____/ $$ |\_$$ | - * $$ | $$\ $$ | $$ | $$ | - * \$$$$$ |$$ | \$$$$$ | - * \______/ \__| \______/ - * - */ +// +// Copyright (c) 2021, 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 frontend import ( diff --git a/cpg-language-go/src/main/golang/frontend/handler.go b/cpg-language-go/src/main/golang/frontend/handler.go index 163cea3897..5f03a0593b 100644 --- a/cpg-language-go/src/main/golang/frontend/handler.go +++ b/cpg-language-go/src/main/golang/frontend/handler.go @@ -1,28 +1,29 @@ -/* - * Copyright (c) 2021, 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. - * - * $$$$$$\ $$$$$$$\ $$$$$$\ - * $$ __$$\ $$ __$$\ $$ __$$\ - * $$ / \__|$$ | $$ |$$ / \__| - * $$ | $$$$$$$ |$$ |$$$$\ - * $$ | $$ ____/ $$ |\_$$ | - * $$ | $$\ $$ | $$ | $$ | - * \$$$$$ |$$ | \$$$$$ | - * \______/ \__| \______/ - * - */ +// +// Copyright (c) 2021, 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 frontend import ( @@ -349,13 +350,7 @@ func (this *GoLanguageFrontend) handleFuncDecl(fset *token.FileSet, funcDecl *as p.SetVariadic(true) var t = this.handleType(fset, ell.Elt) - var i = jnigi.NewObjectRef(cpg.PointerOriginClass) - err := env.GetStaticField(cpg.PointerOriginClass, "ARRAY", i) - if err != nil { - log.Fatal(err) - } - - p.SetType(t.Reference(i)) + p.SetType(this.ref(t, "pointer")) } else { p.SetType(this.handleType(fset, param.Type)) } @@ -446,7 +441,7 @@ func (this *GoLanguageFrontend) handleValueSpec(fset *token.FileSet, valueDecl * return decls } -// handleTypeSpec handles an [ast.TypeSec], which defines either a struct or an +// handleTypeSpec handles an [ast.TypeSpec], which defines either a struct or an // interface. It returns a single [cpg.Declaration]. func (this *GoLanguageFrontend) handleTypeSpec(fset *token.FileSet, typeDecl *ast.TypeSpec) *cpg.Declaration { err := this.LogInfo("Type specifier with name %s and type (%T, %+v)", typeDecl.Name.Name, typeDecl.Type, typeDecl.Type) @@ -1058,6 +1053,8 @@ func (this *GoLanguageFrontend) handleNewExpr(fset *token.FileSet, callExpr *ast t := this.handleType(fset, callExpr.Args[0]) // new is a pointer, so need to reference the type with a pointer + // TODO(oxisto): There is a very weird bug here: If we would use this.Pointer() here, we will get a + // null pointer exception and I have no idea why var pointer = jnigi.NewObjectRef(cpg.PointerOriginClass) err := env.GetStaticField(cpg.PointerOriginClass, "POINTER", pointer) if err != nil { @@ -1226,31 +1223,26 @@ func (this *GoLanguageFrontend) handleBasicLit(fset *token.FileSet, lit *ast.Bas var value cpg.Castable var t *cpg.Type - lang, err := this.GetLanguage() - if err != nil { - panic(err) - } - switch lit.Kind { case token.STRING: // strip the " value = cpg.NewString(lit.Value[1 : len(lit.Value)-1]) - t = cpg.TypeParser_createFrom("string", lang, this.GetCtx()) + t = this.primitiveType("string") case token.INT: i, _ := strconv.ParseInt(lit.Value, 10, 64) value = cpg.NewInteger(int(i)) - t = cpg.TypeParser_createFrom("int", lang, this.GetCtx()) + t = this.primitiveType("int") case token.FLOAT: // default seems to be float64 f, _ := strconv.ParseFloat(lit.Value, 64) value = cpg.NewDouble(f) - t = cpg.TypeParser_createFrom("float64", lang, this.GetCtx()) + t = this.primitiveType("float64") case token.IMAG: // TODO - t = &cpg.UnknownType_getUnknown(lang).Type + t = this.unknownType() case token.CHAR: value = cpg.NewString(lit.Value) - t = cpg.TypeParser_createFrom("rune", lang, this.GetCtx()) + t = this.primitiveType("rune") break } @@ -1316,14 +1308,9 @@ func (this *GoLanguageFrontend) handleFuncLit(fset *token.FileSet, lit *ast.Func } func (this *GoLanguageFrontend) handleIdent(fset *token.FileSet, ident *ast.Ident) *cpg.Expression { - lang, err := this.GetLanguage() - if err != nil { - panic(err) - } - // Check, if this is 'nil', because then we handle it as a literal in the graph if ident.Name == "nil" { - lit := this.NewLiteral(fset, ident, nil, &cpg.UnknownType_getUnknown(lang).Type) + lit := this.NewLiteral(fset, ident, nil, this.unknownType()) (*cpg.Node)(lit).SetName(this.ParseName(ident.Name)) @@ -1384,40 +1371,28 @@ func (this *GoLanguageFrontend) handleType(fset *token.FileSet, typeExpr ast.Exp this.LogTrace("fqn type: %s", name) } - return cpg.TypeParser_createFrom(name, lang, this.GetCtx()) + return this.objectType(name) case *ast.SelectorExpr: // small shortcut fqn := fmt.Sprintf("%s.%s", v.X.(*ast.Ident).Name, v.Sel.Name) this.LogTrace("FQN type: %s", fqn) - return cpg.TypeParser_createFrom(fqn, lang, this.GetCtx()) + return this.objectType(fqn) case *ast.StarExpr: t := this.handleType(fset, v.X) - var i = jnigi.NewObjectRef(cpg.PointerOriginClass) - err = env.GetStaticField(cpg.PointerOriginClass, "POINTER", i) - if err != nil { - log.Fatal(err) - } - this.LogTrace("Pointer to %s", t.GetName()) - return t.Reference(i) + return this.ref(t, "pointer") case *ast.ArrayType: t := this.handleType(fset, v.Elt) - var i = jnigi.NewObjectRef(cpg.PointerOriginClass) - err = env.GetStaticField(cpg.PointerOriginClass, "ARRAY", i) - if err != nil { - log.Fatal(err) - } - this.LogTrace("Array of %s", t.GetName()) - return t.Reference(i) + return this.ref(t, "array") case *ast.MapType: // we cannot properly represent Golangs built-in map types, yet so we have // to make a shortcut here and represent it as a Java-like map type. - t := cpg.TypeParser_createFrom("map", lang, this.GetCtx()) + t := this.objectType("map") keyType := this.handleType(fset, v.Key) valueType := this.handleType(fset, v.Value) @@ -1428,7 +1403,7 @@ func (this *GoLanguageFrontend) handleType(fset *token.FileSet, typeExpr ast.Exp return t case *ast.ChanType: // handle them similar to maps - t := cpg.TypeParser_createFrom("chan", lang, this.GetCtx()) + t := this.objectType("chan") chanType := this.handleType(fset, v.Value) (*cpg.ObjectType)(t).AddGeneric(chanType) @@ -1436,8 +1411,8 @@ func (this *GoLanguageFrontend) handleType(fset *token.FileSet, typeExpr ast.Exp return t case *ast.FuncType: var parametersTypesList, returnTypesList, name *jnigi.ObjectRef - var parameterTypes = []*cpg.Type{} - var returnTypes = []*cpg.Type{} + var parameterTypes []*cpg.Type + var returnTypes []*cpg.Type for _, param := range v.Params.List { parameterTypes = append(parameterTypes, this.handleType(fset, param.Type)) @@ -1484,7 +1459,7 @@ func (this *GoLanguageFrontend) handleType(fset *token.FileSet, typeExpr ast.Exp name += "}" - return cpg.TypeParser_createFrom(name, lang, this.GetCtx()) + return this.objectType(name) case *ast.IndexExpr: // This is a type with one type parameter. First we need to parse the "X" expression as a type var t = this.handleType(fset, v.X) @@ -1511,7 +1486,7 @@ func (this *GoLanguageFrontend) handleType(fset *token.FileSet, typeExpr ast.Exp this.LogError("Not parsing type of type %T yet. Defaulting to unknown type", v) } - return &cpg.UnknownType_getUnknown(lang).Type + return this.unknownType() } func (this *GoLanguageFrontend) isBuiltinType(s string) bool { diff --git a/cpg-language-go/src/main/golang/frontend/statement_builder.go b/cpg-language-go/src/main/golang/frontend/statement_builder.go index f951d41392..93db27d63f 100644 --- a/cpg-language-go/src/main/golang/frontend/statement_builder.go +++ b/cpg-language-go/src/main/golang/frontend/statement_builder.go @@ -1,28 +1,29 @@ -/* - * Copyright (c) 2022, 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. - * - * $$$$$$\ $$$$$$$\ $$$$$$\ - * $$ __$$\ $$ __$$\ $$ __$$\ - * $$ / \__|$$ | $$ |$$ / \__| - * $$ | $$$$$$$ |$$ |$$$$\ - * $$ | $$ ____/ $$ |\_$$ | - * $$ | $$\ $$ | $$ | $$ | - * \$$$$$ |$$ | \$$$$$ | - * \______/ \__| \______/ - * - */ +// +// Copyright (c) 2022, 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 frontend import ( diff --git a/cpg-language-go/src/main/golang/frontend/type_builder.go b/cpg-language-go/src/main/golang/frontend/type_builder.go new file mode 100644 index 0000000000..649d396d69 --- /dev/null +++ b/cpg-language-go/src/main/golang/frontend/type_builder.go @@ -0,0 +1,91 @@ +// +// Copyright (c) 2023, 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 frontend + +import ( + "cpg" + "fmt" + "log" + "strings" + "tekao.net/jnigi" +) + +func (frontend *GoLanguageFrontend) unknownType() *cpg.Type { + return frontend.newType("MetadataProvider", "UnknownType") +} + +func (frontend *GoLanguageFrontend) primitiveType(typeName string) *cpg.Type { + return frontend.newType("LanguageProvider", "PrimitiveType", cpg.NewCharSequence(typeName)) +} + +func (frontend *GoLanguageFrontend) objectType(typeName string) *cpg.Type { + return frontend.newType("LanguageProvider", "ObjectType", cpg.NewCharSequence(typeName)) +} + +func (frontend *GoLanguageFrontend) newType(providerClass string, typ string, args ...any) *cpg.Type { + var node cpg.Type + + // Prepend the frontend as the receiver + args = append([]any{frontend.Cast(cpg.GraphPackage + fmt.Sprintf("/%s", providerClass))}, args...) + + err := env.CallStaticMethod( + cpg.GraphPackage+"/TypeBuilderKt", + fmt.Sprintf("%s%s", strings.ToLower(string(typ[0])), typ[1:]), &node, + args..., + ) + if err != nil { + log.Fatal(err) + } + + return &node +} + +func (frontend *GoLanguageFrontend) ref(t *cpg.Type, typ string) *cpg.Type { + var ( + refType *jnigi.ObjectRef + args []any + ) + + refType = jnigi.NewObjectRef(cpg.TypeClass) + + args = []any{ + frontend.Cast(cpg.GraphPackage + fmt.Sprintf("/%s", "ContextProvider")), + t, + } + + err := env.CallStaticMethod( + cpg.GraphPackage+"/TypeBuilderKt", + typ, + refType, + args..., + ) + if err != nil { + panic(err) + } + + return &cpg.Type{ObjectRef: refType} +} diff --git a/cpg-language-go/src/main/golang/helper.go b/cpg-language-go/src/main/golang/helper.go index 4f1513421b..52a31156d1 100644 --- a/cpg-language-go/src/main/golang/helper.go +++ b/cpg-language-go/src/main/golang/helper.go @@ -1,28 +1,29 @@ -/* - * Copyright (c) 2022, 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. - * - * $$$$$$\ $$$$$$$\ $$$$$$\ - * $$ __$$\ $$ __$$\ $$ __$$\ - * $$ / \__|$$ | $$ |$$ / \__| - * $$ | $$$$$$$ |$$ |$$$$\ - * $$ | $$ ____/ $$ |\_$$ | - * $$ | $$\ $$ | $$ | $$ | - * \$$$$$ |$$ | \$$$$$ | - * \______/ \__| \______/ - * - */ +// +// Copyright (c) 2022, 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 cpg import "tekao.net/jnigi" diff --git a/cpg-language-go/src/main/golang/language.go b/cpg-language-go/src/main/golang/language.go index 36d0d504a5..8e92564fd1 100644 --- a/cpg-language-go/src/main/golang/language.go +++ b/cpg-language-go/src/main/golang/language.go @@ -1,28 +1,29 @@ -/* - * Copyright (c) 2021, 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. - * - * $$$$$$\ $$$$$$$\ $$$$$$\ - * $$ __$$\ $$ __$$\ $$ __$$\ - * $$ / \__|$$ | $$ |$$ / \__| - * $$ | $$$$$$$ |$$ |$$$$\ - * $$ | $$ ____/ $$ |\_$$ | - * $$ | $$\ $$ | $$ | $$ | - * \$$$$$ |$$ | \$$$$$ | - * \______/ \__| \______/ - * - */ +// +// Copyright (c) 2021, 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 cpg import ( diff --git a/cpg-language-go/src/main/golang/lib/cpg/main.go b/cpg-language-go/src/main/golang/lib/cpg/main.go index eb28bdee51..273c321112 100644 --- a/cpg-language-go/src/main/golang/lib/cpg/main.go +++ b/cpg-language-go/src/main/golang/lib/cpg/main.go @@ -1,28 +1,29 @@ -/* - * Copyright (c) 2021, 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. - * - * $$$$$$\ $$$$$$$\ $$$$$$\ - * $$ __$$\ $$ __$$\ $$ __$$\ - * $$ / \__|$$ | $$ |$$ / \__| - * $$ | $$$$$$$ |$$ |$$$$\ - * $$ | $$ ____/ $$ |\_$$ | - * $$ | $$\ $$ | $$ | $$ | - * \$$$$$ |$$ | \$$$$$ | - * \______/ \__| \______/ - * - */ +// +// Copyright (c) 2021, 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 main import ( diff --git a/cpg-language-go/src/main/golang/location.go b/cpg-language-go/src/main/golang/location.go index e5a1fa4e1a..c4b82e984e 100644 --- a/cpg-language-go/src/main/golang/location.go +++ b/cpg-language-go/src/main/golang/location.go @@ -1,28 +1,29 @@ -/* - * Copyright (c) 2021, 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. - * - * $$$$$$\ $$$$$$$\ $$$$$$\ - * $$ __$$\ $$ __$$\ $$ __$$\ - * $$ / \__|$$ | $$ |$$ / \__| - * $$ | $$$$$$$ |$$ |$$$$\ - * $$ | $$ ____/ $$ |\_$$ | - * $$ | $$\ $$ | $$ | $$ | - * \$$$$$ |$$ | \$$$$$ | - * \______/ \__| \______/ - * - */ +// +// Copyright (c) 2021, 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 cpg import ( diff --git a/cpg-language-go/src/main/golang/node.go b/cpg-language-go/src/main/golang/node.go index 03c86dc51a..11f7f260e6 100644 --- a/cpg-language-go/src/main/golang/node.go +++ b/cpg-language-go/src/main/golang/node.go @@ -1,28 +1,29 @@ -/* - * Copyright (c) 2021, 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. - * - * $$$$$$\ $$$$$$$\ $$$$$$\ - * $$ __$$\ $$ __$$\ $$ __$$\ - * $$ / \__|$$ | $$ |$$ / \__| - * $$ | $$$$$$$ |$$ |$$$$\ - * $$ | $$ ____/ $$ |\_$$ | - * $$ | $$\ $$ | $$ | $$ | - * \$$$$$ |$$ | \$$$$$ | - * \______/ \__| \______/ - * - */ +// +// Copyright (c) 2021, 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 cpg import ( diff --git a/cpg-language-go/src/main/golang/scope.go b/cpg-language-go/src/main/golang/scope.go index 95b9312fd4..4915f127dd 100644 --- a/cpg-language-go/src/main/golang/scope.go +++ b/cpg-language-go/src/main/golang/scope.go @@ -1,28 +1,29 @@ -/* - * Copyright (c) 2021, 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. - * - * $$$$$$\ $$$$$$$\ $$$$$$\ - * $$ __$$\ $$ __$$\ $$ __$$\ - * $$ / \__|$$ | $$ |$$ / \__| - * $$ | $$$$$$$ |$$ |$$$$\ - * $$ | $$ ____/ $$ |\_$$ | - * $$ | $$\ $$ | $$ | $$ | - * \$$$$$ |$$ | \$$$$$ | - * \______/ \__| \______/ - * - */ +// +// Copyright (c) 2021, 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 cpg import "tekao.net/jnigi" diff --git a/cpg-language-go/src/main/golang/statements.go b/cpg-language-go/src/main/golang/statements.go index 35bfcb001a..85bf95dbb2 100644 --- a/cpg-language-go/src/main/golang/statements.go +++ b/cpg-language-go/src/main/golang/statements.go @@ -1,28 +1,29 @@ -/* - * Copyright (c) 2021, 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. - * - * $$$$$$\ $$$$$$$\ $$$$$$\ - * $$ __$$\ $$ __$$\ $$ __$$\ - * $$ / \__|$$ | $$ |$$ / \__| - * $$ | $$$$$$$ |$$ |$$$$\ - * $$ | $$ ____/ $$ |\_$$ | - * $$ | $$\ $$ | $$ | $$ | - * \$$$$$ |$$ | \$$$$$ | - * \______/ \__| \______/ - * - */ +// +// Copyright (c) 2021, 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 cpg import ( diff --git a/cpg-language-go/src/main/golang/types.go b/cpg-language-go/src/main/golang/types.go index fe1485c284..f7f037dc67 100644 --- a/cpg-language-go/src/main/golang/types.go +++ b/cpg-language-go/src/main/golang/types.go @@ -1,33 +1,33 @@ -/* - * Copyright (c) 2021, 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. - * - * $$$$$$\ $$$$$$$\ $$$$$$\ - * $$ __$$\ $$ __$$\ $$ __$$\ - * $$ / \__|$$ | $$ |$$ / \__| - * $$ | $$$$$$$ |$$ |$$$$\ - * $$ | $$ ____/ $$ |\_$$ | - * $$ | $$\ $$ | $$ | $$ | - * \$$$$$ |$$ | \$$$$$ | - * \______/ \__| \______/ - * - */ +// +// Copyright (c) 2021, 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 cpg import ( "C" - "tekao.net/jnigi" ) import ( @@ -85,36 +85,34 @@ func InitEnv(e *jnigi.Env) { env = e } -func TypeParser_createFrom(s string, l *Language, ctx *TranslationContext) *Type { - var t Type - err := env.CallStaticMethod(TypeParserClass, "createFrom", &t, NewString(s), l, false, ctx) +func (t *Type) GetRoot() *Type { + var root Type + err := t.CallMethod(env, "getRoot", &root) if err != nil { log.Fatal(err) - } - return &t + return &root } -func UnknownType_getUnknown(l *Language) *UnknownType { - var t UnknownType - err := env.CallStaticMethod(UnknownTypeClass, "getUnknownType", &t, l) - if err != nil { - log.Fatal(err) - +func (h *HasType) SetType(t *Type) { + if t != nil || !t.IsNil() { + err := (*jnigi.ObjectRef)(h).CallMethod(env, "setType", nil, t.Cast(TypeClass)) + if err != nil { + panic(err) + } } - return &t } -func (t *Type) GetRoot() *Type { - var root Type - err := t.CallMethod(env, "getRoot", &root) +func (h *HasType) GetType() *Type { + var t Type + err := (*jnigi.ObjectRef)(h).CallMethod(env, "getType", &t) if err != nil { log.Fatal(err) } - return &root + return &t } func (t *Type) Reference(o *jnigi.ObjectRef) *Type { @@ -128,31 +126,12 @@ func (t *Type) Reference(o *jnigi.ObjectRef) *Type { return &refType } -func (h *HasType) SetType(t *Type) { - if t != nil { - (*jnigi.ObjectRef)(h).CallMethod(env, "setType", nil, t.Cast(TypeClass)) - } -} - -func (h *HasType) GetType() *Type { - var t Type - err := (*jnigi.ObjectRef)(h).CallMethod(env, "getType", &t) - if err != nil { - log.Fatal(err) - } - - return &t -} - func (t *Type) GetName() (fn *Name) { return (*Node)(t.ObjectRef).GetName() } func (t *ObjectType) AddGeneric(g *Type) { - // Stupid workaround, since casting does not work. See - // https://github.com/timob/jnigi/issues/60 - var objType = jnigi.WrapJObject(uintptr(t.JObject()), ObjectTypeClass, false) - err := objType.CallMethod(env, "addGeneric", nil, g.Cast(TypeClass)) + err := t.Cast(ObjectTypeClass).CallMethod(env, "addGeneric", nil, g.Cast(TypeClass)) if err != nil { log.Fatal(err) } 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 d1a9ffe7a1..ae69dd649f 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 @@ -32,8 +32,8 @@ import de.fraunhofer.aisec.cpg.frontends.SupportsParallelParsing import de.fraunhofer.aisec.cpg.frontends.TranslationException import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration -import de.fraunhofer.aisec.cpg.graph.newUnknownType import de.fraunhofer.aisec.cpg.graph.types.Type +import de.fraunhofer.aisec.cpg.graph.unknownType import de.fraunhofer.aisec.cpg.passes.GoExtraPass import de.fraunhofer.aisec.cpg.passes.order.RegisterExtraPass import de.fraunhofer.aisec.cpg.sarif.PhysicalLocation @@ -93,7 +93,7 @@ class GoLanguageFrontend(language: Language, ctx: Translatio override fun typeOf(type: Any): Type { // this is handled by native code - return newUnknownType() + return unknownType() } override fun codeOf(astNode: Any): String? { diff --git a/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/GoExtraPass.kt b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/GoExtraPass.kt index c422085675..a0c7bf15de 100644 --- a/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/GoExtraPass.kt +++ b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/GoExtraPass.kt @@ -143,7 +143,7 @@ class GoExtraPass(ctx: TranslationContext) : ComponentPass(ctx), ScopeProvider { // The key is the first variable. It is always an int val keyVariable = variable.declarations.firstOrNull() as? VariableDeclaration - keyVariable?.type = forEach.parseType("int") + keyVariable?.type = forEach.primitiveType("int") // The value is the second one. Its type depends on the array type val valueVariable = @@ -254,7 +254,7 @@ class GoExtraPass(ctx: TranslationContext) : ComponentPass(ctx), ScopeProvider { ) { val cast = parent.newCastExpression(call.code) cast.location = call.location - cast.castType = call.parseType(typeName.toString()) + cast.castType = call.objectType(typeName) cast.expression = call.arguments.single() if (parent !is ArgumentHolder) { diff --git a/cpg-language-go/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoLanguageFrontendTest.kt b/cpg-language-go/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoLanguageFrontendTest.kt index c2dab38819..dca2e02f44 100644 --- a/cpg-language-go/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoLanguageFrontendTest.kt +++ b/cpg-language-go/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoLanguageFrontendTest.kt @@ -128,7 +128,7 @@ class GoLanguageFrontendTest : BaseTest() { assertNotNull(decl) val new = assertIs(decl.firstAssignment) - assertEquals(tu.parseType("p.MyStruct*"), new.type) + with(tu) { assertEquals(objectType("p.MyStruct").pointer(), new.type) } val construct = new.initializer as? ConstructExpression assertNotNull(construct) @@ -141,7 +141,7 @@ class GoLanguageFrontendTest : BaseTest() { var make = assertIs(decl.firstAssignment) assertNotNull(make) - assertEquals(tu.parseType("int[]"), make.type) + with(tu) { assertEquals(tu.primitiveType("int").array(), make.type) } assertTrue(make is ArrayCreationExpression) @@ -159,7 +159,10 @@ class GoLanguageFrontendTest : BaseTest() { assertTrue(make is ConstructExpression) // TODO: Maps can have dedicated types and parsing them as a generic here is only a // temporary solution. This should be fixed in the future. - assertEquals(tu.parseType("map[string,string]"), make.type) + assertEquals( + tu.objectType("map", listOf(tu.primitiveType("string"), tu.primitiveType("string"))), + make.type + ) // make channel @@ -169,7 +172,7 @@ class GoLanguageFrontendTest : BaseTest() { make = assertIs(decl.firstAssignment) assertNotNull(make) assertTrue(make is ConstructExpression) - assertEquals(tu.parseType("chan[int]"), make.type) + assertEquals(tu.objectType("chan", listOf(tu.primitiveType("int"))), make.type) } @Test @@ -190,26 +193,26 @@ class GoLanguageFrontendTest : BaseTest() { assertNotNull(a.location) assertLocalName("a", a) - assertEquals(tu.parseType("int"), a.type) + assertEquals(tu.primitiveType("int"), a.type) val s = p.variables["s"] assertNotNull(s) assertLocalName("s", s) - assertEquals(tu.parseType("string"), s.type) + assertEquals(tu.primitiveType("string"), s.type) val f = p.variables["f"] assertNotNull(f) assertLocalName("f", f) - assertEquals(tu.parseType("float64"), f.type) + assertEquals(tu.primitiveType("float64"), f.type) val f32 = p.variables["f32"] assertNotNull(f32) assertLocalName("f32", f32) - assertEquals(tu.parseType("float32"), f32.type) + assertEquals(tu.primitiveType("float32"), f32.type) val n = p.variables["n"] assertNotNull(n) - assertEquals(tu.parseType("int*"), n.type) + with(tu) { assertEquals(tu.primitiveType("int").pointer(), n.type) } val nil = n.initializer as? Literal<*> assertNotNull(nil) @@ -275,7 +278,7 @@ class GoLanguageFrontendTest : BaseTest() { val s = myTest.parameters.first() assertNotNull(s) assertLocalName("s", s) - assertEquals(tu.parseType("string"), s.type) + assertEquals(tu.primitiveType("string"), s.type) assertLocalName("myTest", myTest) @@ -292,7 +295,7 @@ class GoLanguageFrontendTest : BaseTest() { assertNotNull(literal) assertEquals("%s", literal.value) - assertEquals(tu.parseType("string"), literal.type) + assertEquals(tu.primitiveType("string"), literal.type) val ref = callExpression.arguments[1] as? DeclaredReferenceExpression assertNotNull(ref) @@ -359,7 +362,7 @@ class GoLanguageFrontendTest : BaseTest() { val myField = fields.first() assertLocalName("MyField", myField) - assertEquals(tu.parseType("int"), myField.type) + assertEquals(tu.primitiveType("int"), myField.type) val myInterface = p.getDeclarationsByName("p.MyInterface", RecordDeclaration::class.java) @@ -460,7 +463,7 @@ class GoLanguageFrontendTest : BaseTest() { assertNotNull(lhs) assertEquals(myFunc.receiver, (lhs.base as? DeclaredReferenceExpression)?.refersTo) assertLocalName("Field", lhs) - assertEquals(tu.parseType("int"), lhs.type) + assertEquals(tu.primitiveType("int"), lhs.type) val rhs = assign.rhs.firstOrNull() as? DeclaredReferenceExpression assertNotNull(rhs) @@ -488,7 +491,7 @@ class GoLanguageFrontendTest : BaseTest() { assertNotNull(b) assertLocalName("b", b) - assertEquals(tu.parseType("bool"), b.type) + assertEquals(tu.primitiveType("bool"), b.type) // true, false are builtin variables, NOT literals in Golang // we might need to parse this special case differently @@ -586,8 +589,10 @@ class GoLanguageFrontendTest : BaseTest() { val c = body.variables["c"] assertNotNull(c) - // type will be inferred from the function declaration - assertEquals(tu.parseType("p.MyStruct*"), c.type) + with(tu) { + // type will be inferred from the function declaration + assertEquals(objectType("p.MyStruct").pointer(), c.type) + } val newMyStruct = assertIs(c.firstAssignment) diff --git a/cpg-language-go/src/test/resources/log4j2.xml b/cpg-language-go/src/test/resources/log4j2.xml index 359d8071bf..747860628a 100644 --- a/cpg-language-go/src/test/resources/log4j2.xml +++ b/cpg-language-go/src/test/resources/log4j2.xml @@ -2,7 +2,7 @@ - + diff --git a/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/DeclarationHandler.kt b/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/DeclarationHandler.kt index 0ef6768d38..2e6ab669df 100644 --- a/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/DeclarationHandler.kt +++ b/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/DeclarationHandler.kt @@ -46,9 +46,7 @@ import de.fraunhofer.aisec.cpg.graph.scopes.RecordScope 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.Type -import de.fraunhofer.aisec.cpg.graph.types.UnknownType import java.util.function.Supplier -import java.util.stream.Collectors import kotlin.collections.set open class DeclarationHandler(lang: JavaLanguageFrontend) : @@ -71,10 +69,7 @@ open class DeclarationHandler(lang: JavaLanguageFrontend) : frontend.scopeManager.enterScope(declaration) createMethodReceiver(currentRecordDecl, declaration) declaration.addThrowTypes( - constructorDecl.thrownExceptions - .stream() - .map { type: ReferenceType -> this.parseType(type.asString()) } - .collect(Collectors.toList()) + constructorDecl.thrownExceptions.map { type: ReferenceType -> frontend.typeOf(type) } ) for (parameter in constructorDecl.parameters) { val param = @@ -88,9 +83,11 @@ open class DeclarationHandler(lang: JavaLanguageFrontend) : frontend.scopeManager.addDeclaration(param) } - val name = frontend.scopeManager.firstScopeOrNull { it is RecordScope }?.astNode?.name - if (name != null) { - val type = this.parseType(name) + val record = + frontend.scopeManager.firstScopeOrNull { it is RecordScope }?.astNode + as? RecordDeclaration + if (record != null) { + val type = record.toType() declaration.type = type } @@ -118,10 +115,7 @@ open class DeclarationHandler(lang: JavaLanguageFrontend) : frontend.scopeManager.enterScope(functionDeclaration) createMethodReceiver(currentRecordDecl, functionDeclaration) functionDeclaration.addThrowTypes( - methodDecl.thrownExceptions - .stream() - .map { type: ReferenceType -> this.parseType(type.asString()) } - .collect(Collectors.toList()) + methodDecl.thrownExceptions.map { type: ReferenceType -> frontend.typeOf(type) } ) for (parameter in methodDecl.parameters) { var resolvedType: Type? = @@ -170,7 +164,7 @@ open class DeclarationHandler(lang: JavaLanguageFrontend) : val receiver = this.newVariableDeclaration( "this", - if (recordDecl != null) this.parseType(recordDecl.name) else newUnknownType(), + recordDecl?.toType() ?: unknownType(), "this", false ) @@ -289,12 +283,10 @@ open class DeclarationHandler(lang: JavaLanguageFrontend) : // 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. + // need this to generate the field. val scope = frontend.scopeManager.currentScope as RecordScope? if (scope?.name != null) { - val fieldType = - scope.name?.let { this.parseType(it) } ?: UnknownType.getUnknownType(language) + 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) @@ -322,7 +314,6 @@ open class DeclarationHandler(lang: JavaLanguageFrontend) : // TODO: can field have more than one variable? val variable = fieldDecl.getVariable(0) val modifiers = fieldDecl.modifiers.map { modifier -> modifier.keyword.asString() } - val joinedModifiers = java.lang.String.join(" ", modifiers) + " " val location = frontend.locationOf(fieldDecl) val initializer = variable.initializer @@ -336,23 +327,23 @@ open class DeclarationHandler(lang: JavaLanguageFrontend) : frontend.scopeManager.currentRecord, variable.resolve().type.describe() ) - ?: this.parseType(joinedModifiers + variable.resolve().type.describe()) + ?: frontend.typeOf(variable.resolve().type) } catch (e: UnsolvedSymbolException) { val t = frontend.recoverTypeFromUnsolvedException(e) if (t == null) { log.warn("Could not resolve type for {}", variable) - type = this.parseType(joinedModifiers + variable.type.asString()) + type = frontend.typeOf(variable.type) } else { - type = this.parseType(joinedModifiers + t) + type = this.objectType(t) type.typeOrigin = Type.Origin.GUESSED } } catch (e: UnsupportedOperationException) { val t = frontend.recoverTypeFromUnsolvedException(e) if (t == null) { log.warn("Could not resolve type for {}", variable) - type = this.parseType(joinedModifiers + variable.type.asString()) + type = frontend.typeOf(variable.type) } else { - type = this.parseType(joinedModifiers + t) + type = this.objectType(t) type.typeOrigin = Type.Origin.GUESSED } } @@ -378,7 +369,7 @@ open class DeclarationHandler(lang: JavaLanguageFrontend) : val enumDeclaration = this.newEnumDeclaration(name, enumDecl.toString(), location) val entries = enumDecl.entries.mapNotNull { handle(it) as EnumConstantDeclaration? } - entries.forEach { it.type = this.parseType(enumDeclaration.name) } + entries.forEach { it.type = this.objectType(enumDeclaration.name) } enumDeclaration.entries = entries val superTypes = enumDecl.implementedTypes.map { frontend.getTypeAsGoodAsPossible(it) } enumDeclaration.superTypes = superTypes 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 1428eae0d4..2b0978b562 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 @@ -92,7 +92,7 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : if (castExpr.type.isPrimitiveType) { // Set Type based on the Casting type as it will result in a conversion for primitive // types - castExpression.type = this.parseType(castExpr.type.resolve().asPrimitive().describe()) + castExpression.type = frontend.typeOf(castExpr.type.resolve().asPrimitive()) } else { // Get Runtime type from cast expression for complex types; castExpression.expression.registerTypeListener(castExpression) @@ -167,20 +167,20 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : val conditionalExpr = expr.asConditionalExpr() val superType: Type = try { - this.parseType(conditionalExpr.calculateResolvedType().describe()) + frontend.typeOf(conditionalExpr.calculateResolvedType()) } catch (e: RuntimeException) { val s = frontend.recoverTypeFromUnsolvedException(e) if (s != null) { - this.parseType(s) + this.objectType(s) } else { - newUnknownType() + unknownType() } } catch (e: NoClassDefFoundError) { val s = frontend.recoverTypeFromUnsolvedException(e) if (s != null) { - this.parseType(s) + this.objectType(s) } else { - newUnknownType() + unknownType() } } val condition = @@ -270,12 +270,12 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : if (resolve.asField().isStatic) { isStaticAccess = true } - baseType = this.parseType(resolve.asField().declaringType().qualifiedName) + baseType = this.objectType(resolve.asField().declaringType().qualifiedName) } catch (ex: RuntimeException) { isStaticAccess = true val typeString = frontend.recoverTypeFromUnsolvedException(ex) if (typeString != null) { - baseType = this.parseType(typeString) + baseType = this.objectType(typeString) } else { // try to get the name val name: String @@ -289,17 +289,17 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : val qualifiedNameFromImports = frontend.getQualifiedNameFromImports(name) baseType = if (qualifiedNameFromImports != null) { - this.parseType(qualifiedNameFromImports) + this.objectType(qualifiedNameFromImports) } else { log.info("Unknown base type 1 for {}", fieldAccessExpr) - newUnknownType() + unknownType() } } } catch (ex: NoClassDefFoundError) { isStaticAccess = true val typeString = frontend.recoverTypeFromUnsolvedException(ex) if (typeString != null) { - baseType = this.parseType(typeString) + baseType = this.objectType(typeString) } else { val name: String val tokenRange = scope.asNameExpr().tokenRange @@ -312,10 +312,10 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : val qualifiedNameFromImports = frontend.getQualifiedNameFromImports(name) baseType = if (qualifiedNameFromImports != null) { - this.parseType(qualifiedNameFromImports) + this.objectType(qualifiedNameFromImports) } else { log.info("Unknown base type 1 for {}", fieldAccessExpr) - newUnknownType() + unknownType() } } } @@ -351,10 +351,10 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : val qualifiedNameFromImports = frontend.getQualifiedNameFromImports(name) val baseType = if (qualifiedNameFromImports != null) { - this.parseType(qualifiedNameFromImports) + this.objectType(qualifiedNameFromImports) } else { log.info("Unknown base type 2 for {}", fieldAccessExpr) - newUnknownType() + unknownType() } base = this.newDeclaredReferenceExpression( @@ -379,18 +379,18 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : symbol.asField().type.describe() ) if (fieldType == null) { - fieldType = this.parseType(symbol.asField().type.describe()) + fieldType = frontend.typeOf(symbol.asField().type) } } catch (ex: RuntimeException) { val typeString = frontend.recoverTypeFromUnsolvedException(ex) fieldType = if (typeString != null) { - this.parseType(typeString) + this.objectType(typeString) } else if (fieldAccessExpr.toString().endsWith(".length")) { - this.parseType("int") + this.primitiveType("int") } else { log.info("Unknown field type for {}", fieldAccessExpr) - newUnknownType() + unknownType() } val memberExpression = this.newMemberExpression( @@ -405,12 +405,12 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : val typeString = frontend.recoverTypeFromUnsolvedException(ex) fieldType = if (typeString != null) { - this.parseType(typeString) + this.objectType(typeString) } else if (fieldAccessExpr.toString().endsWith(".length")) { - this.parseType("int") + this.primitiveType("int") } else { log.info("Unknown field type for {}", fieldAccessExpr) - newUnknownType() + unknownType() } val memberExpression = this.newMemberExpression(fieldAccessExpr.name.identifier, base, fieldType, ".") @@ -430,43 +430,47 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : is IntegerLiteralExpr -> newLiteral( literalExpr.asIntegerLiteralExpr().asNumber(), - this.parseType("int"), + this.primitiveType("int"), value ) is StringLiteralExpr -> newLiteral( literalExpr.asStringLiteralExpr().asString(), - this.parseType("java.lang.String"), + this.primitiveType("java.lang.String"), value ) is BooleanLiteralExpr -> newLiteral( literalExpr.asBooleanLiteralExpr().value, - this.parseType("boolean"), + this.primitiveType("boolean"), value ) is CharLiteralExpr -> - newLiteral(literalExpr.asCharLiteralExpr().asChar(), this.parseType("char"), value) + newLiteral( + literalExpr.asCharLiteralExpr().asChar(), + this.primitiveType("char"), + value + ) is DoubleLiteralExpr -> newLiteral( literalExpr.asDoubleLiteralExpr().asDouble(), - this.parseType("double"), + this.primitiveType("double"), value ) is LongLiteralExpr -> newLiteral( literalExpr.asLongLiteralExpr().asNumber(), - this.parseType("long"), + this.primitiveType("long"), value ) - is NullLiteralExpr -> newLiteral(null, this.parseType("null"), value) + is NullLiteralExpr -> newLiteral(null, this.objectType("null"), value) else -> null } } private fun handleClassExpression(expr: Expression): DeclaredReferenceExpression { val classExpr = expr.asClassExpr() - val type = this.parseType(classExpr.type.asString()) + val type = frontend.typeOf(classExpr.type) val thisExpression = this.newDeclaredReferenceExpression( classExpr.toString().substring(classExpr.toString().lastIndexOf('.') + 1), @@ -481,7 +485,7 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : private fun handleThisExpression(expr: Expression): DeclaredReferenceExpression { val thisExpr = expr.asThisExpr() val resolvedValueDeclaration = thisExpr.resolve() - val type = this.parseType(resolvedValueDeclaration.qualifiedName) + val type = this.objectType(resolvedValueDeclaration.qualifiedName) var name = thisExpr.toString() // If the typeName is specified, then this a "qualified this" and we need to handle it @@ -503,7 +507,7 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : // about the inheritance structure. Thus, we delay the resolving to the variable resolving // process val superExpression = - this.newDeclaredReferenceExpression(expr.toString(), newUnknownType(), expr.toString()) + this.newDeclaredReferenceExpression(expr.toString(), unknownType(), expr.toString()) frontend.setCodeAndLocation(superExpression, expr) return superExpression } @@ -582,7 +586,7 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : symbol.type.describe() ) if (type == null) { - type = this.parseType(symbol.type.describe()) + type = frontend.typeOf(symbol.type) } this.newDeclaredReferenceExpression(symbol.name, type, nameExpr.toString()) } @@ -599,9 +603,9 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : } val t: Type if (typeString == null) { - t = newUnknownType() + t = unknownType() } else { - t = this.parseType(typeString) + t = this.objectType(typeString) t.typeOrigin = Type.Origin.GUESSED } val declaredReferenceExpression = @@ -612,11 +616,11 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : } declaredReferenceExpression } catch (ex: RuntimeException) { - val t = this.parseType("UNKNOWN4") // TODO: What's this? UNKNOWN4?? + val t = unknownType() log.info("Unresolved symbol: {}", nameExpr.nameAsString) this.newDeclaredReferenceExpression(nameExpr.nameAsString, t, nameExpr.toString()) } catch (ex: NoClassDefFoundError) { - val t = this.parseType("UNKNOWN4") + val t = unknownType() log.info("Unresolved symbol: {}", nameExpr.nameAsString) this.newDeclaredReferenceExpression(nameExpr.nameAsString, t, nameExpr.toString()) } @@ -637,7 +641,7 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : val rhs: de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression = this.newLiteral( typeAsGoodAsPossible.typeName, - this.parseType("class"), + this.objectType("class"), binaryExpr.typeAsString ) val binaryOperator = this.newBinaryOperator("instanceof", binaryExpr.toString()) @@ -747,7 +751,7 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : } else { this.parseName(qualifiedName).parent } - baseType = this.parseType(baseName ?: Type.UNKNOWN_TYPE_STRING) + baseType = this.objectType(baseName ?: Type.UNKNOWN_TYPE_STRING) base = this.newDeclaredReferenceExpression(baseName, baseType) } else { // Since it is possible to omit the "this" keyword, some methods in java do not have @@ -758,14 +762,14 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : base = createImplicitThis() } } - val member = this.newMemberExpression(name, base, newUnknownType(), ".") + val member = this.newMemberExpression(name, base, unknownType(), ".") frontend.setCodeAndLocation( member, methodCallExpr.name ) // This will also overwrite the code set to the empty string set above callExpression = this.newMemberCallExpression(member, isStatic, methodCallExpr.toString(), expr) - callExpression.type = typeString?.let { this.parseType(it) } ?: newUnknownType() + callExpression.type = typeString?.let { this.objectType(it) } ?: unknownType() val arguments = methodCallExpr.arguments // handle the arguments @@ -791,7 +795,7 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : val base: de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression val thisType = (frontend.scopeManager.currentFunction as MethodDeclaration?)?.receiver?.type - ?: newUnknownType() + ?: unknownType() base = this.newDeclaredReferenceExpression("this", thisType, "this") base.isImplicit = true return base @@ -843,7 +847,7 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : frontend.scopeManager.enterScope(anonymousRecord) - anonymousRecord.addSuperClass(parseType(constructorName)) + anonymousRecord.addSuperClass(objectType(constructorName)) val anonymousClassBody = objectCreationExpr.anonymousClassBody.get() for (classBody in anonymousClassBody) { // Whatever is implemented in the anonymous class has to be added to the record diff --git a/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/JavaLanguage.kt b/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/JavaLanguage.kt index e932fdbc63..c1666370da 100644 --- a/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/JavaLanguage.kt +++ b/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/JavaLanguage.kt @@ -84,6 +84,8 @@ open class JavaLanguage : "char" to IntegerType("char", 16, this, NumericType.Modifier.UNSIGNED), "short" to IntegerType("short", 16, this, NumericType.Modifier.SIGNED), "int" to IntegerType("int", 32, this, NumericType.Modifier.SIGNED), + "java.lang.Integer" to + IntegerType("java.lang.Integer", 32, this, NumericType.Modifier.SIGNED), "long" to IntegerType("long", 64, this, NumericType.Modifier.SIGNED), // Floating-Point Types: 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 0d26100047..14354fb397 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 @@ -36,10 +36,15 @@ import com.github.javaparser.ast.expr.MethodCallExpr import com.github.javaparser.ast.expr.NameExpr import com.github.javaparser.ast.nodeTypes.NodeWithAnnotations import com.github.javaparser.ast.nodeTypes.NodeWithType -import com.github.javaparser.ast.type.Type +import com.github.javaparser.ast.type.* import com.github.javaparser.resolution.UnsolvedSymbolException import com.github.javaparser.resolution.declarations.ResolvedMethodDeclaration import com.github.javaparser.resolution.declarations.ResolvedValueDeclaration +import com.github.javaparser.resolution.types.ResolvedArrayType +import com.github.javaparser.resolution.types.ResolvedPrimitiveType +import com.github.javaparser.resolution.types.ResolvedReferenceType +import com.github.javaparser.resolution.types.ResolvedType +import com.github.javaparser.resolution.types.ResolvedVoidType import com.github.javaparser.symbolsolver.JavaSymbolSolver import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFacade import com.github.javaparser.symbolsolver.resolution.typesolvers.CombinedTypeSolver @@ -65,6 +70,7 @@ import java.io.File import java.io.FileNotFoundException import java.io.IOException import java.util.function.Consumer +import kotlin.jvm.optionals.getOrNull /** Main parser for ONE Java file. */ @RegisterExtraPass( @@ -144,11 +150,6 @@ open class JavaLanguageFrontend(language: Language, ctx: T } } - override fun typeOf(type: Type): de.fraunhofer.aisec.cpg.graph.types.Type { - // reserved for future use - return newUnknownType() - } - @Throws(TranslationException::class, FileNotFoundException::class) fun parse(file: File?, parser: JavaParser): CompilationUnit { val result = parser.parse(file) @@ -211,8 +212,8 @@ open class JavaLanguageFrontend(language: Language, ctx: T return try { val type = nodeWithType.typeAsString if (type == "var") { - newUnknownType() - } else parseType(resolved.type.describe()) + unknownType() + } else typeOf(resolved.type) } catch (ex: RuntimeException) { getTypeFromImportIfPossible(nodeWithType.type) } catch (ex: NoClassDefFoundError) { @@ -223,8 +224,8 @@ open class JavaLanguageFrontend(language: Language, ctx: T fun getTypeAsGoodAsPossible(type: Type): de.fraunhofer.aisec.cpg.graph.types.Type { return try { if (type.toString() == "var") { - newUnknownType() - } else parseType(type.resolve().describe()) + unknownType() + } else typeOf(type.resolve()) } catch (ex: RuntimeException) { getTypeFromImportIfPossible(type) } catch (ex: NoClassDefFoundError) { @@ -350,7 +351,7 @@ open class JavaLanguageFrontend(language: Language, ctx: T resolved.returnType.describe() ) if (type == null) { - type = parseType(resolved.returnType.describe()) + type = typeOf(resolved.returnType) } type } catch (ex: RuntimeException) { @@ -384,7 +385,7 @@ open class JavaLanguageFrontend(language: Language, ctx: T // if this is not a ClassOrInterfaceType, just return if (!searchType.isClassOrInterfaceType || context == null) { log.warn("Unable to resolve type for {}", type.asString()) - val returnType = parseType(type.asString()) + val returnType = this.typeOf(type) returnType.typeOrigin = de.fraunhofer.aisec.cpg.graph.types.Type.Origin.GUESSED return returnType } @@ -394,23 +395,24 @@ open class JavaLanguageFrontend(language: Language, ctx: T for (importDeclaration in context?.imports ?: listOf()) { if (importDeclaration.name.identifier.endsWith(clazz.name.identifier)) { // TODO: handle type parameters - return parseType(importDeclaration.nameAsString) + return objectType(importDeclaration.nameAsString) } } - var name = clazz.asString() + val returnType = this.typeOf(clazz) // no import found, so our last guess is that the type is in the same package - // as our current translation unit + // as our current translation unit, so we can "adjust" the name to an FQN val o = context?.packageDeclaration if (o?.isPresent == true) { - name = o.get().nameAsString + language.namespaceDelimiter + name + returnType.name = + parseName(o.get().nameAsString + language.namespaceDelimiter + returnType.name) } - val returnType = parseType(name) + returnType.typeOrigin = de.fraunhofer.aisec.cpg.graph.types.Type.Origin.GUESSED return returnType } log.warn("Unable to resolve type for {}", type.asString()) - val returnType = parseType(type.asString()) + val returnType = this.typeOf(type) returnType.typeOrigin = de.fraunhofer.aisec.cpg.graph.types.Type.Origin.GUESSED return returnType } @@ -477,6 +479,32 @@ open class JavaLanguageFrontend(language: Language, ctx: T return list } + override fun typeOf(type: Type): de.fraunhofer.aisec.cpg.graph.types.Type { + return when (type) { + is ArrayType -> this.typeOf(type.elementType).array() + is VoidType -> incompleteType() + is PrimitiveType -> primitiveType(type.asString()) + is ClassOrInterfaceType -> + objectType( + type.nameAsString, + type.typeArguments.getOrNull()?.map { this.typeOf(it) } ?: listOf() + ) + is ReferenceType -> objectType(type.asString()) + else -> objectType(type.asString()) + } + } + + fun typeOf(type: ResolvedType): de.fraunhofer.aisec.cpg.graph.types.Type { + return when (type) { + is ResolvedArrayType -> typeOf(type.componentType).array() + is ResolvedVoidType -> incompleteType() + is ResolvedPrimitiveType -> primitiveType(type.describe()) + is ResolvedReferenceType -> + objectType(type.describe(), type.typeParametersValues().map { typeOf(it) }) + else -> objectType(type.describe()) + } + } + companion object { const val THIS = "this" const val ANNOTATION_MEMBER_VALUE = "value" diff --git a/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/StatementHandler.kt b/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/StatementHandler.kt index 2e74aad2f8..adffc066ac 100644 --- a/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/StatementHandler.kt +++ b/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/StatementHandler.kt @@ -233,7 +233,7 @@ class StatementHandler(lang: JavaLanguageFrontend?) : // and in // cpp StatementHandler if (statement.condition == null) { - val literal: Literal<*> = this.newLiteral(true, this.parseType("boolean"), "true") + val literal: Literal<*> = this.newLiteral(true, this.primitiveType("boolean"), "true") statement.condition = literal } if (forStmt.update.size > 1) { @@ -577,7 +577,7 @@ class StatementHandler(lang: JavaLanguageFrontend?) : } // we do not know which of the exceptions was actually thrown, so we assume this might // be any - concreteType = this.parseType("java.lang.Throwable") + concreteType = this.objectType("java.lang.Throwable") concreteType.typeOrigin = Type.Origin.GUESSED } else { concreteType = frontend.getTypeAsGoodAsPossible(catchCls.parameter.type) diff --git a/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/JavaCallResolverHelper.kt b/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/JavaCallResolverHelper.kt index 13a334c34c..d2940fa7dd 100644 --- a/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/JavaCallResolverHelper.kt +++ b/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/JavaCallResolverHelper.kt @@ -30,7 +30,7 @@ import de.fraunhofer.aisec.cpg.frontends.java.JavaLanguage import de.fraunhofer.aisec.cpg.graph.Name import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration -import de.fraunhofer.aisec.cpg.graph.parseType +import de.fraunhofer.aisec.cpg.graph.objectType import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberCallExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberExpression @@ -111,7 +111,7 @@ class JavaCallResolverHelper { ): RecordDeclaration? { val baseName = callee.base.name.parent ?: return null - if (curClass.parseType(baseName) in curClass.implementedInterfaces) { + if (curClass.objectType(baseName) in curClass.implementedInterfaces) { // Basename is an interface -> BaseName.super refers to BaseName itself return recordMap[baseName] } else { diff --git a/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/JavaExternalTypeHierarchyResolver.kt b/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/JavaExternalTypeHierarchyResolver.kt index 55834f49d1..b6aeaa2525 100644 --- a/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/JavaExternalTypeHierarchyResolver.kt +++ b/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/JavaExternalTypeHierarchyResolver.kt @@ -76,7 +76,7 @@ class JavaExternalTypeHierarchyResolver(ctx: TranslationContext) : ComponentPass val resolvedSuperTypes = symbol.correspondingDeclaration.getAncestors(true) for (anc in resolvedSuperTypes) { // Add all resolved supertypes to the type. - val superType = provider.parseType(anc.qualifiedName) + val superType = provider.objectType(anc.qualifiedName) superType.typeOrigin = Type.Origin.RESOLVED t.superTypes.add(superType) } diff --git a/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/java/JavaLanguageFrontendTest.kt b/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/java/JavaLanguageFrontendTest.kt index 5f9a5b5810..f538719246 100644 --- a/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/java/JavaLanguageFrontendTest.kt +++ b/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/java/JavaLanguageFrontendTest.kt @@ -142,7 +142,7 @@ internal class JavaLanguageFrontendTest : BaseTest() { val sDecl = s.singleDeclaration as? VariableDeclaration assertNotNull(sDecl) assertLocalName("s", sDecl) - assertEquals(tu.parseType("java.lang.String"), sDecl.type) + assertEquals(tu.primitiveType("java.lang.String"), sDecl.type) // should contain a single statement val sce = forEachStatement.statement as? MemberCallExpression @@ -189,11 +189,11 @@ internal class JavaLanguageFrontendTest : BaseTest() { assertNotNull(scope) // first exception type was? resolved, so we can expect a FQN - assertEquals(tu.parseType("java.lang.NumberFormatException"), firstCatch.parameter?.type) + assertEquals(tu.objectType("java.lang.NumberFormatException"), firstCatch.parameter?.type) // second one could not be resolved so we do not have an FQN - assertEquals(tu.parseType("NotResolvableTypeException"), catchClauses[1].parameter?.type) + assertEquals(tu.objectType("NotResolvableTypeException"), catchClauses[1].parameter?.type) // third type should have been resolved through the import - assertEquals(tu.parseType("some.ImportedException"), (catchClauses[2].parameter)?.type) + assertEquals(tu.objectType("some.ImportedException"), (catchClauses[2].parameter)?.type) // and 1 finally val finallyBlock = tryStatement.finallyBlock @@ -303,7 +303,7 @@ internal class JavaLanguageFrontendTest : BaseTest() { assertNotNull(method) assertEquals(recordDeclaration, method.recordDeclaration) assertLocalName("method", method) - assertEquals(tu.parseType("java.lang.Integer"), method.returnTypes.firstOrNull()) + assertEquals(tu.primitiveType("java.lang.Integer"), method.returnTypes.firstOrNull()) val functionType = method.type as? FunctionType assertNotNull(functionType) @@ -424,8 +424,10 @@ internal class JavaLanguageFrontendTest : BaseTest() { val a = (statements[0] as? DeclarationStatement)?.singleDeclaration as? VariableDeclaration assertNotNull(a) - // type should be Integer[] - assertEquals(tu.parseType("int[]"), a.type) + with(tu) { + // type should be Integer[] + assertEquals(primitiveType("int").array(), a.type) + } // it has an array creation initializer val ace = a.initializer as? ArrayCreationExpression @@ -633,7 +635,7 @@ internal class JavaLanguageFrontendTest : BaseTest() { (lhs?.base as? DeclaredReferenceExpression)?.refersTo as? VariableDeclaration? assertNotNull(receiver) assertLocalName("this", receiver) - assertEquals(tu.parseType("my.Animal"), receiver.type) + assertEquals(tu.objectType("my.Animal"), receiver.type) } @Test diff --git a/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/types/TypeTests.kt b/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/types/TypeTests.kt index 65ebf1fbf3..5302853f54 100644 --- a/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/types/TypeTests.kt +++ b/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/types/TypeTests.kt @@ -38,96 +38,6 @@ import kotlin.test.* internal class TypeTests : BaseTest() { - @Test - fun createFromJava() { - var result: Type - var expected: Type - - with( - JavaLanguageFrontend( - JavaLanguage(), - TranslationContext( - TranslationConfiguration.builder().build(), - ScopeManager(), - TypeManager() - ) - ) - ) { - // Test 1: Ignore Access Modifier Keyword (public, private, protected) - var typeString = "private int a" - result = parseType(typeString) - expected = IntegerType("int", 32, JavaLanguage(), NumericType.Modifier.SIGNED) - assertEquals(expected, result) - - // Test 2: constant type using final - typeString = "final int a" - result = parseType(typeString) - expected = IntegerType("int", 32, JavaLanguage(), NumericType.Modifier.SIGNED) - assertEquals(expected, result) - - // Test 3: static type - typeString = "static int a" - result = parseType(typeString) - expected = IntegerType("int", 32, JavaLanguage(), NumericType.Modifier.SIGNED) - assertEquals(expected, result) - - // Test 4: volatile type - typeString = "public volatile int a" - result = parseType(typeString) - expected = IntegerType("int", 32, JavaLanguage(), NumericType.Modifier.SIGNED) - assertEquals(expected, result) - - // Test 5: combining a storage type and a qualifier - typeString = "private static final String a" - result = parseType(typeString) - expected = StringType("java.lang.String", JavaLanguage()) - assertEquals(expected, result) - - // Test 6: using two different qualifiers - typeString = "public final volatile int a" - result = parseType(typeString) - expected = IntegerType("int", 32, JavaLanguage(), NumericType.Modifier.SIGNED) - assertEquals(expected, result) - - // Test 7: Reference level using arrays - typeString = "int[] a" - result = parseType(typeString) - expected = - PointerType( - IntegerType("int", 32, JavaLanguage(), NumericType.Modifier.SIGNED), - PointerType.PointerOrigin.ARRAY - ) - assertEquals(expected, result) - - // Test 8: generics - typeString = "List list" - result = parseType(typeString) - var generics = mutableListOf() - generics.add(StringType("java.lang.String", JavaLanguage())) - expected = ObjectType("List", generics, false, JavaLanguage()) - assertEquals(expected, result) - - // Test 9: more generics - typeString = "List>, List> data" - result = parseType(typeString) - val genericStringType = StringType("java.lang.String", JavaLanguage()) - val generics3: MutableList = ArrayList() - generics3.add(genericStringType) - val genericElement3 = ObjectType("List", generics3, false, JavaLanguage()) - val generics2a: MutableList = ArrayList() - generics2a.add(genericElement3) - val generics2b: MutableList = ArrayList() - generics2b.add(genericStringType) - val genericElement1 = ObjectType("List", generics2a, false, JavaLanguage()) - val genericElement2 = ObjectType("List", generics2b, false, JavaLanguage()) - generics = ArrayList() - generics.add(genericElement1) - generics.add(genericElement2) - expected = ObjectType("List", generics, false, JavaLanguage()) - assertEquals(expected, result) - } - } - // Tests on the resulting graph @Test @Throws(Exception::class) @@ -224,12 +134,12 @@ internal class TypeTests : BaseTest() { ) { val topLevel = Path.of("src", "test", "resources", "compiling", "hierarchy") val result = analyze("java", topLevel, true) { it.registerLanguage(JavaLanguage()) } - val root = parseType("multistep.Root") - val level0 = parseType("multistep.Level0") - val level1 = parseType("multistep.Level1") - val level1b = parseType("multistep.Level1B") - val level2 = parseType("multistep.Level2") - val unrelated = parseType("multistep.Unrelated") + val root = objectType("multistep.Root") + val level0 = objectType("multistep.Level0") + val level1 = objectType("multistep.Level1") + val level1b = objectType("multistep.Level1B") + val level2 = objectType("multistep.Level2") + val unrelated = objectType("multistep.Unrelated") getCommonTypeTestGeneral( root, level0, diff --git a/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolverTest.kt b/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolverTest.kt index 8bd719e38c..dc47080ec8 100644 --- a/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolverTest.kt +++ b/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolverTest.kt @@ -124,8 +124,8 @@ class CallResolverTest : BaseTest() { assertNotNull(tu) val records = result.records - val intType = tu.parseType("int") - val stringType = tu.parseType(("java.lang.String")) + val intType = tu.primitiveType("int") + val stringType = tu.primitiveType(("java.lang.String")) testMethods(records, intType, stringType) testOverriding(records) ensureNoUnknownClassDummies(records) diff --git a/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/DeclarationHandler.kt b/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/DeclarationHandler.kt index e547148e18..69754747d4 100644 --- a/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/DeclarationHandler.kt +++ b/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/DeclarationHandler.kt @@ -106,7 +106,7 @@ class DeclarationHandler(lang: LLVMIRLanguageFrontend) : val funcType = LLVMGetElementType(funcPtrType) val returnType = LLVMGetReturnType(funcType) - functionDeclaration.type = frontend.typeFrom(returnType) + functionDeclaration.type = frontend.typeOf(returnType) frontend.scopeManager.enterScope(functionDeclaration) @@ -215,7 +215,7 @@ class DeclarationHandler(lang: LLVMIRLanguageFrontend) : for (i in 0 until size) { val a = LLVMStructGetTypeAtIndex(typeRef, i) - val fieldType = frontend.typeFrom(a, alreadyVisited) + val fieldType = frontend.typeOf(a, alreadyVisited) // there are no names, so we need to invent some dummy ones for easier reading val fieldName = "field_$i" @@ -254,7 +254,7 @@ class DeclarationHandler(lang: LLVMIRLanguageFrontend) : for (i in 0 until size) { val field = LLVMStructGetTypeAtIndex(typeRef, i) - val fieldType = frontend.typeFrom(field, alreadyVisited) + val fieldType = frontend.typeOf(field, alreadyVisited) name += "_${fieldType.typeName}" } diff --git a/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/ExpressionHandler.kt b/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/ExpressionHandler.kt index c37c551af4..72be04e5b3 100644 --- a/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/ExpressionHandler.kt +++ b/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/ExpressionHandler.kt @@ -64,7 +64,7 @@ class ExpressionHandler(lang: LLVMIRLanguageFrontend) : newDeclaredReferenceExpression("poison", frontend.typeOf(value), "poison") } LLVMConstantTokenNoneValueKind -> - newLiteral(null, newUnknownType(), frontend.codeOf(value)) + newLiteral(null, unknownType(), frontend.codeOf(value)) LLVMUndefValueValueKind -> initializeAsUndef(frontend.typeOf(value), frontend.codeOf(value)) LLVMConstantAggregateZeroValueKind -> @@ -515,11 +515,10 @@ class ExpressionHandler(lang: LLVMIRLanguageFrontend) : } // our new base-type is the type of the field - baseType = field?.type ?: newUnknownType() + baseType = field?.type ?: unknownType() // construct our member expression - expr = - newMemberExpression(fieldName, base, field?.type ?: newUnknownType(), ".", "") + expr = newMemberExpression(fieldName, base, field?.type ?: unknownType(), ".", "") log.info("{}", expr) // the current expression is the new base diff --git a/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/LLVMIRLanguageFrontend.kt b/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/LLVMIRLanguageFrontend.kt index ed44dba0e2..65642a8d5d 100644 --- a/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/LLVMIRLanguageFrontend.kt +++ b/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/LLVMIRLanguageFrontend.kt @@ -29,13 +29,10 @@ 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.TranslationException -import de.fraunhofer.aisec.cpg.graph.Node +import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.declarations.Declaration import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration -import de.fraunhofer.aisec.cpg.graph.newTranslationUnitDeclaration -import de.fraunhofer.aisec.cpg.graph.newUnknownType -import de.fraunhofer.aisec.cpg.graph.parseType import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression import de.fraunhofer.aisec.cpg.graph.types.* @@ -163,7 +160,7 @@ class LLVMIRLanguageFrontend(language: Language, ctx: Tr } override fun typeOf(type: LLVMTypeRef): Type { - return typeFrom(type, mutableMapOf()) + return typeOf(type, mutableMapOf()) } /** Returns a pair of the name and symbol name of [valueRef]. */ @@ -183,10 +180,10 @@ class LLVMIRLanguageFrontend(language: Language, ctx: Tr fun typeOf(valueRef: LLVMValueRef): Type { val typeRef = LLVMTypeOf(valueRef) - return typeFrom(typeRef) + return typeOf(typeRef) } - internal fun typeFrom( + internal fun typeOf( typeRef: LLVMTypeRef, alreadyVisited: MutableMap = mutableMapOf() ): Type { @@ -196,7 +193,7 @@ class LLVMIRLanguageFrontend(language: Language, ctx: Tr if (result != null) return result } if (typeRef in alreadyVisited) { - return alreadyVisited[typeRef] ?: newUnknownType() + return alreadyVisited[typeRef] ?: unknownType() } alreadyVisited[typeRef] = null val res: Type = @@ -204,19 +201,19 @@ class LLVMIRLanguageFrontend(language: Language, ctx: Tr LLVMVectorTypeKind, LLVMArrayTypeKind -> { // var length = LLVMGetArrayLength(typeRef) - val elementType = typeFrom(LLVMGetElementType(typeRef), alreadyVisited) - elementType.reference(PointerType.PointerOrigin.ARRAY) + val elementType = typeOf(LLVMGetElementType(typeRef), alreadyVisited) + elementType.array() } LLVMPointerTypeKind -> { - val elementType = typeFrom(LLVMGetElementType(typeRef), alreadyVisited) - elementType.reference(PointerType.PointerOrigin.POINTER) + val elementType = typeOf(LLVMGetElementType(typeRef), alreadyVisited) + elementType.pointer() } LLVMStructTypeKind -> { val record = declarationHandler.handleStructureType(typeRef, alreadyVisited) record.toType() } else -> { - parseType(typeStr) + objectType(typeStr) } } alreadyVisited[typeRef] = res diff --git a/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/StatementHandler.kt b/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/StatementHandler.kt index 2c027c2d8d..c5fe499ad1 100644 --- a/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/StatementHandler.kt +++ b/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/StatementHandler.kt @@ -573,7 +573,7 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : val cmpPred = when (LLVMGetFCmpPredicate(instr)) { LLVMRealPredicateFalse -> { - return newLiteral(false, parseType("i1"), "false") + return newLiteral(false, primitiveType("i1"), "false") } LLVMRealOEQ -> "==" LLVMRealOGT -> ">" @@ -608,7 +608,7 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : "!=" } LLVMRealPredicateTrue -> { - return newLiteral(true, parseType("i1"), "true") + return newLiteral(true, primitiveType("i1"), "true") } else -> "unknown" } @@ -645,7 +645,7 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : base = newDeclaredReferenceExpression( copy.singleDeclaration?.name?.localName, - (copy.singleDeclaration as? VariableDeclaration)?.type ?: newUnknownType(), + (copy.singleDeclaration as? VariableDeclaration)?.type ?: unknownType(), frontend.codeOf(instr) ) } @@ -695,7 +695,7 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : val field = record.fields["field_$index"] // our new base-type is the type of the field - baseType = field?.type ?: newUnknownType() + baseType = field?.type ?: unknownType() // construct our member expression expr = newMemberExpression(field?.name?.localName, base, baseType, ".", "") @@ -778,12 +778,13 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : val instrString = frontend.codeOf(instr) val callExpression = newCallExpression(llvmInternalRef("llvm.fence"), "llvm.fence", instrString, false) - val ordering = newLiteral(LLVMGetOrdering(instr), parseType("i32"), frontend.codeOf(instr)) + val ordering = + newLiteral(LLVMGetOrdering(instr), primitiveType("i32"), frontend.codeOf(instr)) callExpression.addArgument(ordering, "ordering") if (instrString?.contains("syncscope") == true) { val syncscope = instrString.split("\"")[1] callExpression.addArgument( - newLiteral(syncscope, parseType("String"), instrString), + newLiteral(syncscope, objectType("String"), instrString), "syncscope" ) } @@ -958,12 +959,12 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : } val condition = newBinaryOperator(operatorCode, instrStr) val castExprLhs = newCastExpression(frontend.codeOf(instr)) - castExprLhs.castType = parseType("u${ty.name}") + castExprLhs.castType = objectType("u${ty.name}") castExprLhs.expression = ptrDeref condition.lhs = castExprLhs val castExprRhs = newCastExpression(frontend.codeOf(instr)) - castExprRhs.castType = parseType("u${ty.name}") + castExprRhs.castType = objectType("u${ty.name}") castExprRhs.expression = value condition.rhs = castExprRhs @@ -1023,7 +1024,7 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : // The case statement is derived from the address of the label which we can jump to val caseBBAddress = LLVMValueAsBasicBlock(LLVMGetOperand(instr, idx)).address() val caseStatement = newCaseStatement(nodeCode) - caseStatement.caseExpression = newLiteral(caseBBAddress, parseType("i64"), nodeCode) + caseStatement.caseExpression = newLiteral(caseBBAddress, primitiveType("i64"), nodeCode) caseStatements.addStatement(caseStatement) // Get the label of the goto statement. @@ -1172,7 +1173,7 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : catchClause.parameter = newVariableDeclaration( "e_${gotoCatch.labelName}", - newUnknownType(), + unknownType(), instrStr, true, instr @@ -1225,7 +1226,7 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : val except = newVariableDeclaration( exceptionName, - parseType(catchType), // TODO: This doesn't work for multiple types to catch + objectType(catchType), // TODO: This doesn't work for multiple types to catch frontend.codeOf(instr), false, instr @@ -1254,7 +1255,7 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : arrayExpr.arrayExpression = newDeclaredReferenceExpression( decl?.name?.toString() ?: Node.EMPTY_NAME, - decl?.type ?: newUnknownType(), + decl?.type ?: unknownType(), instrStr ) arrayExpr.subscriptExpression = frontend.getOperandValueAtIndex(instr, 2) @@ -1331,7 +1332,8 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : } else { val arrayExpr = newArraySubscriptionExpression(instrStr) arrayExpr.arrayExpression = frontend.getOperandValueAtIndex(instr, 0) - arrayExpr.subscriptExpression = newLiteral(idxInt, parseType("i32"), instrStr) + arrayExpr.subscriptExpression = + newLiteral(idxInt, primitiveType("i32"), instrStr) initializers += arrayExpr } } else if (idxInt < array1Length + array2Length) { @@ -1343,7 +1345,7 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : val arrayExpr = newArraySubscriptionExpression(instrStr) arrayExpr.arrayExpression = frontend.getOperandValueAtIndex(instr, 1) arrayExpr.subscriptExpression = - newLiteral(idxInt - array1Length, parseType("i32"), instrStr) + newLiteral(idxInt - array1Length, primitiveType("i32"), instrStr) initializers += arrayExpr } } else { @@ -1582,13 +1584,13 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : if (unsigned) { val op1Type = "u${op1.type.name}" val castExprLhs = newCastExpression(frontend.codeOf(instr)) - castExprLhs.castType = parseType(op1Type) + castExprLhs.castType = objectType(op1Type) castExprLhs.expression = op1 binaryOperator.lhs = castExprLhs val op2Type = "u${op2.type.name}" val castExprRhs = newCastExpression(frontend.codeOf(instr)) - castExprRhs.castType = parseType(op2Type) + castExprRhs.castType = objectType(op2Type) castExprRhs.expression = op2 binaryOperator.rhs = castExprRhs } else { diff --git a/cpg-language-llvm/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/LLVMIRLanguageFrontendTest.kt b/cpg-language-llvm/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/LLVMIRLanguageFrontendTest.kt index 8f2c0448ab..0d98a6852b 100644 --- a/cpg-language-llvm/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/LLVMIRLanguageFrontendTest.kt +++ b/cpg-language-llvm/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/LLVMIRLanguageFrontendTest.kt @@ -26,13 +26,10 @@ package de.fraunhofer.aisec.cpg.frontends.llvm import de.fraunhofer.aisec.cpg.* -import de.fraunhofer.aisec.cpg.graph.TypeManager -import de.fraunhofer.aisec.cpg.graph.bodyOrNull -import de.fraunhofer.aisec.cpg.graph.byNameOrNull +import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration -import de.fraunhofer.aisec.cpg.graph.parseType import de.fraunhofer.aisec.cpg.graph.statements.* import de.fraunhofer.aisec.cpg.graph.statements.expressions.* import de.fraunhofer.aisec.cpg.graph.types.ObjectType @@ -312,10 +309,10 @@ class LLVMIRLanguageFrontendTest { val rhs = (comparison.rhs as Literal<*>) val lhs = (comparison.lhs as DeclaredReferenceExpression).refersTo as VariableDeclaration assertEquals(10L, (rhs.value as Long)) - assertEquals(tu.parseType("i32"), rhs.type) + assertEquals(tu.primitiveType("i32"), rhs.type) assertLocalName("x", comparison.lhs as DeclaredReferenceExpression) assertLocalName("x", lhs) - assertEquals(tu.parseType("i32"), lhs.type) + assertEquals(tu.primitiveType("i32"), lhs.type) // Check that the jump targets are set correctly val ifStatement = main.bodyOrNull(0) @@ -346,11 +343,11 @@ class LLVMIRLanguageFrontendTest { assertTrue(ifBranchComp.lhs is CastExpression) val ifBranchCompRhs = ifBranchComp.rhs as CastExpression - assertEquals(tu.parseType("ui32"), ifBranchCompRhs.castType) - assertEquals(tu.parseType("ui32"), ifBranchCompRhs.type) + assertEquals(tu.objectType("ui32"), ifBranchCompRhs.castType) + assertEquals(tu.objectType("ui32"), ifBranchCompRhs.type) val ifBranchCompLhs = ifBranchComp.lhs as CastExpression - assertEquals(tu.parseType("ui32"), ifBranchCompLhs.castType) - assertEquals(tu.parseType("ui32"), ifBranchCompLhs.type) + assertEquals(tu.objectType("ui32"), ifBranchCompLhs.castType) + assertEquals(tu.objectType("ui32"), ifBranchCompLhs.type) val declRefExpr = ifBranchCompLhs.expression as DeclaredReferenceExpression assertEquals(-3, ((ifBranchCompRhs.expression as Literal<*>).value as Long)) diff --git a/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonLanguageFrontend.kt b/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonLanguageFrontend.kt index 11f1116b10..6e381748da 100644 --- a/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonLanguageFrontend.kt +++ b/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonLanguageFrontend.kt @@ -31,8 +31,8 @@ import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend import de.fraunhofer.aisec.cpg.frontends.TranslationException import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration -import de.fraunhofer.aisec.cpg.graph.newUnknownType import de.fraunhofer.aisec.cpg.graph.types.Type +import de.fraunhofer.aisec.cpg.graph.unknownType import de.fraunhofer.aisec.cpg.sarif.PhysicalLocation import java.io.File import java.nio.file.Paths @@ -50,7 +50,7 @@ class PythonLanguageFrontend(language: Language, ctx: Tr override fun typeOf(type: Any): Type { // will be invoked by native function - return newUnknownType() + return unknownType() } override fun codeOf(astNode: Any): String? { diff --git a/cpg-language-python/src/main/python/CPGPython/_expressions.py b/cpg-language-python/src/main/python/CPGPython/_expressions.py index dd642a561b..366d917141 100644 --- a/cpg-language-python/src/main/python/CPGPython/_expressions.py +++ b/cpg-language-python/src/main/python/CPGPython/_expressions.py @@ -26,6 +26,7 @@ from ._spotless_dummy import * from de.fraunhofer.aisec.cpg.graph import ExpressionBuilderKt from de.fraunhofer.aisec.cpg.graph import NodeBuilderKt +from de.fraunhofer.aisec.cpg.graph import TypeBuilderKt from de.fraunhofer.aisec.cpg.graph.types import UnknownType import ast @@ -67,7 +68,8 @@ def handle_expression_impl(self, expr): self.get_src_code(expr), expr) # we got a complex number - complextype = NodeBuilderKt.parseType(self.frontend, "complex") + complextype = TypeBuilderKt.primitiveType(self.frontend, + "complex") # TODO: fix this once the CPG supports complex numbers realpart = complex(lhs.getValue()) @@ -242,7 +244,7 @@ def handle_expression_impl(self, expr): cast = ExpressionBuilderKt.newCastExpression( self.frontend, self.get_src_code(expr)) cast.setCastType( - NodeBuilderKt.parseType(self.frontend, "str")) + TypeBuilderKt.primitiveType(self.frontend, "str")) cast.setExpression( self.handle_expression(expr.args[0])) return cast @@ -275,27 +277,31 @@ def handle_expression_impl(self, expr): elif isinstance(expr, ast.Constant): resultvalue = expr.value if isinstance(expr.value, type(None)): - tpe = NodeBuilderKt.parseType(self.frontend, "None") + tpe = TypeBuilderKt.objectType(self.frontend, "None") elif isinstance(expr.value, bool): - tpe = NodeBuilderKt.parseType(self.frontend, "bool") + tpe = TypeBuilderKt.primitiveType(self.frontend, "bool") elif isinstance(expr.value, int): - tpe = NodeBuilderKt.parseType(self.frontend, "int") + tpe = TypeBuilderKt.primitiveType(self.frontend, "int") elif isinstance(expr.value, float): - tpe = NodeBuilderKt.parseType(self.frontend, "float") + tpe = TypeBuilderKt.primitiveType(self.frontend, "float") elif isinstance(expr.value, complex): - tpe = NodeBuilderKt.parseType(self.frontend, "complex") + tpe = TypeBuilderKt.primitiveType(self.frontend, "complex") # TODO: fix this once the CPG supports complex numbers resultvalue = str(resultvalue) elif isinstance(expr.value, str): - tpe = NodeBuilderKt.parseType(self.frontend, "str") + tpe = TypeBuilderKt.primitiveType(self.frontend, "str") elif isinstance(expr.value, bytes): - tpe = NodeBuilderKt.parseType(self.frontend, "byte[]") + tpe = NodeBuilderKt.array( + self.frontend, + TypeBuilderKt.primitiveType( + self.frontend, + "byte")) else: self.log_with_loc( "Found unexpected type - using a dummy: %s" % (type(expr.value)), loglevel="ERROR") - tpe = UnknownType.getUnknownType(self.frontend.getLanguage()) + tpe = TypeBuilderKt.unknownType(self.frontend) lit = ExpressionBuilderKt.newLiteral( self.frontend, resultvalue, tpe, self.get_src_code(expr)) @@ -314,7 +320,7 @@ def handle_expression_impl(self, expr): value.getName(), value.getType(), value.getCode()) mem = ExpressionBuilderKt.newMemberExpression( self.frontend, expr.attr, value, - UnknownType.getUnknownType(self.frontend.getLanguage()), + TypeBuilderKt.unknownType(self.frontend), ".", self.get_src_code(expr)) return mem @@ -333,7 +339,7 @@ def handle_expression_impl(self, expr): elif isinstance(expr, ast.Name): r = ExpressionBuilderKt.newDeclaredReferenceExpression( self.frontend, expr.id, - UnknownType.getUnknownType(self.frontend.getLanguage()), + TypeBuilderKt.unknownType(self.frontend), self.get_src_code(expr)) # Take a little shortcut and set refersTo, in case this is a method diff --git a/cpg-language-python/src/main/python/CPGPython/_statements.py b/cpg-language-python/src/main/python/CPGPython/_statements.py index 2a10a329fe..5b27a9f519 100644 --- a/cpg-language-python/src/main/python/CPGPython/_statements.py +++ b/cpg-language-python/src/main/python/CPGPython/_statements.py @@ -26,6 +26,7 @@ from ._spotless_dummy import * from de.fraunhofer.aisec.cpg.graph import DeclarationBuilderKt from de.fraunhofer.aisec.cpg.graph import NodeBuilderKt +from de.fraunhofer.aisec.cpg.graph import TypeBuilderKt from de.fraunhofer.aisec.cpg.graph import StatementBuilderKt from de.fraunhofer.aisec.cpg.graph import ExpressionBuilderKt from de.fraunhofer.aisec.cpg.graph.statements import CompoundStatement @@ -63,7 +64,7 @@ def handle_statement_impl(self, stmt): tname = "%s.%s" % (namespace.toString(), base.id) self.log_with_loc("Building super type using current " "namespace: %s" % tname) - t = NodeBuilderKt.parseType(self.frontend, tname) + t = TypeBuilderKt.objectType(self.frontend, tname) bases.append(t) cls.setSuperClasses(bases) @@ -170,7 +171,7 @@ def handle_statement_impl(self, stmt): else: name = s.name src = name - tpe = UnknownType.getUnknownType(self.frontend.getLanguage()) + tpe = TypeBuilderKt.unknownType(self.frontend) v = DeclarationBuilderKt.newVariableDeclaration(self.frontend, name, tpe, src, False) @@ -200,7 +201,7 @@ def handle_statement_impl(self, stmt): else: name = s.name src = name - tpe = UnknownType.getUnknownType(self.frontend.getLanguage()) + tpe = TypeBuilderKt.unknownType(self.frontend) v = DeclarationBuilderKt.newVariableDeclaration( self.frontend, name, tpe, src, False) # inaccurate but ast.alias does not hold location information @@ -301,8 +302,9 @@ def handle_function_or_method(self, node, record=None): if record is not None: if len(node.args.args) > 0: recv_node = node.args.args[0] - tpe = NodeBuilderKt.parseType(self.frontend, - record.getName()) + tpe = TypeBuilderKt.objectType( + self.frontend, + record.getName()) recv = DeclarationBuilderKt.newVariableDeclaration( self.frontend, recv_node.arg, tpe, self.get_src_code(recv_node), @@ -406,9 +408,10 @@ def handle_function_or_method(self, node, record=None): def handle_argument(self, arg: ast.arg): self.log_with_loc("Handling an argument: %s" % (ast.dump(arg))) if arg.annotation is not None: - tpe = NodeBuilderKt.parseType(self.frontend, arg.annotation.id) + # TODO: parse non-scalar types + tpe = TypeBuilderKt.objectType(self.frontend, arg.annotation.id) else: - tpe = UnknownType.getUnknownType(self.frontend.getLanguage()) + tpe = TypeBuilderKt.unknownType(self.frontend) # TODO variadic pvd = DeclarationBuilderKt.newParamVariableDeclaration( self.frontend, arg.arg, tpe, False, self.get_src_code(arg)) @@ -579,7 +582,7 @@ class Foo: else: v = DeclarationBuilderKt.newFieldDeclaration( self.frontend, name, - UnknownType.getUnknownType(self.frontend.getLanguage()), + TypeBuilderKt.unknownType(self.frontend), None, self.get_src_code(stmt), None, None, False) # TODO None -> add infos self.scopemanager.addDeclaration(v) @@ -604,8 +607,7 @@ def bar(self): else: v = DeclarationBuilderKt.newVariableDeclaration( self.frontend, lhs.getName(), - UnknownType.getUnknownType( - self.frontend.getLanguage()), + TypeBuilderKt.unknownType(self.frontend), self.get_src_code(stmt), False) if rhs is not None: @@ -643,8 +645,7 @@ def bar(self): else: v = DeclarationBuilderKt.newFieldDeclaration( self.frontend, lhs.getName(), - UnknownType.getUnknownType( - self.frontend.getLanguage()), + TypeBuilderKt.unknownType(self.frontend), None, self.get_src_code(stmt), None, None, False) self.scopemanager.addDeclaration(v) @@ -666,7 +667,7 @@ def bar(self): else: v = DeclarationBuilderKt.newVariableDeclaration( self.frontend, lhs.getName(), - UnknownType.getUnknownType(self.frontend.getLanguage()), + TypeBuilderKt.unknownType(self.frontend), self.get_src_code(stmt), False) if rhs is not None: diff --git a/cpg-language-python/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonFrontendTest.kt b/cpg-language-python/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonFrontendTest.kt index b5f808b10f..39fd3815a6 100644 --- a/cpg-language-python/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonFrontendTest.kt +++ b/cpg-language-python/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonFrontendTest.kt @@ -69,19 +69,19 @@ class PythonFrontendTest : BaseTest() { val b = p.variables["b"] assertNotNull(b) assertLocalName("b", b) - assertEquals(tu.parseType("bool"), b.type) + assertEquals(tu.primitiveType("bool"), b.type) assertEquals(true, (b.initializer as? Literal<*>)?.value) val i = p.variables["i"] assertNotNull(i) assertLocalName("i", i) - assertEquals(tu.parseType("int"), i.type) + assertEquals(tu.primitiveType("int"), i.type) assertEquals(42L, (i.initializer as? Literal<*>)?.value) val f = p.variables["f"] assertNotNull(f) assertLocalName("f", f) - assertEquals(tu.parseType("float"), f.type) + assertEquals(tu.primitiveType("float"), f.type) assertEquals(1.0, (f.initializer as? Literal<*>)?.value) val c = p.variables["c"] @@ -96,13 +96,13 @@ class PythonFrontendTest : BaseTest() { val t = p.variables["t"] assertNotNull(t) assertLocalName("t", t) - assertEquals(tu.parseType("str"), t.type) + assertEquals(tu.primitiveType("str"), t.type) assertEquals("Hello", (t.initializer as? Literal<*>)?.value) val n = p.variables["n"] assertNotNull(n) assertLocalName("n", n) - assertEquals(tu.parseType("None"), n.type) + assertEquals(tu.objectType("None"), n.type) assertEquals(null, (n.initializer as? Literal<*>)?.value) } @@ -142,7 +142,7 @@ class PythonFrontendTest : BaseTest() { val s = bar.parameters.first() assertNotNull(s) assertLocalName("s", s) - assertEquals(tu.parseType("str"), s.type) + assertEquals(tu.primitiveType("str"), s.type) assertLocalName("bar", bar) @@ -159,7 +159,7 @@ class PythonFrontendTest : BaseTest() { assertNotNull(literal) assertEquals("bar(s) here: ", literal.value) - assertEquals(tu.parseType("str"), literal.type) + assertEquals(tu.primitiveType("str"), literal.type) val ref = callExpression.arguments[1] as? DeclaredReferenceExpression assertNotNull(ref) @@ -219,11 +219,11 @@ class PythonFrontendTest : BaseTest() { as? VariableDeclaration assertNotNull(sel) assertLocalName("sel", sel) - assertEquals(tu.parseType("bool"), sel.type) + assertEquals(tu.primitiveType("bool"), sel.type) val initializer = sel.initializer as? Literal<*> assertNotNull(initializer) - assertEquals(tu.parseType("bool"), initializer.type) + assertEquals(tu.primitiveType("bool"), initializer.type) assertEquals("True", initializer.code) val `if` = body.statements[1] as? IfStatement @@ -308,11 +308,11 @@ class PythonFrontendTest : BaseTest() { val foo = body.singleDeclaration as? VariableDeclaration assertNotNull(foo) assertLocalName("foo", foo) - assertEquals(tu.parseType("int"), foo.type) + assertEquals(tu.primitiveType("int"), foo.type) val initializer = foo.initializer as? ConditionalExpression assertNotNull(initializer) - assertEquals(tu.parseType("int"), initializer.type) + assertEquals(tu.primitiveType("int"), initializer.type) val ifCond = initializer.condition as? Literal<*> assertNotNull(ifCond) @@ -321,13 +321,13 @@ class PythonFrontendTest : BaseTest() { val elseExpr = initializer.elseExpr as? Literal<*> assertNotNull(elseExpr) - assertEquals(tu.parseType("bool"), ifCond.type) + assertEquals(tu.primitiveType("bool"), ifCond.type) assertEquals(false, ifCond.value) - assertEquals(tu.parseType("int"), thenExpr.type) + assertEquals(tu.primitiveType("int"), thenExpr.type) assertEquals(21, (thenExpr.value as? Long)?.toInt()) - assertEquals(tu.parseType("int"), elseExpr.type) + assertEquals(tu.primitiveType("int"), elseExpr.type) assertEquals(42, (elseExpr.value as? Long)?.toInt()) } @@ -433,7 +433,7 @@ class PythonFrontendTest : BaseTest() { assertNotNull(i) assertLocalName("i", i) - assertEquals(tu.parseType("int"), i.type) + assertEquals(tu.primitiveType("int"), i.type) // self.somevar = i val someVarDeclaration = diff --git a/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/DeclarationHandler.kt b/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/DeclarationHandler.kt index a0317bd83a..f928df4c2d 100644 --- a/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/DeclarationHandler.kt +++ b/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/DeclarationHandler.kt @@ -56,8 +56,7 @@ class DeclarationHandler(lang: TypeScriptLanguageFrontend) : private fun handlePropertySignature(node: TypeScriptNode): FieldDeclaration { val name = this.frontend.getIdentifierName(node) - val type = - node.typeChildNode?.let { this.frontend.typeHandler.handle(it) } ?: newUnknownType() + val type = node.typeChildNode?.let { this.frontend.typeOf(it) } ?: unknownType() val field = newFieldDeclaration( @@ -110,8 +109,7 @@ class DeclarationHandler(lang: TypeScriptLanguageFrontend) : private fun handleParameter(node: TypeScriptNode): Declaration { val name = this.frontend.getIdentifierName(node) - val type = - node.typeChildNode?.let { this.frontend.typeHandler.handle(it) } ?: newUnknownType() + val type = node.typeChildNode?.let { this.frontend.typeOf(it) } ?: unknownType() return newParamVariableDeclaration(name, type, false, this.frontend.codeOf(node)) } @@ -160,9 +158,7 @@ class DeclarationHandler(lang: TypeScriptLanguageFrontend) : else -> newFunctionDeclaration(name, this.frontend.codeOf(node)) } - node.typeChildNode?.let { - func.type = this.frontend.typeHandler.handle(it) ?: newUnknownType() - } + node.typeChildNode?.let { func.type = this.frontend.typeOf(it) } this.frontend.scopeManager.enterScope(func) @@ -202,7 +198,7 @@ class DeclarationHandler(lang: TypeScriptLanguageFrontend) : // TODO: support ObjectBindingPattern (whatever it is). seems to be multiple assignment val declaration = - newVariableDeclaration(name, newUnknownType(), this.frontend.codeOf(node), false) + newVariableDeclaration(name, unknownType(), this.frontend.codeOf(node), false) declaration.location = this.frontend.locationOf(node) // the last node that is not an identifier or an object binding pattern is an initializer diff --git a/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/ExpressionHandler.kt b/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/ExpressionHandler.kt index 224e388e56..9069f4910a 100644 --- a/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/ExpressionHandler.kt +++ b/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/ExpressionHandler.kt @@ -113,7 +113,7 @@ class ExpressionHandler(lang: TypeScriptLanguageFrontend) : // the function will (probably) not have a defined return type, so we try to deduce this // from a return statement - if (func?.type == newUnknownType()) { + if (func?.type == unknownType()) { val returnValue = func.bodyOrNull()?.returnValue /*if (returnValue == null) { @@ -121,7 +121,7 @@ class ExpressionHandler(lang: TypeScriptLanguageFrontend) : func.type = TypeParser.createFrom("void", false) } else {*/ - val returnType = returnValue?.type ?: newUnknownType() + val returnType = returnValue?.type ?: unknownType() func.type = returnType // } @@ -163,13 +163,13 @@ class ExpressionHandler(lang: TypeScriptLanguageFrontend) : ?.replace("'", "") ?: "" - return newLiteral(value, parseType("String"), frontend.codeOf(node)) + return newLiteral(value, primitiveType("string"), frontend.codeOf(node)) } private fun handleIdentifier(node: TypeScriptNode): Expression { val name = this.frontend.codeOf(node)?.trim() ?: "" - return newDeclaredReferenceExpression(name, newUnknownType(), this.frontend.codeOf(node)) + return newDeclaredReferenceExpression(name, unknownType(), this.frontend.codeOf(node)) } private fun handlePropertyAccessExpression(node: TypeScriptNode): Expression { @@ -179,7 +179,7 @@ class ExpressionHandler(lang: TypeScriptLanguageFrontend) : val name = node.children?.last()?.let { this.frontend.codeOf(it) } ?: "" - return newMemberExpression(name, base, newUnknownType(), ".", this.frontend.codeOf(node)) + return newMemberExpression(name, base, unknownType(), ".", this.frontend.codeOf(node)) } private fun handleCallExpression(node: TypeScriptNode): Expression { diff --git a/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/TypeHandler.kt b/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/TypeHandler.kt index e1f83be9ec..b1dd08906d 100644 --- a/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/TypeHandler.kt +++ b/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/TypeHandler.kt @@ -26,11 +26,12 @@ package de.fraunhofer.aisec.cpg.frontends.typescript import de.fraunhofer.aisec.cpg.frontends.Handler -import de.fraunhofer.aisec.cpg.graph.newUnknownType -import de.fraunhofer.aisec.cpg.graph.parseType -import de.fraunhofer.aisec.cpg.graph.types.PointerType +import de.fraunhofer.aisec.cpg.graph.array +import de.fraunhofer.aisec.cpg.graph.objectType +import de.fraunhofer.aisec.cpg.graph.primitiveType import de.fraunhofer.aisec.cpg.graph.types.Type import de.fraunhofer.aisec.cpg.graph.types.UnknownType +import de.fraunhofer.aisec.cpg.graph.unknownType class TypeHandler(frontend: TypeScriptLanguageFrontend) : Handler( @@ -51,32 +52,32 @@ class TypeHandler(frontend: TypeScriptLanguageFrontend) : "ArrayType" -> return handleArrayType(node) } - return newUnknownType() + return unknownType() } private fun handleArrayType(node: TypeScriptNode): Type { - val type = node.firstChild("TypeReference")?.let { this.handle(it) } ?: newUnknownType() + val type = node.firstChild("TypeReference")?.let { this.handle(it) } ?: unknownType() - return type.reference(PointerType.PointerOrigin.ARRAY) + return type.array() } private fun handleStringKeyword(): Type { - return parseType("string") + return primitiveType("string") } private fun handleNumberKeyword(): Type { - return parseType("number") + return primitiveType("number") } private fun handleAnyKeyword(): Type { - return parseType("any") + return objectType("any") } private fun handleTypeReference(node: TypeScriptNode): Type { node.firstChild("Identifier")?.let { - return parseType(this.frontend.getIdentifierName(node)) + return objectType(this.frontend.getIdentifierName(node)) } - return newUnknownType() + return unknownType() } } diff --git a/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/TypeScriptLanguageFrontend.kt b/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/TypeScriptLanguageFrontend.kt index 8eb7e3eeda..c85ac52ee3 100644 --- a/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/TypeScriptLanguageFrontend.kt +++ b/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/TypeScriptLanguageFrontend.kt @@ -109,8 +109,7 @@ class TypeScriptLanguageFrontend( } override fun typeOf(type: TypeScriptNode): Type { - // reserved for future use - return newUnknownType() + return typeHandler.handleNode(type) } /** diff --git a/cpg-language-typescript/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/TypescriptLanguageFrontendTest.kt b/cpg-language-typescript/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/TypescriptLanguageFrontendTest.kt index 65d8bf2726..5e465efe3f 100644 --- a/cpg-language-typescript/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/TypescriptLanguageFrontendTest.kt +++ b/cpg-language-typescript/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/TypescriptLanguageFrontendTest.kt @@ -27,21 +27,17 @@ package de.fraunhofer.aisec.cpg.frontends.typescript import de.fraunhofer.aisec.cpg.TestUtils import de.fraunhofer.aisec.cpg.assertLocalName -import de.fraunhofer.aisec.cpg.graph.byNameOrNull +import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration -import de.fraunhofer.aisec.cpg.graph.get -import de.fraunhofer.aisec.cpg.graph.parseType import de.fraunhofer.aisec.cpg.graph.statements.DeclarationStatement import de.fraunhofer.aisec.cpg.graph.statements.ReturnStatement import de.fraunhofer.aisec.cpg.graph.statements.expressions.* +import de.fraunhofer.aisec.cpg.graph.types.PointerType import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker import java.nio.file.Path -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertNotNull -import kotlin.test.assertSame +import kotlin.test.* class TypeScriptLanguageFrontendTest { @@ -66,11 +62,11 @@ class TypeScriptLanguageFrontendTest { val someFunction = functions.first() assertLocalName("someFunction", someFunction) - assertEquals(tu.parseType("Number"), someFunction.type) + assertEquals(tu.primitiveType("number"), someFunction.type) val someOtherFunction = functions.last() assertLocalName("someOtherFunction", someOtherFunction) - assertEquals(tu.parseType("Number"), someOtherFunction.type) + assertEquals(tu.primitiveType("number"), someOtherFunction.type) val parameters = someOtherFunction.parameters assertNotNull(parameters) @@ -79,7 +75,7 @@ class TypeScriptLanguageFrontendTest { val parameter = parameters.first() assertLocalName("s", parameter) - assertEquals(tu.parseType("String"), parameter.type) + assertEquals(tu.primitiveType("string"), parameter.type) } @Test @@ -278,7 +274,7 @@ class TypeScriptLanguageFrontendTest { val lastName = user.fields.lastOrNull() assertNotNull(lastName) assertLocalName("lastName", lastName) - assertEquals(tu.parseType("string"), lastName.type) + assertEquals(tu.primitiveType("string"), lastName.type) val usersState = tu.getDeclarationsByName("UsersState", RecordDeclaration::class.java).iterator().next() @@ -291,7 +287,8 @@ class TypeScriptLanguageFrontendTest { val users = usersState.fields.firstOrNull() assertNotNull(users) assertLocalName("users", users) - assertEquals(tu.parseType("User[]"), users.type) + assertIs(users.type) + assertLocalName("User[]", users.type) val usersComponent = tu.getDeclarationsByName("Users", RecordDeclaration::class.java).iterator().next() diff --git a/cpg-language-typescript/src/test/resources/log4j2.xml b/cpg-language-typescript/src/test/resources/log4j2.xml new file mode 100644 index 0000000000..ac6e67063f --- /dev/null +++ b/cpg-language-typescript/src/test/resources/log4j2.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/cpg-language-typescript/src/test/resources/typescript/function.ts b/cpg-language-typescript/src/test/resources/typescript/function.ts index 3620d429bf..ec260d52b8 100644 --- a/cpg-language-typescript/src/test/resources/typescript/function.ts +++ b/cpg-language-typescript/src/test/resources/typescript/function.ts @@ -1,7 +1,7 @@ /* Block comment on a function */ -function someFunction(): Number { +function someFunction(): number { // Comment on a variable const i = someOtherFunction("hello"); @@ -9,6 +9,6 @@ function someFunction(): Number { } // Comment on a Function -function someOtherFunction(s: String): Number { +function someOtherFunction(s: string): number { return s.length; } \ No newline at end of file From 7870db61c1497ce9abb1bbdacc9b26bf2f838070 Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Thu, 27 Jul 2023 17:00:56 +0200 Subject: [PATCH 111/143] Change DFG to use PropertyEdges (#1233) * change the DFG to use PropertyEdges * remove calls where the same DFG edge would be a second time * add properties parameter to addNextDFG and addPrevDFG function to be able to specify properties for the edges * fix spotless issues * Fix after merge --------- Co-authored-by: Christian Banse --- .../de/fraunhofer/aisec/cpg/graph/Node.kt | 81 ++++++++++++++----- .../aisec/cpg/graph/edge/PropertyEdge.kt | 22 ++++- .../aisec/cpg/passes/CXXCallResolverHelper.kt | 5 +- .../aisec/cpg/passes/inference/Inference.kt | 2 - 4 files changed, 81 insertions(+), 29 deletions(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Node.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Node.kt index 7089f436fd..0e3b01f1b8 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Node.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Node.kt @@ -39,6 +39,7 @@ import de.fraunhofer.aisec.cpg.graph.edge.Properties import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.unwrap import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdgeDelegate +import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdgeSetDelegate import de.fraunhofer.aisec.cpg.graph.scopes.GlobalScope import de.fraunhofer.aisec.cpg.graph.scopes.RecordScope import de.fraunhofer.aisec.cpg.graph.scopes.Scope @@ -187,13 +188,25 @@ open class Node : IVisitable, Persistable, LanguageProvider, ScopeProvider this.nextEOGEdges = PropertyEdge.transformIntoOutgoingPropertyEdgeList(value, this) } + /** Incoming data flow edges */ @Relationship(value = "DFG", direction = Relationship.Direction.INCOMING) @PopulatedByPass(DFGPass::class, ControlFlowSensitiveDFGPass::class) - var prevDFG: MutableSet = HashSet() + var prevDFGEdges: MutableList> = mutableListOf() + internal set + /** Virtual property for accessing [prevDFGEdges] without property edges. */ @PopulatedByPass(DFGPass::class, ControlFlowSensitiveDFGPass::class) - @Relationship(value = "DFG") - var nextDFG: MutableSet = HashSet() + var prevDFG: MutableSet by PropertyEdgeSetDelegate(Node::prevDFGEdges, false) + + /** Outgoing data flow edges */ + @PopulatedByPass(DFGPass::class, ControlFlowSensitiveDFGPass::class) + @Relationship(value = "DFG", direction = Relationship.Direction.OUTGOING) + var nextDFGEdges: MutableList> = mutableListOf() + internal set + + /** Virtual property for accessing [nextDFGEdges] without property edges. */ + @PopulatedByPass(DFGPass::class, ControlFlowSensitiveDFGPass::class) + var nextDFG: MutableSet by PropertyEdgeSetDelegate(Node::nextDFGEdges, true) var typedefs: MutableSet = HashSet() @@ -244,21 +257,34 @@ open class Node : IVisitable, Persistable, LanguageProvider, ScopeProvider nextEOGEdges.clear() } - fun addNextDFG(next: Node) { - nextDFG.add(next) - next.prevDFG.add(this) + fun addNextDFG( + next: Node, + properties: MutableMap = EnumMap(Properties::class.java) + ) { + val edge = PropertyEdge(this, next, properties) + nextDFGEdges.add(edge) + next.prevDFGEdges.add(edge) } fun removeNextDFG(next: Node?) { if (next != null) { - nextDFG.remove(next) - next.prevDFG.remove(this) + val thisRemove = + PropertyEdge.findPropertyEdgesByPredicate(nextDFGEdges) { it.end === next } + nextDFGEdges.removeAll(thisRemove) + + val nextRemove = + PropertyEdge.findPropertyEdgesByPredicate(next.prevDFGEdges) { it.start == this } + next.prevDFGEdges.removeAll(nextRemove) } } - fun addPrevDFG(prev: Node) { - prevDFG.add(prev) - prev.nextDFG.add(this) + fun addPrevDFG( + prev: Node, + properties: MutableMap = EnumMap(Properties::class.java) + ) { + val edge = PropertyEdge(prev, this, properties) + prevDFGEdges.add(edge) + prev.nextDFGEdges.add(edge) } fun addPrevCDG(prev: Node) { @@ -267,15 +293,22 @@ open class Node : IVisitable, Persistable, LanguageProvider, ScopeProvider prev.nextCDGEdges.add(edge) } - fun addAllPrevDFG(prev: Collection) { - prevDFG.addAll(prev) - prev.forEach { it.nextDFG.add(this) } + fun addAllPrevDFG( + prev: Collection, + properties: MutableMap = EnumMap(Properties::class.java) + ) { + prev.forEach { addPrevDFG(it, properties.toMutableMap()) } } fun removePrevDFG(prev: Node?) { if (prev != null) { - prevDFG.remove(prev) - prev.nextDFG.remove(this) + val thisRemove = + PropertyEdge.findPropertyEdgesByPredicate(prevDFGEdges) { it.start === prev } + prevDFGEdges.removeAll(thisRemove) + + val prevRemove = + PropertyEdge.findPropertyEdgesByPredicate(prev.nextDFGEdges) { it.end === this } + prev.nextDFGEdges.removeAll(prevRemove) } } @@ -309,15 +342,19 @@ open class Node : IVisitable, Persistable, LanguageProvider, ScopeProvider * further children that have no alternative connection paths to the rest of the graph. */ fun disconnectFromGraph() { - for (n in nextDFG) { - n.prevDFG.remove(this) + for (n in nextDFGEdges) { + val remove = + PropertyEdge.findPropertyEdgesByPredicate(n.end.prevDFGEdges) { it.start == this } + n.end.prevDFGEdges.removeAll(remove) } - nextDFG.clear() + nextDFGEdges.clear() - for (n in prevDFG) { - n.nextDFG.remove(this) + for (n in prevDFGEdges) { + val remove = + PropertyEdge.findPropertyEdgesByPredicate(n.start.nextDFGEdges) { it.end == this } + n.start.nextDFGEdges.removeAll(remove) } - prevDFG.clear() + prevDFGEdges.clear() for (n in nextEOGEdges) { val remove = diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edge/PropertyEdge.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edge/PropertyEdge.kt index 1b5e59d4f2..8e74020131 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edge/PropertyEdge.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edge/PropertyEdge.kt @@ -168,7 +168,7 @@ open class PropertyEdge : Persistable { ): MutableList> { val propertyEdges: MutableList> = ArrayList() for (n in nodes) { - var propertyEdge = PropertyEdge(commonRelationshipNode, n) + val propertyEdge = PropertyEdge(commonRelationshipNode, n) propertyEdge.addProperty(Properties.INDEX, propertyEdges.size) propertyEdges.add(propertyEdge) } @@ -373,3 +373,23 @@ class PropertyEdgeDelegate( } } } + +/** Similar to a [PropertyEdgeDelegate], but with a [Set] instead of [List]. */ +@Transient +class PropertyEdgeSetDelegate( + val edge: KProperty1>>, + val outgoing: Boolean = true +) { + operator fun getValue(thisRef: S, property: KProperty<*>): MutableSet { + return PropertyEdge.unwrap(edge.get(thisRef), outgoing).toMutableSet() + } + + operator fun setValue(thisRef: S, property: KProperty<*>, value: MutableSet) { + if (edge is KMutableProperty1) { + edge.setter.call( + thisRef, + PropertyEdge.transformIntoOutgoingPropertyEdgeList(value.toList(), thisRef as Node) + ) + } + } +} diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CXXCallResolverHelper.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CXXCallResolverHelper.kt index 24ba098441..1030524b39 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CXXCallResolverHelper.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CXXCallResolverHelper.kt @@ -378,10 +378,7 @@ fun applyTemplateInstantiation( // Template. for ((declaration) in initializationSignature) { if (declaration is ParamVariableDeclaration) { - initializationSignature[declaration]?.let { - declaration.addPrevDFG(it) - it.addNextDFG(declaration) // TODO: This should be unnecessary - } + initializationSignature[declaration]?.let { declaration.addPrevDFG(it) } } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/Inference.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/Inference.kt index 76bcd8c09a..6d1af8f6f1 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/Inference.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/Inference.kt @@ -293,8 +293,6 @@ class Inference(val start: Node, override val ctx: TranslationContext) : node .startInference(ctx) .inferNonTypeTemplateParameter(inferredNonTypeIdentifier) - - paramVariableDeclaration.addPrevDFG(node) node.addNextDFG(paramVariableDeclaration) nonTypeCounter++ inferred.addParameter(paramVariableDeclaration) From 470290f355395eb54dd878a5d845335ae6b44a48 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 29 Jul 2023 13:10:16 +0200 Subject: [PATCH 112/143] Update dependency rollup to v3.27.0 (#1267) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- cpg-language-typescript/src/main/nodejs/package-lock.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cpg-language-typescript/src/main/nodejs/package-lock.json b/cpg-language-typescript/src/main/nodejs/package-lock.json index 5a26994aaf..1fe34fba97 100644 --- a/cpg-language-typescript/src/main/nodejs/package-lock.json +++ b/cpg-language-typescript/src/main/nodejs/package-lock.json @@ -13,7 +13,7 @@ "@rollup/plugin-commonjs": "^25.0.3", "@rollup/plugin-node-resolve": "^15.1.0", "@rollup/plugin-typescript": "^11.1.2", - "rollup": "^3.26.3", + "rollup": "^3.27.0", "tslib": "^2.6.0" } }, @@ -370,9 +370,9 @@ } }, "node_modules/rollup": { - "version": "3.26.3", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.26.3.tgz", - "integrity": "sha512-7Tin0C8l86TkpcMtXvQu6saWH93nhG3dGQ1/+l5V2TDMceTxO7kDiK6GzbfLWNNxqJXm591PcEZUozZm51ogwQ==", + "version": "3.27.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.27.0.tgz", + "integrity": "sha512-aOltLCrYZ0FhJDm7fCqwTjIUEVjWjcydKBV/Zeid6Mn8BWgDCUBBWT5beM5ieForYNo/1ZHuGJdka26kvQ3Gzg==", "dev": true, "bin": { "rollup": "dist/bin/rollup" From 5a38c97a69da83383c4629ab31b8e1a23f65a597 Mon Sep 17 00:00:00 2001 From: KuechA <31155350+KuechA@users.noreply.github.com> Date: Sat, 29 Jul 2023 17:21:46 +0200 Subject: [PATCH 113/143] Use assign expressions in LLVM frontend (#1265) --- .../cpg/frontends/llvm/StatementHandler.kt | 76 ++++++++++--------- .../llvm/LLVMIRLanguageFrontendTest.kt | 74 +++++++++++------- 2 files changed, 86 insertions(+), 64 deletions(-) diff --git a/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/StatementHandler.kt b/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/StatementHandler.kt index c5fe499ad1..bb741ce560 100644 --- a/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/StatementHandler.kt +++ b/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/StatementHandler.kt @@ -506,15 +506,15 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : * of a de-referenced pointer in C like `*a = 1`. */ private fun handleStore(instr: LLVMValueRef): Statement { - val binOp = newBinaryOperator("=", frontend.codeOf(instr)) - val dereference = newUnaryOperator("*", postfix = false, prefix = true, "") dereference.input = frontend.getOperandValueAtIndex(instr, 1) - binOp.lhs = dereference - binOp.rhs = frontend.getOperandValueAtIndex(instr, 0) - - return binOp + return newAssignExpression( + "=", + listOf(dereference), + listOf(frontend.getOperandValueAtIndex(instr, 0)), + frontend.codeOf(instr) + ) } /** @@ -707,10 +707,8 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : } val compoundStatement = newCompoundStatement(frontend.codeOf(instr)) - - val assignment = newBinaryOperator("=", frontend.codeOf(instr)) - assignment.lhs = base - assignment.rhs = valueToSet + val assignment = + newAssignExpression("=", listOf(base), listOf(valueToSet), frontend.codeOf(instr)) compoundStatement.addStatement(copy) compoundStatement.addStatement(assignment) @@ -848,9 +846,7 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : val ptrDerefAssign = newUnaryOperator("*", false, true, instrStr) ptrDerefAssign.input = frontend.getOperandValueAtIndex(instr, 0) - val assignment = newBinaryOperator("=", instrStr) - assignment.lhs = ptrDerefAssign - assignment.rhs = value + val assignment = newAssignExpression("=", listOf(ptrDerefAssign), listOf(value), instrStr) val ifStatement = newIfStatement(instrStr) ifStatement.condition = cmpExpr @@ -872,7 +868,7 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : val ptr = frontend.getOperandValueAtIndex(instr, 0) val value = frontend.getOperandValueAtIndex(instr, 1) val ty = value.type - val exchOp = newBinaryOperator("=", instrStr) + val exchOp = newAssignExpression("=", code = instrStr) exchOp.name = Name("atomicrmw") val ptrDeref = newUnaryOperator("*", false, true, instrStr) @@ -880,31 +876,31 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : val ptrDerefExch = newUnaryOperator("*", false, true, instrStr) ptrDerefExch.input = frontend.getOperandValueAtIndex(instr, 0) - exchOp.lhs = ptrDerefExch + exchOp.lhs = listOf(ptrDerefExch) when (operation) { LLVMAtomicRMWBinOpXchg -> { - exchOp.rhs = value + exchOp.rhs = listOf(value) } LLVMAtomicRMWBinOpFAdd, LLVMAtomicRMWBinOpAdd -> { val binaryOperator = newBinaryOperator("+", instrStr) binaryOperator.lhs = ptrDeref binaryOperator.rhs = value - exchOp.rhs = binaryOperator + exchOp.rhs = listOf(binaryOperator) } LLVMAtomicRMWBinOpFSub, LLVMAtomicRMWBinOpSub -> { val binaryOperator = newBinaryOperator("-", instrStr) binaryOperator.lhs = ptrDeref binaryOperator.rhs = value - exchOp.rhs = binaryOperator + exchOp.rhs = listOf(binaryOperator) } LLVMAtomicRMWBinOpAnd -> { val binaryOperator = newBinaryOperator("&", instrStr) binaryOperator.lhs = ptrDeref binaryOperator.rhs = value - exchOp.rhs = binaryOperator + exchOp.rhs = listOf(binaryOperator) } LLVMAtomicRMWBinOpNand -> { val binaryOperator = newBinaryOperator("|", instrStr) @@ -912,19 +908,19 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : binaryOperator.rhs = value val unaryOperator = newUnaryOperator("~", false, true, instrStr) unaryOperator.input = binaryOperator - exchOp.rhs = unaryOperator + exchOp.rhs = listOf(unaryOperator) } LLVMAtomicRMWBinOpOr -> { val binaryOperator = newBinaryOperator("|", instrStr) binaryOperator.lhs = ptrDeref binaryOperator.rhs = value - exchOp.rhs = binaryOperator + exchOp.rhs = listOf(binaryOperator) } LLVMAtomicRMWBinOpXor -> { val binaryOperator = newBinaryOperator("^", instrStr) binaryOperator.lhs = ptrDeref binaryOperator.rhs = value - exchOp.rhs = binaryOperator + exchOp.rhs = listOf(binaryOperator) } LLVMAtomicRMWBinOpMax, LLVMAtomicRMWBinOpMin -> { @@ -947,7 +943,7 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : value, ty, ) - exchOp.rhs = conditional + exchOp.rhs = listOf(conditional) } LLVMAtomicRMWBinOpUMax, LLVMAtomicRMWBinOpUMin -> { @@ -977,7 +973,7 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : value, ty, ) - exchOp.rhs = conditional + exchOp.rhs = listOf(conditional) } else -> { throw TranslationException("LLVMAtomicRMWBinOp $operation not supported") @@ -1260,10 +1256,14 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : ) arrayExpr.subscriptExpression = frontend.getOperandValueAtIndex(instr, 2) - val binaryExpr = newBinaryOperator("=", instrStr) - binaryExpr.lhs = arrayExpr - binaryExpr.rhs = frontend.getOperandValueAtIndex(instr, 1) - compoundStatement.addStatement(binaryExpr) + val assignExpr = + newAssignExpression( + "=", + listOf(arrayExpr), + listOf(frontend.getOperandValueAtIndex(instr, 1)), + instrStr + ) + compoundStatement.addStatement(assignExpr) return compoundStatement } @@ -1440,13 +1440,19 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : for ((l, r) in labelMap) { // Now, we iterate over all the basic blocks and add an assign statement. - val assignment = newBinaryOperator("=", code) - assignment.rhs = r - assignment.lhs = newDeclaredReferenceExpression(varName, type, code) - (assignment.lhs as DeclaredReferenceExpression).type = type - (assignment.lhs as DeclaredReferenceExpression).unregisterTypeListener(assignment) - assignment.unregisterTypeListener(assignment.lhs as DeclaredReferenceExpression) - (assignment.lhs as DeclaredReferenceExpression).refersTo = declaration + val assignment = + newAssignExpression( + "=", + listOf(newDeclaredReferenceExpression(varName, type, code)), + listOf(r), + code + ) + (assignment.lhs.first() as DeclaredReferenceExpression).type = type + (assignment.lhs.first() as DeclaredReferenceExpression).unregisterTypeListener( + assignment + ) + assignment.unregisterTypeListener(assignment.lhs.first() as DeclaredReferenceExpression) + (assignment.lhs.first() as DeclaredReferenceExpression).refersTo = declaration flatAST.add(assignment) val basicBlock = l.subStatement as? CompoundStatement diff --git a/cpg-language-llvm/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/LLVMIRLanguageFrontendTest.kt b/cpg-language-llvm/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/LLVMIRLanguageFrontendTest.kt index 0d98a6852b..97710bb3a0 100644 --- a/cpg-language-llvm/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/LLVMIRLanguageFrontendTest.kt +++ b/cpg-language-llvm/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/LLVMIRLanguageFrontendTest.kt @@ -388,12 +388,14 @@ class LLVMIRLanguageFrontendTest { assertLocalName("ptr", (decl.initializer as UnaryOperator).input) // Check that the replacement equals *ptr = *ptr + 1 - val replacement = (atomicrmwStatement.statements[1] as BinaryOperator) + val replacement = (atomicrmwStatement.statements[1] as AssignExpression) + assertEquals(1, replacement.lhs.size) + assertEquals(1, replacement.rhs.size) assertEquals("=", replacement.operatorCode) - assertEquals("*", (replacement.lhs as UnaryOperator).operatorCode) - assertLocalName("ptr", (replacement.lhs as UnaryOperator).input) + assertEquals("*", (replacement.lhs.first() as UnaryOperator).operatorCode) + assertLocalName("ptr", (replacement.lhs.first() as UnaryOperator).input) // Check that the rhs is equal to *ptr + 1 - val add = replacement.rhs as BinaryOperator + val add = replacement.rhs.first() as BinaryOperator assertEquals("+", add.operatorCode) assertEquals("*", (add.lhs as UnaryOperator).operatorCode) assertLocalName("ptr", (add.lhs as UnaryOperator).input) @@ -445,12 +447,14 @@ class LLVMIRLanguageFrontendTest { assertLocalName("ptr", (ifExpr.lhs as UnaryOperator).input) assertEquals(5L, (ifExpr.rhs as Literal<*>).value as Long) - val thenExpr = ifStatement.thenStatement as BinaryOperator + val thenExpr = ifStatement.thenStatement as AssignExpression + assertEquals(1, thenExpr.lhs.size) + assertEquals(1, thenExpr.rhs.size) assertEquals("=", thenExpr.operatorCode) - assertEquals("*", (thenExpr.lhs as UnaryOperator).operatorCode) - assertLocalName("ptr", (thenExpr.lhs as UnaryOperator).input) - assertLocalName("old", thenExpr.rhs as DeclaredReferenceExpression) - assertLocalName("old", (thenExpr.rhs as DeclaredReferenceExpression).refersTo) + assertEquals("*", (thenExpr.lhs.first() as UnaryOperator).operatorCode) + assertLocalName("ptr", (thenExpr.lhs.first() as UnaryOperator).input) + assertLocalName("old", thenExpr.rhs.first() as DeclaredReferenceExpression) + assertLocalName("old", (thenExpr.rhs.first() as DeclaredReferenceExpression).refersTo) } @Test @@ -596,17 +600,19 @@ class LLVMIRLanguageFrontendTest { assertEquals("i32*", alloca.type.typeName) // store i32 3, i32* %ptr - val store = main.bodyOrNull() + val store = main.bodyOrNull() assertNotNull(store) assertEquals("=", store.operatorCode) - val dereferencePtr = store.lhs as? UnaryOperator + assertEquals(1, store.lhs.size) + val dereferencePtr = store.lhs.first() as? UnaryOperator assertNotNull(dereferencePtr) assertEquals("*", dereferencePtr.operatorCode) assertEquals("i32", dereferencePtr.type.typeName) assertSame(ptr, (dereferencePtr.input as? DeclaredReferenceExpression)?.refersTo) - val value = store.rhs as? Literal<*> + assertEquals(1, store.rhs.size) + val value = store.rhs.first() as? Literal<*> assertNotNull(value) assertEquals(3L, value.value) assertEquals("i32", value.type.typeName) @@ -655,12 +661,14 @@ class LLVMIRLanguageFrontendTest { assertEquals("literal_i32_i8", copyStatement.type.typeName) // Now, we set b.field_1 to 7 - val assignment = (compoundStatement.statements[1] as BinaryOperator) + val assignment = (compoundStatement.statements[1] as AssignExpression) assertEquals("=", assignment.operatorCode) - assertLocalName("b", (assignment.lhs as MemberExpression).base) - assertEquals(".", (assignment.lhs as MemberExpression).operatorCode) - assertLocalName("field_1", assignment.lhs as MemberExpression) - assertEquals(7L, (assignment.rhs as Literal<*>).value as Long) + assertEquals(1, assignment.lhs.size) + assertEquals(1, assignment.rhs.size) + assertLocalName("b", (assignment.lhs.first() as MemberExpression).base) + assertEquals(".", (assignment.lhs.first() as MemberExpression).operatorCode) + assertLocalName("field_1", assignment.lhs.first() as MemberExpression) + assertEquals(7L, (assignment.rhs.first() as Literal<*>).value as Long) } @Test @@ -758,13 +766,15 @@ class LLVMIRLanguageFrontendTest { val thenStmt = ifStatement.thenStatement as? CompoundStatement assertNotNull(thenStmt) assertEquals(3, thenStmt.statements.size) - assertNotNull(thenStmt.statements[1] as? BinaryOperator) + assertNotNull(thenStmt.statements[1] as? AssignExpression) val aDecl = (thenStmt.statements[0] as DeclarationStatement).singleDeclaration as VariableDeclaration - val thenY = thenStmt.statements[1] as BinaryOperator - assertSame(aDecl, (thenY.rhs as DeclaredReferenceExpression).refersTo) - assertSame(yDecl, (thenY.lhs as DeclaredReferenceExpression).refersTo) + val thenY = thenStmt.statements[1] as AssignExpression + assertEquals(1, thenY.lhs.size) + assertEquals(1, thenY.rhs.size) + assertSame(aDecl, (thenY.rhs.first() as DeclaredReferenceExpression).refersTo) + assertSame(yDecl, (thenY.lhs.first() as DeclaredReferenceExpression).refersTo) val elseStmt = ifStatement.elseStatement as? CompoundStatement assertNotNull(elseStmt) @@ -772,10 +782,12 @@ class LLVMIRLanguageFrontendTest { val bDecl = (elseStmt.statements[0] as DeclarationStatement).singleDeclaration as VariableDeclaration - assertNotNull(elseStmt.statements[1] as? BinaryOperator) - val elseY = elseStmt.statements[1] as BinaryOperator - assertSame(bDecl, (elseY.rhs as DeclaredReferenceExpression).refersTo) - assertSame(yDecl, (elseY.lhs as DeclaredReferenceExpression).refersTo) + assertNotNull(elseStmt.statements[1] as? AssignExpression) + val elseY = elseStmt.statements[1] as AssignExpression + assertEquals(1, elseY.lhs.size) + assertEquals(1, elseY.lhs.size) + assertSame(bDecl, (elseY.rhs.first() as DeclaredReferenceExpression).refersTo) + assertSame(yDecl, (elseY.lhs.first() as DeclaredReferenceExpression).refersTo) val continueBlock = (thenStmt.statements[2] as? GotoStatement)?.targetLabel?.subStatement @@ -844,19 +856,23 @@ class LLVMIRLanguageFrontendTest { assertEquals("y", (yModInit.initializer as? DeclaredReferenceExpression)?.name?.localName) assertSame(origY, (yModInit.initializer as? DeclaredReferenceExpression)?.refersTo) // Now, test the modification of yMod[3] = 8 - val yMod = ((mainBody.statements[3] as CompoundStatement).statements[1] as? BinaryOperator) + val yMod = + ((mainBody.statements[3] as CompoundStatement).statements[1] as? AssignExpression) assertNotNull(yMod) + assertEquals(1, yMod.lhs.size) + assertEquals(1, yMod.rhs.size) assertEquals( 3L, - ((yMod.lhs as? ArraySubscriptionExpression)?.subscriptExpression as? Literal<*>)?.value + ((yMod.lhs.first() as? ArraySubscriptionExpression)?.subscriptExpression as? Literal<*>) + ?.value ) assertSame( yModInit, - ((yMod.lhs as? ArraySubscriptionExpression)?.arrayExpression + ((yMod.lhs.first() as? ArraySubscriptionExpression)?.arrayExpression as? DeclaredReferenceExpression) ?.refersTo ) - assertEquals(8L, (yMod.rhs as? Literal<*>)?.value) + assertEquals(8L, (yMod.rhs.first() as? Literal<*>)?.value) // Test the last shufflevector instruction which does not contain constant as initializers. val shuffledInit = From 6285f0b3682d578033d8d82d505a99ae2a5d96fb Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Mon, 31 Jul 2023 21:39:33 +0200 Subject: [PATCH 114/143] New Go language frontend (#1249) This PR contains a complete re-write of the Go language frontend based on JNA instead of a pure Go implementation. While this has a slight performance drawback (I am working on providing some numbers), it has infinitly better debugging capabilities. The handlers and most of the frontend itself is written in Kotlin using our classes and we than use a JNA bridge to a C API wrapper around the Go `go/ast` package. Depends on #1199 Fixes #1256 Fixes #1257 Fixes #1258 Fixes #1259 --- .github/workflows/build.yml | 8 +- .../graph/declarations/DeclarationSequence.kt | 7 +- .../cpg/passes/EvaluationOrderGraphPass.kt | 51 +- cpg-language-go/build.gradle.kts | 4 + .../src/main/golang/basic_types.go | 86 - cpg-language-go/src/main/golang/build.sh | 9 +- .../src/main/golang/declarations.go | 187 -- .../src/main/golang/expressions.go | 266 --- .../golang/frontend/declaration_builder.go | 97 - .../golang/frontend/expression_builder.go | 154 -- .../src/main/golang/frontend/frontend.go | 203 --- .../src/main/golang/frontend/handler.go | 1571 ----------------- .../main/golang/frontend/statement_builder.go | 93 - .../src/main/golang/frontend/type_builder.go | 91 - cpg-language-go/src/main/golang/go.mod | 4 +- cpg-language-go/src/main/golang/go.sum | 18 +- cpg-language-go/src/main/golang/helper.go | 58 - cpg-language-go/src/main/golang/language.go | 75 - .../src/main/golang/lib/cpg/main.go | 883 ++++++++- cpg-language-go/src/main/golang/location.go | 62 - cpg-language-go/src/main/golang/node.go | 141 -- cpg-language-go/src/main/golang/scope.go | 92 - cpg-language-go/src/main/golang/statements.go | 118 -- cpg-language-go/src/main/golang/types.go | 149 -- .../frontends/golang/DeclarationHandler.kt | 214 +++ .../cpg/frontends/golang/ExpressionHandler.kt | 380 ++++ .../aisec/cpg/frontends/golang/GoHandler.kt | 80 + .../frontends/golang/GoLanguageFrontend.kt | 273 ++- .../cpg/frontends/golang/GoStandardLibrary.kt | 1060 +++++++++++ .../frontends/golang/SpecificationHandler.kt | 187 ++ .../cpg/frontends/golang/StatementHandler.kt | 325 ++++ .../cpg/passes/GoEvaluationOrderGraphPass.kt | 102 ++ .../aisec/cpg/passes/GoExtraPass.kt | 6 + .../golang/GoLanguageFrontendTest.kt | 16 +- .../cpg/frontends/golang/StatementTest.kt | 113 ++ .../src/test/resources/golang/branch.go | 26 + .../src/test/resources/golang/defer.go | 28 + cpg-language-go/src/test/resources/log4j2.xml | 2 +- 38 files changed, 3630 insertions(+), 3609 deletions(-) delete mode 100644 cpg-language-go/src/main/golang/basic_types.go delete mode 100644 cpg-language-go/src/main/golang/declarations.go delete mode 100644 cpg-language-go/src/main/golang/expressions.go delete mode 100644 cpg-language-go/src/main/golang/frontend/declaration_builder.go delete mode 100644 cpg-language-go/src/main/golang/frontend/expression_builder.go delete mode 100644 cpg-language-go/src/main/golang/frontend/frontend.go delete mode 100644 cpg-language-go/src/main/golang/frontend/handler.go delete mode 100644 cpg-language-go/src/main/golang/frontend/statement_builder.go delete mode 100644 cpg-language-go/src/main/golang/frontend/type_builder.go delete mode 100644 cpg-language-go/src/main/golang/helper.go delete mode 100644 cpg-language-go/src/main/golang/language.go delete mode 100644 cpg-language-go/src/main/golang/location.go delete mode 100644 cpg-language-go/src/main/golang/node.go delete mode 100644 cpg-language-go/src/main/golang/scope.go delete mode 100644 cpg-language-go/src/main/golang/statements.go delete mode 100644 cpg-language-go/src/main/golang/types.go create mode 100644 cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/DeclarationHandler.kt create mode 100644 cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/ExpressionHandler.kt create mode 100644 cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoHandler.kt create mode 100644 cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoStandardLibrary.kt create mode 100644 cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/SpecificationHandler.kt create mode 100644 cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/StatementHandler.kt create mode 100644 cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/GoEvaluationOrderGraphPass.kt create mode 100644 cpg-language-go/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/StatementTest.kt create mode 100644 cpg-language-go/src/test/resources/golang/branch.go create mode 100644 cpg-language-go/src/test/resources/golang/defer.go diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b5031ebacb..f462b3b482 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -19,16 +19,10 @@ jobs: runs-on: macos-latest steps: - uses: actions/checkout@v3 - with: - fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of SonarQube analysis - name: Setup Go uses: actions/setup-go@v4 with: - go-version: 1.18 - - uses: actions/setup-java@v3 - with: - distribution: "zulu" - java-version: "17" + go-version: "1.20" - name: Build run: | cd cpg-language-go/src/main/golang diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/DeclarationSequence.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/DeclarationSequence.kt index d4bf050121..60261034dc 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/DeclarationSequence.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/DeclarationSequence.kt @@ -47,8 +47,9 @@ class DeclarationSequence : Declaration(), DeclarationHolder { for (declarationChild in declaration.children) { addIfNotContains(childEdges, declarationChild) } + } else { + addIfNotContains(childEdges, declaration) } - addIfNotContains(childEdges, declaration) } fun asList(): List { @@ -62,6 +63,10 @@ class DeclarationSequence : Declaration(), DeclarationHolder { return childEdges[0].end } + operator fun plusAssign(declaration: Declaration) { + return addDeclaration(declaration) + } + override val declarations: List get() = children } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt index ff9e6b3349..a23e07bca4 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt @@ -40,6 +40,7 @@ import de.fraunhofer.aisec.cpg.graph.types.Type import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker import de.fraunhofer.aisec.cpg.helpers.Util import de.fraunhofer.aisec.cpg.passes.order.DependsOn +import de.fraunhofer.aisec.cpg.passes.order.ReplacePass import java.util.* import org.slf4j.LoggerFactory @@ -290,7 +291,7 @@ open class EvaluationOrderGraphPass(ctx: TranslationContext) : TranslationUnitPa pushToEOG(node) } - protected fun handleFunctionDeclaration(node: FunctionDeclaration) { + protected open fun handleFunctionDeclaration(node: FunctionDeclaration) { // reset EOG currentPredecessors.clear() var needToLeaveRecord = false @@ -525,25 +526,43 @@ open class EvaluationOrderGraphPass(ctx: TranslationContext) : TranslationUnitPa } protected fun handleUnaryOperator(node: UnaryOperator) { + // TODO(oxisto): These operator codes are highly language specific and might be more suited + // to be handled differently (see https://github.com/Fraunhofer-AISEC/cpg/issues/1161) + if (node.operatorCode == "throw") { + handleThrowOperator(node) + } else { + handleUnspecificUnaryOperator(node) + } + } + + protected fun handleThrowOperator(node: UnaryOperator) { val input = node.input createEOG(input) - if (node.operatorCode == "throw") { - val catchingScope = - scopeManager.firstScopeOrNull { scope -> - scope is TryScope || scope is FunctionScope - } - val throwType = input.type - pushToEOG(node) - if (catchingScope is TryScope) { - catchingScope.catchesOrRelays[throwType] = ArrayList(currentPredecessors) - } else if (catchingScope is FunctionScope) { - catchingScope.catchesOrRelays[throwType] = ArrayList(currentPredecessors) - } - currentPredecessors.clear() - } else { - pushToEOG(node) + val catchingScope = + scopeManager.firstScopeOrNull { scope -> scope is TryScope || scope is FunctionScope } + + val throwType = input.type + pushToEOG(node) + if (catchingScope is TryScope) { + catchingScope.catchesOrRelays[throwType] = ArrayList(currentPredecessors) + } else if (catchingScope is FunctionScope) { + catchingScope.catchesOrRelays[throwType] = ArrayList(currentPredecessors) } + currentPredecessors.clear() + } + + /** + * This function handles all regular unary operators that do not receive any special handling + * (such as [handleThrowOperator]). This gives language frontends a chance to override this + * function using [ReplacePass], handle specific operators on their own and delegate the rest to + * this function. + */ + protected open fun handleUnspecificUnaryOperator(node: UnaryOperator) { + val input = node.input + createEOG(input) + + pushToEOG(node) } protected fun handleCompoundStatementExpression(node: CompoundStatementExpression) { diff --git a/cpg-language-go/build.gradle.kts b/cpg-language-go/build.gradle.kts index f3b4b7a87c..fc19473bca 100644 --- a/cpg-language-go/build.gradle.kts +++ b/cpg-language-go/build.gradle.kts @@ -39,6 +39,10 @@ publishing { } } +dependencies { + implementation("net.java.dev.jna:jna:5.13.0") +} + if (!project.hasProperty("skipGoBuild")) { val compileGolang = tasks.register("compileGolang") { doLast { diff --git a/cpg-language-go/src/main/golang/basic_types.go b/cpg-language-go/src/main/golang/basic_types.go deleted file mode 100644 index b302ff2134..0000000000 --- a/cpg-language-go/src/main/golang/basic_types.go +++ /dev/null @@ -1,86 +0,0 @@ -// -// Copyright (c) 2021, 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 cpg - -import ( - "log" - - "tekao.net/jnigi" -) - -func NewString(s string) *jnigi.ObjectRef { - if s != "" { - o, err := env.NewObject("java/lang/String", []byte(s)) - if err != nil { - log.Fatal(err) - } - - return o - } else { - return jnigi.NewObjectRef("java/lang/String") - } -} - -func NewCharSequence(s string) *jnigi.ObjectRef { - o, err := env.NewObject("java/lang/String", []byte(s)) - if err != nil { - log.Fatal(err) - - } - - return o.Cast("java/lang/CharSequence") -} - -func NewBoolean(b bool) *jnigi.ObjectRef { - // TODO: Use Boolean.valueOf - o, err := env.NewObject("java/lang/Boolean", b) - if err != nil { - log.Fatal(err) - } - - return o -} - -func NewInteger(i int) *jnigi.ObjectRef { - // TODO: Use Integer.valueOf - o, err := env.NewObject("java/lang/Integer", i) - if err != nil { - log.Fatal(err) - } - - return o -} - -func NewDouble(d float64) *jnigi.ObjectRef { - // TODO: Use Integer.valueOf - o, err := env.NewObject("java/lang/Double", d) - if err != nil { - log.Fatal(err) - } - - return o -} diff --git a/cpg-language-go/src/main/golang/build.sh b/cpg-language-go/src/main/golang/build.sh index 03821e7457..acf306a320 100755 --- a/cpg-language-go/src/main/golang/build.sh +++ b/cpg-language-go/src/main/golang/build.sh @@ -8,16 +8,9 @@ else EXTENSION="so" fi -if [ "$JAVA_HOME" == "" ] -then - JAVA_HOME=`/usr/libexec/java_home` -fi - -export CGO_CFLAGS="-I${JAVA_HOME}/include -I/${JAVA_HOME}/include/${ARCH}" - CGO_ENABLED=1 GOARCH=amd64 go build -buildmode=c-shared -o ../resources/libcpgo-amd64.${EXTENSION} lib/cpg/main.go if [ $ARCH == "darwin" ] then -CGO_ENABLED=1 GOARCH=arm64 go build -buildmode=c-shared -o ../resources/libcpgo-arm64.${EXTENSION} lib/cpg/main.go + CGO_ENABLED=1 GOARCH=arm64 go build -buildmode=c-shared -o ../resources/libcpgo-arm64.${EXTENSION} lib/cpg/main.go fi diff --git a/cpg-language-go/src/main/golang/declarations.go b/cpg-language-go/src/main/golang/declarations.go deleted file mode 100644 index 13d64c2efe..0000000000 --- a/cpg-language-go/src/main/golang/declarations.go +++ /dev/null @@ -1,187 +0,0 @@ -// -// Copyright (c) 2021, 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 cpg - -import ( - "log" - "runtime/debug" - - "tekao.net/jnigi" -) - -type Declaration Node -type IncludeDeclaration jnigi.ObjectRef -type TranslationUnitDeclaration Declaration -type FunctionDeclaration Declaration -type MethodDeclaration FunctionDeclaration -type RecordDeclaration Declaration -type FieldDeclaration Declaration -type VariableDeclaration Declaration -type ParamVariableDeclaration Declaration -type NamespaceDeclaration Declaration - -const DeclarationsPackage = GraphPackage + "/declarations" -const DeclarationClass = DeclarationsPackage + "/Declaration" -const RecordDeclarationClass = DeclarationsPackage + "/RecordDeclaration" -const FunctionDeclarationClass = DeclarationsPackage + "/FunctionDeclaration" -const VariableDeclarationClass = DeclarationsPackage + "/VariableDeclaration" -const IncludeDeclarationClass = DeclarationsPackage + "/IncludeDeclaration" -const TranslationUnitDeclarationClass = DeclarationsPackage + "/TranslationUnitDeclaration" - -func (n *IncludeDeclaration) SetFilename(s string) error { - return (*jnigi.ObjectRef)(n).SetField(env, "filename", NewString(s)) -} - -func (f *FunctionDeclaration) SetType(t *Type) { - (*HasType)(f).SetType(t) -} - -func (f *FunctionDeclaration) SetReturnTypes(types []*Type) (err error) { - var list *jnigi.ObjectRef - - list, err = ListOf(types) - if err != nil { - return err - } - - var funcDecl = (*jnigi.ObjectRef)(f).Cast(FunctionDeclarationClass) - - err = (*jnigi.ObjectRef)(funcDecl).CallMethod(env, "setReturnTypes", nil, list.Cast("java/util/List")) - - return -} - -func (f *FunctionDeclaration) AddParameter(p *ParamVariableDeclaration) { - (*jnigi.ObjectRef)(f).CallMethod(env, "addParameter", nil, (*jnigi.ObjectRef)(p)) -} - -func (f *FunctionDeclaration) SetBody(s *Statement) (err error) { - err = (*jnigi.ObjectRef)(f).CallMethod(env, "setBody", nil, (*jnigi.ObjectRef)(s).Cast(StatementClass)) - - return -} - -func (n *NamespaceDeclaration) SetPath(path string) (err error) { - err = (*jnigi.ObjectRef)(n).CallMethod(env, "setPath", nil, NewString(path)) - - return -} - -func (m *MethodDeclaration) SetType(t *Type) { - (*HasType)(m).SetType(t) -} - -func (m *MethodDeclaration) SetReceiver(v *VariableDeclaration) error { - return (*jnigi.ObjectRef)(m).SetField(env, "receiver", (*jnigi.ObjectRef)(v)) -} - -func (m *MethodDeclaration) GetReceiver() *VariableDeclaration { - o := jnigi.NewObjectRef(VariableDeclarationClass) - err := (*jnigi.ObjectRef)(m).GetField(env, "receiver", o) - - if err != nil { - log.Fatal(err) - debug.PrintStack() - } - - return (*VariableDeclaration)(o) -} - -func (p *ParamVariableDeclaration) SetType(t *Type) { - (*HasType)(p).SetType(t) -} - -func (p *ParamVariableDeclaration) SetVariadic(b bool) (err error) { - err = (*jnigi.ObjectRef)(p).CallMethod(env, "setVariadic", nil, b) - - return -} - -func (f *FieldDeclaration) SetType(t *Type) { - (*HasType)(f).SetType(t) -} - -func (v *VariableDeclaration) SetType(t *Type) { - (*HasType)(v).SetType(t) -} - -func (v *VariableDeclaration) IsNil() bool { - return (*jnigi.ObjectRef)(v).IsNil() -} - -func (v *VariableDeclaration) SetInitializer(e *Expression) (err error) { - err = (*jnigi.ObjectRef)(v).CallMethod(env, "setInitializer", nil, (*jnigi.ObjectRef)(e).Cast(ExpressionClass)) - - return -} - -func (v *VariableDeclaration) Declaration() *Declaration { - return (*Declaration)(v) -} - -func (t *TranslationUnitDeclaration) GetIncludeByName(s string) *IncludeDeclaration { - var i = jnigi.NewObjectRef(IncludeDeclarationClass) - err := (*jnigi.ObjectRef)(t).CallMethod(env, "getIncludeByName", i, NewString(s)) - if err != nil { - log.Fatal(err) - debug.PrintStack() - } - - return (*IncludeDeclaration)(i) -} - -func (r *RecordDeclaration) SetKind(s string) error { - return (*jnigi.ObjectRef)(r).SetField(env, "kind", NewString(s)) -} - -func (r *RecordDeclaration) AddMethod(m *MethodDeclaration) (err error) { - err = (*jnigi.ObjectRef)(r).CallMethod(env, "addMethod", nil, (*jnigi.ObjectRef)(m)) - - return -} - -func (r *RecordDeclaration) AddSuperClass(t *Type) (err error) { - (*jnigi.ObjectRef)(r).CallMethod(env, "addSuperClass", nil, t) - - return -} - -func (r *RecordDeclaration) IsNil() bool { - return (*jnigi.ObjectRef)(r).IsNil() -} - -func (r *MethodDeclaration) IsNil() bool { - return (*jnigi.ObjectRef)(r).IsNil() -} - -func (r *CompoundStatement) IsNil() bool { - return (*jnigi.ObjectRef)(r).IsNil() -} - -func (c *CaseStatement) SetCaseExpression(e *Expression) error { - return (*jnigi.ObjectRef)(c).SetField(env, "caseExpression", (*jnigi.ObjectRef)(e).Cast(ExpressionClass)) -} diff --git a/cpg-language-go/src/main/golang/expressions.go b/cpg-language-go/src/main/golang/expressions.go deleted file mode 100644 index 0486da9036..0000000000 --- a/cpg-language-go/src/main/golang/expressions.go +++ /dev/null @@ -1,266 +0,0 @@ -// -// Copyright (c) 2021, 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 cpg - -import ( - "log" - - "tekao.net/jnigi" -) - -type Expression Statement - -const ExpressionsPackage = GraphPackage + "/statements/expressions" -const ExpressionClass = ExpressionsPackage + "/Expression" -const MemberExpressionClass = ExpressionsPackage + "/MemberExpression" - -func (e *Expression) ConvertToGo(o *jnigi.ObjectRef) error { - *e = (Expression)(*o) - return nil -} - -func (e *Expression) GetClassName() string { - return ExpressionClass -} - -func (e *Expression) Cast(className string) *jnigi.ObjectRef { - return (*jnigi.ObjectRef)(e).Cast(className) -} - -func (e *Expression) IsArray() bool { - return false -} - -type CallExpression Expression -type CastExpression Expression -type NewExpression Expression -type ArrayCreationExpression Expression -type ArraySubscriptionExpression Expression -type RangeExpression Expression -type ConstructExpression Expression -type InitializerListExpression Expression -type MemberCallExpression CallExpression -type MemberExpression Expression -type BinaryOperator Expression -type AssignExpression Expression -type UnaryOperator Expression -type Literal Expression -type DeclaredReferenceExpression Expression -type KeyValueExpression Expression -type LambdaExpression Expression -type ProblemExpression Expression - -func (e *Expression) SetType(t *Type) { - if t != nil && !t.IsNil() { - (*HasType)(e).SetType(t) - } -} - -func (c *CallExpression) SetFqn(s string) { - (*jnigi.ObjectRef)(c).SetField(env, "fqn", NewString(s)) -} - -func (c *CastExpression) SetExpression(e *Expression) { - (*jnigi.ObjectRef)(c).CallMethod(env, "setExpression", nil, (*jnigi.ObjectRef)(e).Cast(ExpressionClass)) -} - -func (c *CastExpression) SetCastType(t *Type) { - (*jnigi.ObjectRef)(c).CallMethod(env, "setCastType", nil, t) -} - -func (c *MemberCallExpression) SetFqn(s string) { - (*CallExpression)(c).SetFqn(s) -} - -func (m *MemberCallExpression) SetBase(e *Expression) { - (*jnigi.ObjectRef)(m).SetField(env, "base", (*jnigi.ObjectRef)(e).Cast(ExpressionClass)) -} - -func (m *MemberCallExpression) SetMember(n *Node) { - (*jnigi.ObjectRef)(m).SetField(env, "member", (*jnigi.ObjectRef)(n).Cast(NodeClass)) -} - -func (m *MemberCallExpression) Expression() *Expression { - return (*Expression)(m) -} - -func (m *MemberExpression) SetBase(e *Expression) { - (*jnigi.ObjectRef)(m).SetField(env, "base", (*jnigi.ObjectRef)(e).Cast(ExpressionClass)) -} - -func (m *MemberExpression) GetBase() *Expression { - var expr Expression - err := (*jnigi.ObjectRef)(m).GetField(env, "base", &expr) - if err != nil { - log.Fatal(err) - } - - return &expr -} - -func (e *Expression) GetName() *Name { - return (*Node)(e).GetName() -} - -func (r *DeclaredReferenceExpression) Expression() *Expression { - return (*Expression)(r) -} - -func (r *DeclaredReferenceExpression) Node() *Node { - return (*Node)(r) -} - -func (c *CallExpression) AddArgument(e *Expression) { - (*jnigi.ObjectRef)(c).CallMethod(env, "addArgument", nil, (*jnigi.ObjectRef)(e).Cast(ExpressionClass)) -} - -func (b *BinaryOperator) SetLHS(e *Expression) { - (*jnigi.ObjectRef)(b).CallMethod(env, "setLhs", nil, (*jnigi.ObjectRef)(e).Cast(ExpressionClass)) -} - -func (b *BinaryOperator) SetRHS(e *Expression) { - (*jnigi.ObjectRef)(b).CallMethod(env, "setRhs", nil, (*jnigi.ObjectRef)(e).Cast(ExpressionClass)) -} - -func (b *BinaryOperator) SetOperatorCode(s string) (err error) { - return (*jnigi.ObjectRef)(b).SetField(env, "operatorCode", NewString(s)) -} - -func (a *AssignExpression) SetLHS(e []*Expression) { - list, err := ListOf(e) - if err != nil { - panic(err) - } - - (*jnigi.ObjectRef)(a).CallMethod(env, "setLhs", nil, list.Cast("java/util/List")) -} - -func (a *AssignExpression) SetRHS(e []*Expression) { - list, err := ListOf(e) - if err != nil { - panic(err) - } - - (*jnigi.ObjectRef)(a).CallMethod(env, "setRhs", nil, list.Cast("java/util/List")) -} - -func (a *AssignExpression) SetOperatorCode(op string) { - (*jnigi.ObjectRef)(a).CallMethod(env, "setOperatorCode", nil, NewString(op)) -} - -func (u *UnaryOperator) SetInput(e *Expression) { - (*jnigi.ObjectRef)(u).CallMethod(env, "setInput", nil, (*jnigi.ObjectRef)(e).Cast(ExpressionClass)) -} - -func (u *UnaryOperator) SetOperatorCode(s string) (err error) { - return (*jnigi.ObjectRef)(u).SetField(env, "operatorCode", NewString(s)) -} - -func (l *Literal) SetType(t *Type) { - if t != nil && !t.IsNil() { - (*Expression)(l).SetType(t) - } -} - -func (l *Literal) SetValue(value interface{}) { - object, ok := value.(*jnigi.ObjectRef) - - // need to convert it to object since its a generic, which types is erased at runtime - if ok { - value = object.Cast("java/lang/Object") - } - - // basic types should be just fine, i guess? - - (*jnigi.ObjectRef)(l).SetField(env, "value", value) -} - -func (r *DeclaredReferenceExpression) SetRefersTo(d *Declaration) { - (*jnigi.ObjectRef)(r).CallMethod(env, "setRefersTo", nil, (*jnigi.ObjectRef)(d).Cast(DeclarationClass)) -} - -func (r *ArrayCreationExpression) AddDimension(e *Expression) { - (*jnigi.ObjectRef)(r).CallMethod(env, "addDimension", nil, (*jnigi.ObjectRef)(e).Cast(ExpressionClass)) -} - -func (r *ArraySubscriptionExpression) SetArrayExpression(e *Expression) { - (*jnigi.ObjectRef)(r).CallMethod(env, "setArrayExpression", nil, (*jnigi.ObjectRef)(e).Cast(ExpressionClass)) -} - -func (r *ArraySubscriptionExpression) SetSubscriptExpression(e *Expression) { - (*jnigi.ObjectRef)(r).CallMethod(env, "setSubscriptExpression", nil, (*jnigi.ObjectRef)(e).Cast(ExpressionClass)) -} - -func (s *RangeExpression) SetFloor(e *Expression) { - (*jnigi.ObjectRef)(s).CallMethod(env, "setFloor", nil, (*jnigi.ObjectRef)(e).Cast(ExpressionClass)) -} - -func (s *RangeExpression) SetCeiling(e *Expression) { - (*jnigi.ObjectRef)(s).CallMethod(env, "setCeiling", nil, (*jnigi.ObjectRef)(e).Cast(ExpressionClass)) -} - -func (s *RangeExpression) SetThird(e *Expression) { - (*jnigi.ObjectRef)(s).CallMethod(env, "setThird", nil, (*jnigi.ObjectRef)(e).Cast(ExpressionClass)) -} - -func (c *ConstructExpression) AddArgument(e *Expression) { - (*jnigi.ObjectRef)(c).CallMethod(env, "addArgument", nil, (*jnigi.ObjectRef)(e).Cast(ExpressionClass)) -} - -func (c *ConstructExpression) AddPrevDFG(n *Node) { - (*jnigi.ObjectRef)(c).CallMethod(env, "addPrevDFG", nil, (*jnigi.ObjectRef)(n).Cast(NodeClass)) -} - -func (n *NewExpression) SetInitializer(e *Expression) (err error) { - err = (*jnigi.ObjectRef)(n).CallMethod(env, "setInitializer", nil, (*jnigi.ObjectRef)(e).Cast(ExpressionClass)) - - return -} - -func (c *InitializerListExpression) SetInitializers(e []*Expression) { - l, err := ListOf(e) - if err != nil { - panic(err) - } - - (*jnigi.ObjectRef)(c).CallMethod(env, "setInitializers", nil, l.Cast("java/util/List")) -} - -func (k *KeyValueExpression) SetKey(e *Expression) { - (*jnigi.ObjectRef)(k).CallMethod(env, "setKey", nil, (*jnigi.ObjectRef)(e).Cast(ExpressionClass)) -} - -func (k *KeyValueExpression) SetValue(e *Expression) { - (*jnigi.ObjectRef)(k).CallMethod(env, "setValue", nil, (*jnigi.ObjectRef)(e).Cast(ExpressionClass)) -} - -func (l *LambdaExpression) SetFunction(f *FunctionDeclaration) { - err := (*jnigi.ObjectRef)(l).CallMethod(env, "setFunction", nil, (*jnigi.ObjectRef)(f).Cast(FunctionDeclarationClass)) - if err != nil { - panic(err) - } -} diff --git a/cpg-language-go/src/main/golang/frontend/declaration_builder.go b/cpg-language-go/src/main/golang/frontend/declaration_builder.go deleted file mode 100644 index a3d3a6697d..0000000000 --- a/cpg-language-go/src/main/golang/frontend/declaration_builder.go +++ /dev/null @@ -1,97 +0,0 @@ -// -// Copyright (c) 2022, 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 frontend - -import ( - "cpg" - "fmt" - "go/ast" - "go/token" - - "tekao.net/jnigi" -) - -func (frontend *GoLanguageFrontend) NewTranslationUnitDeclaration(fset *token.FileSet, astNode ast.Node, name string) *cpg.TranslationUnitDeclaration { - return (*cpg.TranslationUnitDeclaration)(frontend.NewDeclaration("TranslationUnitDeclaration", fset, astNode, name)) -} - -func (frontend *GoLanguageFrontend) NewNamespaceDeclaration(fset *token.FileSet, astNode ast.Node, name string) *cpg.NamespaceDeclaration { - return (*cpg.NamespaceDeclaration)(frontend.NewDeclaration("NamespaceDeclaration", fset, astNode, name)) -} - -func (frontend *GoLanguageFrontend) NewIncludeDeclaration(fset *token.FileSet, astNode ast.Node, name string) *cpg.IncludeDeclaration { - return (*cpg.IncludeDeclaration)(frontend.NewDeclaration("IncludeDeclaration", fset, astNode, name)) -} - -func (frontend *GoLanguageFrontend) NewFunctionDeclaration(fset *token.FileSet, astNode ast.Node, name string, code string, localNameOnly bool) *cpg.FunctionDeclaration { - return (*cpg.FunctionDeclaration)(frontend.NewDeclaration("FunctionDeclaration", fset, astNode, name, - cpg.NewString(code), - jnigi.NewObjectRef("java/lang/Object"), - localNameOnly, - )) -} - -func (frontend *GoLanguageFrontend) NewMethodDeclaration(fset *token.FileSet, astNode ast.Node, name string) *cpg.MethodDeclaration { - return (*cpg.MethodDeclaration)(frontend.NewDeclaration("MethodDeclaration", fset, astNode, name)) -} - -func (frontend *GoLanguageFrontend) NewRecordDeclaration(fset *token.FileSet, astNode ast.Node, name string, kind string) *cpg.RecordDeclaration { - return (*cpg.RecordDeclaration)(frontend.NewDeclaration("RecordDeclaration", fset, astNode, name, cpg.NewString(kind))) -} - -func (frontend *GoLanguageFrontend) NewVariableDeclaration(fset *token.FileSet, astNode ast.Node, name string) *cpg.VariableDeclaration { - return (*cpg.VariableDeclaration)(frontend.NewDeclaration("VariableDeclaration", fset, astNode, name)) -} - -func (frontend *GoLanguageFrontend) NewParamVariableDeclaration(fset *token.FileSet, astNode ast.Node, name string) *cpg.ParamVariableDeclaration { - return (*cpg.ParamVariableDeclaration)(frontend.NewDeclaration("ParamVariableDeclaration", fset, astNode, name)) -} - -func (frontend *GoLanguageFrontend) NewFieldDeclaration(fset *token.FileSet, astNode ast.Node, name string) *cpg.FieldDeclaration { - return (*cpg.FieldDeclaration)(frontend.NewDeclaration("FieldDeclaration", fset, astNode, name)) -} - -func (frontend *GoLanguageFrontend) NewDeclaration(typ string, fset *token.FileSet, astNode ast.Node, name string, args ...any) *jnigi.ObjectRef { - var node = jnigi.NewObjectRef(fmt.Sprintf("%s/%s", cpg.DeclarationsPackage, typ)) - - // Prepend the frontend and the name as the receiver and the first argument - args = append([]any{frontend.Cast(MetadataProviderClass), cpg.NewCharSequence(name)}, args...) - - err := env.CallStaticMethod( - cpg.GraphPackage+"/DeclarationBuilderKt", - fmt.Sprintf("new%s", typ), node, - args..., - ) - if err != nil { - panic(err) - } - - updateCode(fset, (*cpg.Node)(node), astNode) - updateLocation(fset, (*cpg.Node)(node), astNode) - - return node -} diff --git a/cpg-language-go/src/main/golang/frontend/expression_builder.go b/cpg-language-go/src/main/golang/frontend/expression_builder.go deleted file mode 100644 index 4a457293b7..0000000000 --- a/cpg-language-go/src/main/golang/frontend/expression_builder.go +++ /dev/null @@ -1,154 +0,0 @@ -// -// Copyright (c) 2022, 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 frontend - -import ( - "cpg" - "fmt" - "go/ast" - "go/token" - - "tekao.net/jnigi" -) - -func (frontend *GoLanguageFrontend) NewCallExpression(fset *token.FileSet, astNode ast.Node, callee cpg.Castable, name string) *cpg.CallExpression { - if callee == nil { - callee = jnigi.NewObjectRef(cpg.ExpressionClass) - } else { - callee = callee.Cast(cpg.ExpressionClass) - } - - return (*cpg.CallExpression)(frontend.NewExpression("CallExpression", fset, astNode, callee, cpg.NewCharSequence(name))) -} - -func (frontend *GoLanguageFrontend) NewCastExpression(fset *token.FileSet, astNode ast.Node) *cpg.CastExpression { - return (*cpg.CastExpression)(frontend.NewExpression("CastExpression", fset, astNode)) -} - -func (frontend *GoLanguageFrontend) NewMemberExpression(fset *token.FileSet, astNode ast.Node, name string, base cpg.Castable) *cpg.MemberExpression { - return (*cpg.MemberExpression)(frontend.NewExpression("MemberExpression", fset, astNode, cpg.NewCharSequence(name), base.Cast(cpg.ExpressionClass))) -} - -func (frontend *GoLanguageFrontend) NewMemberCallExpression(fset *token.FileSet, astNode ast.Node, callee *cpg.Expression) *cpg.MemberCallExpression { - return (*cpg.MemberCallExpression)(frontend.NewExpression("MemberCallExpression", fset, astNode, - callee.Cast(cpg.ExpressionClass), - )) -} - -func (frontend *GoLanguageFrontend) NewNewExpression(fset *token.FileSet, astNode ast.Node) *cpg.NewExpression { - return (*cpg.NewExpression)(frontend.NewExpression("NewExpression", fset, astNode)) -} - -func (frontend *GoLanguageFrontend) NewArrayCreationExpression(fset *token.FileSet, astNode ast.Node) *cpg.ArrayCreationExpression { - return (*cpg.ArrayCreationExpression)(frontend.NewExpression("ArrayCreationExpression", fset, astNode)) -} - -func (frontend *GoLanguageFrontend) NewArraySubscriptionExpression(fset *token.FileSet, astNode ast.Node) *cpg.ArraySubscriptionExpression { - return (*cpg.ArraySubscriptionExpression)(frontend.NewExpression("ArraySubscriptionExpression", fset, astNode)) -} - -func (frontend *GoLanguageFrontend) NewRangeExpression(fset *token.FileSet, astNode ast.Node) *cpg.RangeExpression { - return (*cpg.RangeExpression)(frontend.NewExpression("RangeExpression", fset, astNode)) -} - -func (frontend *GoLanguageFrontend) NewConstructExpression(fset *token.FileSet, astNode ast.Node) *cpg.ConstructExpression { - return (*cpg.ConstructExpression)(frontend.NewExpression("ConstructExpression", fset, astNode)) -} - -func (frontend *GoLanguageFrontend) NewInitializerListExpression(fset *token.FileSet, astNode ast.Node) *cpg.InitializerListExpression { - return (*cpg.InitializerListExpression)(frontend.NewExpression("InitializerListExpression", fset, astNode)) -} - -func (frontend *GoLanguageFrontend) NewBinaryOperator(fset *token.FileSet, astNode ast.Node, opCode string) *cpg.BinaryOperator { - return (*cpg.BinaryOperator)(frontend.NewExpression("BinaryOperator", fset, astNode, - cpg.NewString(opCode), - )) -} - -func (frontend *GoLanguageFrontend) NewAssignExpression(fset *token.FileSet, astNode ast.Node, opCode string) *cpg.AssignExpression { - return (*cpg.AssignExpression)(frontend.NewExpression("AssignExpression", fset, astNode, - cpg.NewString(opCode), - )) -} - -func (frontend *GoLanguageFrontend) NewUnaryOperator(fset *token.FileSet, astNode ast.Node, opCode string, postfix bool, prefix bool) *cpg.UnaryOperator { - return (*cpg.UnaryOperator)(frontend.NewExpression("UnaryOperator", fset, astNode, - cpg.NewString(opCode), - postfix, prefix, - )) -} - -func (frontend *GoLanguageFrontend) NewLiteral(fset *token.FileSet, astNode ast.Node, value cpg.Castable, typ *cpg.Type) *cpg.Literal { - if value == nil { - value = jnigi.NewObjectRef("java/lang/Object") - } else { - value = value.Cast("java/lang/Object") - } - - if typ == nil { - panic("typ is nil") - } - - return (*cpg.Literal)(frontend.NewExpression("Literal", fset, astNode, value, typ.Cast(cpg.TypeClass))) -} - -func (frontend *GoLanguageFrontend) NewDeclaredReferenceExpression(fset *token.FileSet, astNode ast.Node, name string) *cpg.DeclaredReferenceExpression { - return (*cpg.DeclaredReferenceExpression)(frontend.NewExpression("DeclaredReferenceExpression", fset, astNode, cpg.NewCharSequence(name))) -} - -func (frontend *GoLanguageFrontend) NewKeyValueExpression(fset *token.FileSet, astNode ast.Node) *cpg.KeyValueExpression { - return (*cpg.KeyValueExpression)(frontend.NewExpression("KeyValueExpression", fset, astNode)) -} - -func (frontend *GoLanguageFrontend) NewLambdaExpression(fset *token.FileSet, astNode ast.Node) *cpg.LambdaExpression { - return (*cpg.LambdaExpression)(frontend.NewExpression("LambdaExpression", fset, astNode)) -} - -func (frontend *GoLanguageFrontend) NewProblemExpression(fset *token.FileSet, astNode ast.Node, problem string) *cpg.ProblemExpression { - return (*cpg.ProblemExpression)(frontend.NewExpression("ProblemExpression", fset, astNode, cpg.NewString(problem))) -} - -func (frontend *GoLanguageFrontend) NewExpression(typ string, fset *token.FileSet, astNode ast.Node, args ...any) *jnigi.ObjectRef { - var node = jnigi.NewObjectRef(fmt.Sprintf("%s/%s", cpg.ExpressionsPackage, typ)) - - // Prepend the frontend as the receiver - args = append([]any{frontend.Cast(cpg.GraphPackage + "/MetadataProvider")}, args...) - - err := env.CallStaticMethod( - cpg.GraphPackage+"/ExpressionBuilderKt", - fmt.Sprintf("new%s", typ), node, - args..., - ) - if err != nil { - panic(err) - } - - updateCode(fset, (*cpg.Node)(node), astNode) - updateLocation(fset, (*cpg.Node)(node), astNode) - - return node -} diff --git a/cpg-language-go/src/main/golang/frontend/frontend.go b/cpg-language-go/src/main/golang/frontend/frontend.go deleted file mode 100644 index f724f62b6a..0000000000 --- a/cpg-language-go/src/main/golang/frontend/frontend.go +++ /dev/null @@ -1,203 +0,0 @@ -// -// Copyright (c) 2021, 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 frontend - -import ( - "bytes" - "cpg" - "fmt" - "go/ast" - "go/printer" - "go/token" - "log" - - "golang.org/x/mod/modfile" - "tekao.net/jnigi" -) - -var env *jnigi.Env - -type GoLanguageFrontend struct { - *jnigi.ObjectRef - File *ast.File - Module *modfile.File - CommentMap ast.CommentMap - TopLevel string - - CurrentTU *cpg.TranslationUnitDeclaration -} - -func InitEnv(e *jnigi.Env) { - env = e -} - -func (g *GoLanguageFrontend) GetCodeFromRawNode(fset *token.FileSet, astNode ast.Node) string { - var codeBuf bytes.Buffer - _ = printer.Fprint(&codeBuf, fset, astNode) - - return codeBuf.String() -} - -func (g *GoLanguageFrontend) GetScopeManager() *cpg.ScopeManager { - var scope = jnigi.NewObjectRef(cpg.ScopeManagerClass) - err := g.GetField(env, "scopeManager", scope) - if err != nil { - log.Fatal(err) - } - - return (*cpg.ScopeManager)(scope) -} - -func (g *GoLanguageFrontend) getLog() (logger *jnigi.ObjectRef, err error) { - logger = jnigi.NewObjectRef("org/slf4j/Logger") - err = env.GetStaticField(cpg.LanguageFrontendClass, "log", logger) - - return -} - -func (g *GoLanguageFrontend) LogInfo(format string, args ...interface{}) (err error) { - var logger *jnigi.ObjectRef - - if logger, err = g.getLog(); err != nil { - return - } - - err = logger.CallMethod(env, "info", nil, cpg.NewString(fmt.Sprintf(format, args...))) - - return -} - -func (g *GoLanguageFrontend) LogDebug(format string, args ...interface{}) (err error) { - var logger *jnigi.ObjectRef - - if logger, err = g.getLog(); err != nil { - return - } - - err = logger.CallMethod(env, "debug", nil, cpg.NewString(fmt.Sprintf(format, args...))) - - return -} - -func (g *GoLanguageFrontend) LogTrace(format string, args ...interface{}) (err error) { - var logger *jnigi.ObjectRef - - if logger, err = g.getLog(); err != nil { - return - } - - err = logger.CallMethod(env, "trace", nil, cpg.NewString(fmt.Sprintf(format, args...))) - - return -} - -func (g *GoLanguageFrontend) LogError(format string, args ...interface{}) (err error) { - var logger *jnigi.ObjectRef - - if logger, err = g.getLog(); err != nil { - return - } - - err = logger.CallMethod(env, "error", nil, cpg.NewString(fmt.Sprintf(format, args...))) - - return -} - -func (g *GoLanguageFrontend) GetLanguage() (l *cpg.Language, err error) { - l = new(cpg.Language) - err = g.ObjectRef.CallMethod(env, "getLanguage", l) - - return -} - -func (g *GoLanguageFrontend) GetCtx() (ctx *cpg.TranslationContext) { - ctx = new(cpg.TranslationContext) - err := g.ObjectRef.CallMethod(env, "getCtx", ctx) - if err != nil { - panic(err) - } - - return -} - -func updateCode(fset *token.FileSet, node *cpg.Node, astNode ast.Node) { - node.SetCode(code(fset, astNode)) -} - -func code(fset *token.FileSet, astNode ast.Node) string { - var codeBuf bytes.Buffer - _ = printer.Fprint(&codeBuf, fset, astNode) - - return codeBuf.String() -} - -func updateLocation(fset *token.FileSet, node *cpg.Node, astNode ast.Node) { - if astNode == nil { - return - } - - file := fset.File(astNode.Pos()) - if file == nil { - return - } - - uri, err := env.NewObject("java/net/URI", cpg.NewString(file.Name())) - if err != nil { - log.Fatal(err) - } - - region := cpg.NewRegion(fset, astNode, - fset.Position(astNode.Pos()).Line, - fset.Position(astNode.Pos()).Column, - fset.Position(astNode.End()).Line, - fset.Position(astNode.End()).Column, - ) - - location := cpg.NewPhysicalLocation(fset, astNode, uri, region) - - err = node.SetLocation(location) - if err != nil { - log.Fatal(err) - } -} - -func updateLanguage(node *cpg.Node, frontend *GoLanguageFrontend) { - var ( - err error - l *cpg.Language - ) - - l, err = frontend.GetLanguage() - if err != nil { - log.Fatal(err) - } - - err = node.SetLanguge(l) - if err != nil { - log.Fatal(err) - } -} diff --git a/cpg-language-go/src/main/golang/frontend/handler.go b/cpg-language-go/src/main/golang/frontend/handler.go deleted file mode 100644 index 5f03a0593b..0000000000 --- a/cpg-language-go/src/main/golang/frontend/handler.go +++ /dev/null @@ -1,1571 +0,0 @@ -// -// Copyright (c) 2021, 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 frontend - -import ( - "cpg" - "fmt" - "go/ast" - "go/token" - "io/ioutil" - "log" - "os" - "path" - "path/filepath" - "strconv" - "strings" - - "golang.org/x/mod/modfile" - "tekao.net/jnigi" -) - -const MetadataProviderClass = cpg.GraphPackage + "/MetadataProvider" -const LanguageProviderClass = cpg.GraphPackage + "/LanguageProvider" - -func getImportName(spec *ast.ImportSpec) string { - if spec.Name != nil { - return spec.Name.Name - } - - var path = spec.Path.Value[1 : len(spec.Path.Value)-1] - var paths = strings.Split(path, "/") - - return paths[len(paths)-1] -} - -func (frontend *GoLanguageFrontend) ParseModule(topLevel string) (exists bool, err error) { - frontend.LogInfo("Looking for a go.mod file in %s", topLevel) - - mod := path.Join(topLevel, "go.mod") - - if _, err := os.Stat(mod); err != nil { - if os.IsNotExist(err) { - frontend.LogInfo("%s does not exist", mod) - - return false, nil - } - } - - b, err := ioutil.ReadFile(mod) - if err != nil { - return true, fmt.Errorf("could not read go.mod: %w", err) - } - - module, err := modfile.Parse(mod, b, nil) - if err != nil { - return true, fmt.Errorf("could not parse mod file: %w", err) - } - - frontend.Module = module - - frontend.LogInfo("Go application has module support with path %s", module.Module.Mod.Path) - - return true, nil -} - -func (this *GoLanguageFrontend) HandleFile(fset *token.FileSet, file *ast.File, path string) (tu *cpg.TranslationUnitDeclaration, err error) { - tu = this.NewTranslationUnitDeclaration(fset, file, path) - - scope := this.GetScopeManager() - - // reset scope - scope.ResetToGlobal((*cpg.Node)(tu)) - - this.CurrentTU = tu - - for _, imprt := range file.Imports { - i := this.handleImportSpec(fset, imprt) - - err = scope.AddDeclaration((*cpg.Declaration)(i)) - if err != nil { - log.Fatal(err) - } - } - - // Create a new namespace declaration, representing the package - p := this.NewNamespaceDeclaration(fset, nil, file.Name.Name) - - // we need to construct the package "path" (e.g. "encoding/json") out of the - // module path as well as the current directory in relation to the topLevel - packagePath := filepath.Dir(path) - - // Construct a relative path starting from the top level - packagePath, err = filepath.Rel(this.TopLevel, packagePath) - if err == nil { - // If we are in a module, we need to prepend the module path to it - if this.Module != nil { - packagePath = filepath.Join(this.Module.Module.Mod.Path, packagePath) - } - - p.SetPath(packagePath) - } else { - this.LogError("Could not relativize package path to top level. Cannot set package path: %v", err) - } - - // enter scope - scope.EnterScope((*cpg.Node)(p)) - - for _, decl := range file.Decls { - // Retrieve all top level declarations. One "Decl" could potentially - // contain multiple CPG declarations. - decls := this.handleDecl(fset, decl) - - for _, d := range decls { - if d != nil { - // Add declaration to current scope. This will also add it to the - // respective AST scope holder - err = scope.AddDeclaration((*cpg.Declaration)(d)) - if err != nil { - log.Fatal(err) - - } - } - } - } - - // leave scope - scope.LeaveScope((*cpg.Node)(p)) - - // add it - scope.AddDeclaration((*cpg.Declaration)(p)) - - return -} - -// handleComments maps comments from ast.Node to a cpg.Node by using ast.CommentMap. -func (this *GoLanguageFrontend) handleComments(node *cpg.Node, astNode ast.Node) { - this.LogTrace("Handling comments for %+v", astNode) - - var comment = "" - - // Lookup ast node in comment map. One cannot use Filter() because this would actually filter all the comments - // that are "below" this AST node as well, e.g. in its children. We only want the comments on the node itself. - // Therefore we must convert the CommentMap back into an actual map to access the stored entry for the node. - comments, ok := (map[ast.Node][]*ast.CommentGroup)(this.CommentMap)[astNode] - if !ok { - return - } - - for _, c := range comments { - text := strings.TrimRight(c.Text(), "\n") - comment += text - } - - if comment != "" { - node.SetComment(comment) - - this.LogTrace("Comments: %+v", comment) - } -} - -// handleDecl parses an [ast.Decl]. Note, that in a "Decl", one or more actual -// declarations can be found. Therefore, this function returns a slice of -// [cpg.Declaration]. -func (this *GoLanguageFrontend) handleDecl(fset *token.FileSet, decl ast.Decl) (decls []*cpg.Declaration) { - this.LogTrace("Handling declaration (%T): %+v", decl, decl) - - decls = []*cpg.Declaration{} - - switch v := decl.(type) { - case *ast.FuncDecl: - // There can be only a single function declaration - decls = append(decls, (*cpg.Declaration)(this.handleFuncDecl(fset, v))) - case *ast.GenDecl: - // GenDecl can hold multiple declarations - decls = this.handleGenDecl(fset, v) - default: - this.LogError("Not parsing declaration of type %T yet: %+v", v, v) - // TODO: Return a ProblemDeclaration - } - - // Handle comments for all declarations - for _, d := range decls { - // TODO: This is problematic because we are assigning it the wrong node - if d != nil { - this.handleComments((*cpg.Node)(d), decl) - } - } - - return -} - -func (this *GoLanguageFrontend) handleFuncDecl(fset *token.FileSet, funcDecl *ast.FuncDecl) *cpg.FunctionDeclaration { - this.LogTrace("Handling func Decl: %+v", *funcDecl) - - var scope = this.GetScopeManager() - var receiver *cpg.VariableDeclaration - - var f *cpg.FunctionDeclaration - if funcDecl.Recv != nil { - m := this.NewMethodDeclaration(fset, funcDecl, funcDecl.Name.Name) - - // TODO: why is this a list? - var recv = funcDecl.Recv.List[0] - - var recordType = this.handleType(fset, recv.Type) - - // The name of the Go receiver is optional. In fact, if the name is not - // specified we probably do not need any receiver variable at all, - // because the syntax is only there to ensure that this method is part - // of the struct, but it is not modifying the receiver. - if len(recv.Names) > 0 { - receiver = this.NewVariableDeclaration(fset, nil, recv.Names[0].Name) - - // TODO: should we use the FQN here? FQNs are a mess in the CPG... - receiver.SetType(recordType) - - err := m.SetReceiver(receiver) - if err != nil { - log.Fatal(err) - } - } - - if recordType != nil { - var recordName = recordType.GetName() - - // TODO: this will only find methods within the current translation unit - // this is a limitation that we have for C++ as well - record, err := this.GetScopeManager().GetRecordForName( - this.GetScopeManager().GetCurrentScope(), - recordName) - - if err != nil { - log.Fatal(err) - } - - if record != nil && !record.IsNil() { - // now this gets a little bit hacky, we will add it to the record declaration - // this is strictly speaking not 100 % true, since the method property edge is - // marked as AST and in Go a method is not part of the struct's AST but is declared - // outside. In the future, we need to differentiate between just the associated members - // of the class and the pure AST nodes declared in the struct itself - this.LogTrace("Record: %+v", record) - - err = record.AddMethod(m) - if err != nil { - log.Fatal(err) - - } - } - } - - f = (*cpg.FunctionDeclaration)(m) - } else { - // We do not want to prefix the package for an empty (lambda) function name - var localNameOnly bool = false - if funcDecl.Name.Name == "" { - localNameOnly = true - } - - f = this.NewFunctionDeclaration(fset, funcDecl, funcDecl.Name.Name, "", localNameOnly) - } - - // enter scope for function - scope.EnterScope((*cpg.Node)(f)) - - if receiver != nil { - this.LogTrace("Adding receiver %s", (*cpg.Node)(receiver).GetName()) - - // add the receiver do the scope manager, so we can resolve the receiver value - this.GetScopeManager().AddDeclaration((*cpg.Declaration)(receiver)) - } - - var t *cpg.Type = this.handleType(fset, funcDecl.Type) - var returnTypes []*cpg.Type = []*cpg.Type{} - - if funcDecl.Type.Results != nil { - for _, returnVariable := range funcDecl.Type.Results.List { - returnTypes = append(returnTypes, this.handleType(fset, returnVariable.Type)) - - // if the function has named return variables, be sure to declare them as well - if returnVariable.Names != nil { - p := this.NewVariableDeclaration(fset, returnVariable, returnVariable.Names[0].Name) - - p.SetType(this.handleType(fset, returnVariable.Type)) - - // add parameter to scope - this.GetScopeManager().AddDeclaration((*cpg.Declaration)(p)) - } - } - } - - this.LogTrace("Function has type %s", t.GetName()) - - f.SetType(t) - f.SetReturnTypes(returnTypes) - - // TODO: for other languages, we would enter the record declaration, if - // this is a method; however I am not quite sure if this makes sense for - // go, since we do not have a 'this', but rather a named receiver - - for _, param := range funcDecl.Type.Params.List { - this.LogTrace("Parsing param: %+v", param) - - var name string - // Somehow parameters end up having no name sometimes, have not fully understood why. - if len(param.Names) > 0 { - // TODO: more than one name? - name = param.Names[0].Name - - // If the name is an underscore, it means that the parameter is - // unnamed. In order to avoid confusing and some compatibility with - // other languages, we are just setting the name to an empty string - // in this case. - if name == "_" { - name = "" - } - } else { - this.LogError("Some param has no name, which is a bit weird: %+v", param) - } - - p := this.NewParamVariableDeclaration(fset, param, name) - - // Check for varargs. In this case we want to parse the element type - // (and make it an array afterwards) - if ell, ok := param.Type.(*ast.Ellipsis); ok { - p.SetVariadic(true) - var t = this.handleType(fset, ell.Elt) - - p.SetType(this.ref(t, "pointer")) - } else { - p.SetType(this.handleType(fset, param.Type)) - } - - // add parameter to scope - this.GetScopeManager().AddDeclaration((*cpg.Declaration)(p)) - - this.handleComments((*cpg.Node)(p), param) - } - - this.LogTrace("Parsing function body of %s", (*cpg.Node)(f).GetName()) - - // parse body - s := this.handleBlockStmt(fset, funcDecl.Body) - - err := f.SetBody((*cpg.Statement)(s)) - if err != nil { - log.Fatal(err) - - } - - // leave scope - err = scope.LeaveScope((*cpg.Node)(f)) - if err != nil { - log.Fatal(err) - - } - - return f -} - -func (this *GoLanguageFrontend) handleGenDecl(fset *token.FileSet, genDecl *ast.GenDecl) (decls []*cpg.Declaration) { - decls = []*cpg.Declaration{} - - for _, spec := range genDecl.Specs { - switch v := spec.(type) { - case *ast.ValueSpec: - decls = append(decls, this.handleValueSpec(fset, v)...) - case *ast.TypeSpec: - decls = append(decls, this.handleTypeSpec(fset, v)) - case *ast.ImportSpec: - // Somehow these end up duplicate in the AST, so do not handle them here - default: - this.LogError("Not parsing specification of type %T yet: %+v", v, v) - } - } - - return -} - -// handleValueSpec handles parsing of an [ast.ValueSpec], which is a variable -// declaration. Since this can potentially declare multiple variables with one -// "spec", this returns a slice of [cpg.Declaration]. -func (this *GoLanguageFrontend) handleValueSpec(fset *token.FileSet, valueDecl *ast.ValueSpec) (decls []*cpg.Declaration) { - decls = []*cpg.Declaration{} - - // We need to declare one variable for each name - for idx, ident := range valueDecl.Names { - d := this.NewVariableDeclaration(fset, valueDecl, ident.Name) - - // Handle the type (if its there) - if valueDecl.Type != nil { - t := this.handleType(fset, valueDecl.Type) - - d.SetType(t) - } - - // There could either be no initializers, otherwise the amount of values - // must match the names - lenValues := len(valueDecl.Values) - if lenValues != 0 && lenValues != len(valueDecl.Names) { - this.LogError("Number of initializers does not match number of names. Initializers might be incomplete") - } - - // The initializer is in the "Values" slice with the respective index - if len(valueDecl.Values) > idx { - var expr = this.handleExpr(fset, valueDecl.Values[idx]) - - err := d.SetInitializer(expr) - if err != nil { - log.Fatal(err) - } - } - - decls = append(decls, d.Declaration()) - } - - return decls -} - -// handleTypeSpec handles an [ast.TypeSpec], which defines either a struct or an -// interface. It returns a single [cpg.Declaration]. -func (this *GoLanguageFrontend) handleTypeSpec(fset *token.FileSet, typeDecl *ast.TypeSpec) *cpg.Declaration { - err := this.LogInfo("Type specifier with name %s and type (%T, %+v)", typeDecl.Name.Name, typeDecl.Type, typeDecl.Type) - if err != nil { - log.Fatal(err) - } - - switch v := typeDecl.Type.(type) { - case *ast.StructType: - return (*cpg.Declaration)(this.handleStructTypeSpec(fset, typeDecl, v)) - case *ast.InterfaceType: - return (*cpg.Declaration)(this.handleInterfaceTypeSpec(fset, typeDecl, v)) - } - - return nil -} - -func (this *GoLanguageFrontend) handleImportSpec(fset *token.FileSet, importSpec *ast.ImportSpec) *cpg.Declaration { - this.LogTrace("Import specifier with: %+v)", *importSpec) - - i := this.NewIncludeDeclaration(fset, importSpec, getImportName(importSpec)) - - var scope = this.GetScopeManager() - - i.SetFilename(importSpec.Path.Value[1 : len(importSpec.Path.Value)-1]) - - err := scope.AddDeclaration((*cpg.Declaration)(i)) - if err != nil { - log.Fatal(err) - } - - return (*cpg.Declaration)(i) -} - -func (this *GoLanguageFrontend) handleIdentAsName(ident *ast.Ident) string { - return ident.Name -} - -func (this *GoLanguageFrontend) handleStructTypeSpec(fset *token.FileSet, typeDecl *ast.TypeSpec, structType *ast.StructType) *cpg.RecordDeclaration { - r := this.NewRecordDeclaration(fset, typeDecl, this.handleIdentAsName(typeDecl.Name), "struct") - - var scope = this.GetScopeManager() - - scope.EnterScope((*cpg.Node)(r)) - - if !structType.Incomplete { - for _, field := range structType.Fields.List { - - // a field can also have no name, which means that it is embedded, not quite - // sure yet how to handle this, but since the embedded field can be accessed - // by its type, it could make sense to name the field according to the type - - var name string - t := this.handleType(fset, field.Type) - - if field.Names == nil { - // retrieve the root type name - var typeName = t.GetRoot().GetName().ToString() - - this.LogTrace("Handling embedded field of type %s", typeName) - - name = typeName - } else { - this.LogTrace("Handling field %s", field.Names[0].Name) - - // TODO: Multiple names? - name = field.Names[0].Name - } - - f := this.NewFieldDeclaration(fset, field, name) - - f.SetType(t) - - scope.AddDeclaration((*cpg.Declaration)(f)) - } - } - - scope.LeaveScope((*cpg.Node)(r)) - - return r -} - -func (this *GoLanguageFrontend) handleInterfaceTypeSpec(fset *token.FileSet, typeDecl *ast.TypeSpec, interfaceType *ast.InterfaceType) *cpg.RecordDeclaration { - r := this.NewRecordDeclaration(fset, typeDecl, this.handleIdentAsName(typeDecl.Name), "interface") - - var scope = this.GetScopeManager() - - scope.EnterScope((*cpg.Node)(r)) - - if !interfaceType.Incomplete { - for _, method := range interfaceType.Methods.List { - t := this.handleType(fset, method.Type) - - // Even though this list is called "Methods", it contains all kinds - // of things, so we need to proceed with caution. Only if the - // "method" actually has a name, we declare a new method - // declaration. - if len(method.Names) > 0 { - m := this.NewMethodDeclaration(fset, method, method.Names[0].Name) - m.SetType(t) - - scope.AddDeclaration((*cpg.Declaration)(m)) - } else { - this.LogTrace("Adding %s as super class of interface %s", t.GetName(), (*cpg.Node)(r).GetName()) - // Otherwise, it contains either types or interfaces. For now we - // hope that it only has interfaces. We consider embedded - // interfaces as sort of super types for this interface. - r.AddSuperClass(t) - } - } - } - - scope.LeaveScope((*cpg.Node)(r)) - - return r -} - -func (this *GoLanguageFrontend) handleBlockStmt(fset *token.FileSet, blockStmt *ast.BlockStmt) *cpg.CompoundStatement { - this.LogTrace("Handling block statement: %+v", *blockStmt) - - c := this.NewCompoundStatement(fset, blockStmt) - - // enter scope - this.GetScopeManager().EnterScope((*cpg.Node)(c)) - - for _, stmt := range blockStmt.List { - var s *cpg.Statement - - s = this.handleStmt(fset, stmt, blockStmt) - - if s != nil { - // add statement - c.AddStatement(s) - } - } - - // leave scope - this.GetScopeManager().LeaveScope((*cpg.Node)(c)) - - return c -} - -func (this *GoLanguageFrontend) handleForStmt(fset *token.FileSet, forStmt *ast.ForStmt) *cpg.ForStatement { - this.LogTrace("Handling for statement: %+v", *forStmt) - - f := this.NewForStatement(fset, forStmt) - - var scope = this.GetScopeManager() - - scope.EnterScope((*cpg.Node)(f)) - - if forStmt.Init != nil { - initStatement := this.handleStmt(fset, forStmt.Init, forStmt) - f.SetInitializerStatement(initStatement) - } - - if forStmt.Cond != nil { - condition := this.handleExpr(fset, forStmt.Cond) - f.SetCondition(condition) - } - - if forStmt.Post != nil { - iter := this.handleStmt(fset, forStmt.Post, forStmt) - f.SetIterationStatement(iter) - } - - if body := this.handleStmt(fset, forStmt.Body, forStmt); body != nil { - f.SetStatement(body) - } - - scope.LeaveScope((*cpg.Node)(f)) - - return f -} - -func (this *GoLanguageFrontend) handleRangeStmt(fset *token.FileSet, rangeStmt *ast.RangeStmt) *cpg.ForEachStatement { - this.LogTrace("Handling range statement: %+v", *rangeStmt) - - f := this.NewForEachStatement(fset, rangeStmt) - - var scope = this.GetScopeManager() - - scope.EnterScope((*cpg.Node)(f)) - - // TODO: Support other use cases that do not use DEFINE - if rangeStmt.Tok == token.DEFINE { - stmt := this.NewDeclarationStatement(fset, rangeStmt) - - // TODO: not really the best way to deal with this - // TODO: key type is always int. we could set this - var keyName = rangeStmt.Key.(*ast.Ident).Name - - key := this.NewVariableDeclaration(fset, rangeStmt.Key, keyName) - this.GetScopeManager().AddDeclaration((*cpg.Declaration)(key)) - stmt.AddToPropertyEdgeDeclaration((*cpg.Declaration)(key)) - - if rangeStmt.Value != nil { - // TODO: not really the best way to deal with this - // TODO: key type is always int. we could set this - var valueName = rangeStmt.Value.(*ast.Ident).Name - - value := this.NewVariableDeclaration(fset, rangeStmt.Key, valueName) - this.GetScopeManager().AddDeclaration((*cpg.Declaration)(value)) - stmt.AddToPropertyEdgeDeclaration((*cpg.Declaration)(value)) - } - - f.SetVariable((*cpg.Statement)(stmt)) - } - - iterable := (*cpg.Statement)(this.handleExpr(fset, rangeStmt.X)) - f.SetIterable(iterable) - - body := this.handleStmt(fset, rangeStmt.Body, rangeStmt) - f.SetStatement(body) - - scope.LeaveScope((*cpg.Node)(f)) - - return f -} - -func (this *GoLanguageFrontend) handleReturnStmt(fset *token.FileSet, returnStmt *ast.ReturnStmt) *cpg.ReturnStatement { - this.LogTrace("Handling return statement: %+v", *returnStmt) - - r := this.NewReturnStatement(fset, returnStmt) - - if returnStmt.Results != nil && len(returnStmt.Results) > 0 { - e := this.handleExpr(fset, returnStmt.Results[0]) - - // TODO: parse more than one result expression - - if e != nil { - r.SetReturnValue(e) - } - } else { - // TODO: connect result statement to result variables - } - - return r -} - -func (this *GoLanguageFrontend) handleIncDecStmt(fset *token.FileSet, incDecStmt *ast.IncDecStmt) *cpg.UnaryOperator { - this.LogTrace("Handling decimal increment statement: %+v", *incDecStmt) - - var opCode string - if incDecStmt.Tok == token.INC { - opCode = "++" - } - - if incDecStmt.Tok == token.DEC { - opCode = "--" - } - - u := this.NewUnaryOperator(fset, incDecStmt, opCode, true, false) - - if input := this.handleExpr(fset, incDecStmt.X); input != nil { - u.SetInput(input) - } - - return u -} - -func (this *GoLanguageFrontend) handleStmt(fset *token.FileSet, stmt ast.Stmt, parent ast.Stmt) (s *cpg.Statement) { - this.LogTrace("Handling statement (%T): %+v", stmt, stmt) - - switch v := stmt.(type) { - case *ast.ExprStmt: - // in our cpg, each expression is also a statement, - // so we do not need an expression statement wrapper - s = (*cpg.Statement)(this.handleExpr(fset, v.X)) - case *ast.AssignStmt: - s = (*cpg.Statement)(this.handleAssignStmt(fset, v, parent)) - case *ast.DeclStmt: - s = (*cpg.Statement)(this.handleDeclStmt(fset, v)) - case *ast.GoStmt: - s = (*cpg.Statement)(this.handleGoStmt(fset, v)) - case *ast.IfStmt: - s = (*cpg.Statement)(this.handleIfStmt(fset, v)) - case *ast.SwitchStmt: - s = (*cpg.Statement)(this.handleSwitchStmt(fset, v)) - case *ast.CaseClause: - s = (*cpg.Statement)(this.handleCaseClause(fset, v)) - case *ast.BlockStmt: - s = (*cpg.Statement)(this.handleBlockStmt(fset, v)) - case *ast.ForStmt: - s = (*cpg.Statement)(this.handleForStmt(fset, v)) - case *ast.RangeStmt: - s = (*cpg.Statement)(this.handleRangeStmt(fset, v)) - case *ast.ReturnStmt: - s = (*cpg.Statement)(this.handleReturnStmt(fset, v)) - case *ast.IncDecStmt: - s = (*cpg.Statement)(this.handleIncDecStmt(fset, v)) - default: - msg := fmt.Sprintf("Not parsing statement of type %T yet: %s", v, code(fset, v)) - this.LogError(msg) - s = (*cpg.Statement)(this.NewProblemExpression(fset, v, msg)) - } - - if s != nil { - this.handleComments((*cpg.Node)(s), stmt) - } - - return -} - -func (this *GoLanguageFrontend) handleExpr(fset *token.FileSet, expr ast.Expr) (e *cpg.Expression) { - this.LogTrace("Handling expression (%T): %+v", expr, expr) - - switch v := expr.(type) { - case *ast.CallExpr: - e = (*cpg.Expression)(this.handleCallExpr(fset, v)) - case *ast.IndexExpr: - e = (*cpg.Expression)(this.handleIndexExpr(fset, v)) - case *ast.BinaryExpr: - e = (*cpg.Expression)(this.handleBinaryExpr(fset, v)) - case *ast.UnaryExpr: - e = (*cpg.Expression)(this.handleUnaryExpr(fset, v)) - case *ast.StarExpr: - e = (*cpg.Expression)(this.handleStarExpr(fset, v)) - case *ast.SelectorExpr: - e = (*cpg.Expression)(this.handleSelectorExpr(fset, v)) - case *ast.SliceExpr: - e = (*cpg.Expression)(this.handleSliceExpr(fset, v)) - case *ast.KeyValueExpr: - e = (*cpg.Expression)(this.handleKeyValueExpr(fset, v)) - case *ast.BasicLit: - e = (*cpg.Expression)(this.handleBasicLit(fset, v)) - case *ast.CompositeLit: - e = (*cpg.Expression)(this.handleCompositeLit(fset, v)) - case *ast.FuncLit: - e = (*cpg.Expression)(this.handleFuncLit(fset, v)) - case *ast.Ident: - e = (*cpg.Expression)(this.handleIdent(fset, v)) - case *ast.TypeAssertExpr: - e = (*cpg.Expression)(this.handleTypeAssertExpr(fset, v)) - case *ast.ParenExpr: - e = this.handleExpr(fset, v.X) - default: - msg := fmt.Sprintf("Not parsing expression of type %T yet: %s", v, code(fset, v)) - this.LogError(msg) - e = (*cpg.Expression)(this.NewProblemExpression(fset, v, msg)) - } - - if e != nil { - this.handleComments((*cpg.Node)(e), expr) - } - - return -} - -func (this *GoLanguageFrontend) handleAssignStmt(fset *token.FileSet, assignStmt *ast.AssignStmt, parent ast.Stmt) (expr *cpg.Expression) { - this.LogTrace("Handling assignment statement: %+v", assignStmt) - - this.LogDebug("Parent: %#v", parent) - - var rhs = []*cpg.Expression{} - var lhs = []*cpg.Expression{} - for _, expr := range assignStmt.Lhs { - lhs = append(lhs, this.handleExpr(fset, expr)) - } - - for _, expr := range assignStmt.Rhs { - rhs = append(rhs, this.handleExpr(fset, expr)) - } - - a := this.NewAssignExpression(fset, assignStmt, "=") - - a.SetLHS(lhs) - a.SetRHS(rhs) - - // We need to explicitly set the operator code on this assignment as - // something which potentially declares a variable, so we can resolve this - // in our extra pass. - if assignStmt.Tok == token.DEFINE { - a.SetOperatorCode(":=") - } - - expr = (*cpg.Expression)(a) - - return -} - -func (this *GoLanguageFrontend) handleDeclStmt(fset *token.FileSet, declStmt *ast.DeclStmt) (expr *cpg.Expression) { - this.LogTrace("Handling declaration statement: %+v", *declStmt) - - // Lets create a variable declaration (wrapped with a declaration stmt) with - // this, because we define the variable here - stmt := this.NewDeclarationStatement(fset, declStmt) - - decls := this.handleDecl(fset, declStmt.Decl) - - // Loop over the declarations and add them to the scope as well as the statement. - for _, d := range decls { - stmt.AddToPropertyEdgeDeclaration(d) - this.GetScopeManager().AddDeclaration(d) - } - - return (*cpg.Expression)(stmt) -} - -// handleGoStmt handles the `go` statement, which is a special keyword in go -// that starts the supplied call expression in a separate Go routine. We cannot -// model this 1:1, so we basically we create a call expression to a built-in call. -func (this *GoLanguageFrontend) handleGoStmt(fset *token.FileSet, goStmt *ast.GoStmt) (expr *cpg.Expression) { - this.LogTrace("Handling go statement: %+v", *goStmt) - - ref := (*cpg.Expression)(this.NewDeclaredReferenceExpression(fset, nil, "go")) - - call := this.NewCallExpression(fset, goStmt, ref, "go") - call.AddArgument(this.handleCallExpr(fset, goStmt.Call)) - - return (*cpg.Expression)(call) -} - -func (this *GoLanguageFrontend) handleIfStmt(fset *token.FileSet, ifStmt *ast.IfStmt) (expr *cpg.Expression) { - this.LogTrace("Handling if statement: %+v", *ifStmt) - - stmt := this.NewIfStatement(fset, ifStmt) - - var scope = this.GetScopeManager() - - scope.EnterScope((*cpg.Node)(stmt)) - - if ifStmt.Init != nil { - init := this.handleStmt(fset, ifStmt.Init, ifStmt) - stmt.SetInitializerStatement(init) - } - - cond := this.handleExpr(fset, ifStmt.Cond) - stmt.SetCondition(cond) - - then := this.handleBlockStmt(fset, ifStmt.Body) - // Somehow this can be nil-ish? - if !then.IsNil() { - stmt.SetThenStatement((*cpg.Statement)(then)) - } - - if ifStmt.Else != nil { - els := this.handleStmt(fset, ifStmt.Else, ifStmt) - stmt.SetElseStatement((*cpg.Statement)(els)) - } - - scope.LeaveScope((*cpg.Node)(stmt)) - - return (*cpg.Expression)(stmt) -} - -func (this *GoLanguageFrontend) handleSwitchStmt(fset *token.FileSet, switchStmt *ast.SwitchStmt) (expr *cpg.Expression) { - this.LogTrace("Handling switch statement: %+v", *switchStmt) - - s := this.NewSwitchStatement(fset, switchStmt) - - if switchStmt.Init != nil { - s.SetInitializerStatement(this.handleStmt(fset, switchStmt.Init, switchStmt)) - } - - if switchStmt.Tag != nil { - s.SetCondition(this.handleExpr(fset, switchStmt.Tag)) - } - - s.SetStatement((*cpg.Statement)(this.handleBlockStmt(fset, switchStmt.Body))) // should only contain case clauses - - return (*cpg.Expression)(s) -} - -func (this *GoLanguageFrontend) handleCaseClause(fset *token.FileSet, caseClause *ast.CaseClause) (expr *cpg.Expression) { - this.LogTrace("Handling case clause: %+v", *caseClause) - - var s *cpg.Statement - - if caseClause.List == nil { - s = (*cpg.Statement)(this.NewDefaultStatement(fset, nil)) - } else { - c := this.NewCaseStatement(fset, caseClause) - c.SetCaseExpression(this.handleExpr(fset, caseClause.List[0])) - - s = (*cpg.Statement)(c) - } - - // need to find the current block / scope and add the statements to it - block := this.GetScopeManager().GetCurrentBlock() - - // add the case statement - if s != nil && block != nil && !block.IsNil() { - block.AddStatement((*cpg.Statement)(s)) - } - - for _, stmt := range caseClause.Body { - s = this.handleStmt(fset, stmt, caseClause) - - if s != nil && block != nil && !block.IsNil() { - // add statement - block.AddStatement(s) - } - } - - // this is a little trick, to not add the case statement in handleStmt because we added it already. - // otherwise, the order is screwed up. - return nil -} - -func (this *GoLanguageFrontend) handleCallExpr(fset *token.FileSet, callExpr *ast.CallExpr) *cpg.Expression { - var c *cpg.CallExpression - - // In Go, regular cast expressions (not type asserts are modelled as calls). - // In this case, the Fun contains a type expression. - switch v := callExpr.Fun.(type) { - case *ast.ArrayType, - *ast.StructType, - *ast.FuncType, - *ast.InterfaceType, - *ast.MapType, - *ast.ChanType: - this.LogDebug("Handling cast expression: %#v", callExpr) - - cast := this.NewCastExpression(fset, callExpr) - cast.SetCastType(this.handleType(fset, v)) - - if len(callExpr.Args) > 1 { - cast.SetExpression(this.handleExpr(fset, callExpr.Args[0])) - } - - return (*cpg.Expression)(cast) - } - - // parse the Fun field, to see which kind of expression it is - var reference = this.handleExpr(fset, callExpr.Fun) - - if reference == nil { - return nil - } - - name := reference.GetName().GetLocalName() - - if name == "new" { - return this.handleNewExpr(fset, callExpr) - } else if name == "make" { - return this.handleMakeExpr(fset, callExpr) - } - - isMemberExpression, err := (*jnigi.ObjectRef)(reference).IsInstanceOf(env, cpg.MemberExpressionClass) - if err != nil { - log.Fatal(err) - - } - - if isMemberExpression { - this.LogTrace("Fun is a member call to %s", name) - - m := this.NewMemberCallExpression(fset, callExpr, reference) - - c = (*cpg.CallExpression)(m) - } else { - this.LogTrace("Handling regular call expression to %s", name) - - c = this.NewCallExpression(fset, callExpr, reference, name) - } - - for _, arg := range callExpr.Args { - e := this.handleExpr(fset, arg) - - if e != nil { - c.AddArgument(e) - } - } - - return (*cpg.Expression)(c) -} - -func (this *GoLanguageFrontend) handleIndexExpr(fset *token.FileSet, indexExpr *ast.IndexExpr) *cpg.ArraySubscriptionExpression { - a := this.NewArraySubscriptionExpression(fset, indexExpr) - - a.SetArrayExpression(this.handleExpr(fset, indexExpr.X)) - a.SetSubscriptExpression(this.handleExpr(fset, indexExpr.Index)) - - return a -} - -// handleSliceExpr handles a [ast.SliceExpr], which is an extended version of -// [ast.IndexExpr]. We are modelling this as a combination of a -// [cpg.ArraySubscriptionExpression] that contains a [cpg.RangeExpression] as -// its subscriptExpression to share some code between this and an index -// expression. -func (this *GoLanguageFrontend) handleSliceExpr(fset *token.FileSet, sliceExpr *ast.SliceExpr) *cpg.ArraySubscriptionExpression { - a := this.NewArraySubscriptionExpression(fset, sliceExpr) - - a.SetArrayExpression(this.handleExpr(fset, sliceExpr.X)) - - // Build the slice expression - s := this.NewRangeExpression(fset, sliceExpr) - if sliceExpr.Low != nil { - s.SetFloor(this.handleExpr(fset, sliceExpr.Low)) - } - if sliceExpr.High != nil { - s.SetCeiling(this.handleExpr(fset, sliceExpr.High)) - } - if sliceExpr.Max != nil { - s.SetThird(this.handleExpr(fset, sliceExpr.Max)) - } - - a.SetSubscriptExpression((*cpg.Expression)(s)) - - return a -} - -func (this *GoLanguageFrontend) handleNewExpr(fset *token.FileSet, callExpr *ast.CallExpr) *cpg.Expression { - n := this.NewNewExpression(fset, callExpr) - - // first argument is type - t := this.handleType(fset, callExpr.Args[0]) - - // new is a pointer, so need to reference the type with a pointer - // TODO(oxisto): There is a very weird bug here: If we would use this.Pointer() here, we will get a - // null pointer exception and I have no idea why - var pointer = jnigi.NewObjectRef(cpg.PointerOriginClass) - err := env.GetStaticField(cpg.PointerOriginClass, "POINTER", pointer) - if err != nil { - log.Fatal(err) - } - - (*cpg.HasType)(n).SetType(t.Reference(pointer)) - - // a new expression also needs an initializer, which is usually a constructexpression - c := this.NewConstructExpression(fset, callExpr) - (*cpg.HasType)(c).SetType(t) - - n.SetInitializer((*cpg.Expression)(c)) - - return (*cpg.Expression)(n) -} - -func (this *GoLanguageFrontend) handleMakeExpr(fset *token.FileSet, callExpr *ast.CallExpr) *cpg.Expression { - var n *cpg.Expression - - if callExpr.Args == nil || len(callExpr.Args) < 1 { - return nil - } - - // first argument is always the type, handle it - t := this.handleType(fset, callExpr.Args[0]) - - // actually make() can make more than just arrays, i.e. channels and maps - if _, isArray := callExpr.Args[0].(*ast.ArrayType); isArray { - r := this.NewArrayCreationExpression(fset, callExpr) - - // second argument is a dimension (if this is an array), usually a literal - if len(callExpr.Args) > 1 { - d := this.handleExpr(fset, callExpr.Args[1]) - - r.AddDimension(d) - } - - n = (*cpg.Expression)(r) - } else { - // create at least a generic construct expression for the given map or channel type - // and provide the remaining arguments - - c := this.NewConstructExpression(fset, callExpr) - - // pass the remaining arguments - for _, arg := range callExpr.Args[1:] { - a := this.handleExpr(fset, arg) - - c.AddArgument(a) - } - - n = (*cpg.Expression)(c) - } - - // set the type, we have parsed earlier - (*cpg.HasType)(n).SetType(t) - - return n -} - -func (this *GoLanguageFrontend) handleBinaryExpr(fset *token.FileSet, binaryExpr *ast.BinaryExpr) *cpg.BinaryOperator { - b := this.NewBinaryOperator(fset, binaryExpr, binaryExpr.Op.String()) - - lhs := this.handleExpr(fset, binaryExpr.X) - rhs := this.handleExpr(fset, binaryExpr.Y) - - b.SetLHS(lhs) - b.SetRHS(rhs) - - return b -} - -func (this *GoLanguageFrontend) handleUnaryExpr(fset *token.FileSet, unaryExpr *ast.UnaryExpr) *cpg.UnaryOperator { - u := this.NewUnaryOperator(fset, unaryExpr, unaryExpr.Op.String(), false, false) - - input := this.handleExpr(fset, unaryExpr.X) - if input != nil { - u.SetInput(input) - } - - return u -} - -func (this *GoLanguageFrontend) handleStarExpr(fset *token.FileSet, unaryExpr *ast.StarExpr) *cpg.UnaryOperator { - u := this.NewUnaryOperator(fset, unaryExpr, "*", false, true) - - input := this.handleExpr(fset, unaryExpr.X) - if input != nil { - u.SetInput(input) - } - - return u -} - -func (this *GoLanguageFrontend) handleSelectorExpr(fset *token.FileSet, selectorExpr *ast.SelectorExpr) *cpg.DeclaredReferenceExpression { - base := this.handleExpr(fset, selectorExpr.X) - - // check, if this just a regular reference to a variable with a package scope and not a member expression - var isMemberExpression bool = true - for _, imp := range this.File.Imports { - if base.GetName().GetLocalName() == getImportName(imp) { - // found a package name, so this is NOT a member expression - isMemberExpression = false - } - } - - var decl *cpg.DeclaredReferenceExpression - if isMemberExpression { - m := this.NewMemberExpression(fset, selectorExpr, selectorExpr.Sel.Name, base) - - decl = (*cpg.DeclaredReferenceExpression)(m) - } else { - // we need to set the name to a FQN-style, including the package scope. the call resolver will then resolve this - fqn := fmt.Sprintf("%s.%s", base.GetName(), selectorExpr.Sel.Name) - - this.LogTrace("Trying to parse the fqn '%s'", fqn) - - name := this.ParseName(fqn) - - decl = this.NewDeclaredReferenceExpression(fset, selectorExpr, fqn) - decl.Node().SetName(name) - } - - // For now we just let the VariableUsageResolver handle this. Therefore, - // we can not differentiate between field access to a receiver, an object - // or a const field within a package at this point. - - // check, if the base relates to a receiver - /*var method = (*cpg.MethodDeclaration)((*jnigi.ObjectRef)(this.GetScopeManager().GetCurrentFunction()).Cast(MethodDeclarationClass)) - - if method != nil && !method.IsNil() { - //recv := method.GetReceiver() - - // this refers to our receiver - if (*cpg.Node)(recv).GetName() == (*cpg.Node)(base).GetName() { - - (*cpg.DeclaredReferenceExpression)(base).SetRefersTo(recv.Declaration()) - } - }*/ - - return decl -} - -func (this *GoLanguageFrontend) handleKeyValueExpr(fset *token.FileSet, expr *ast.KeyValueExpr) *cpg.KeyValueExpression { - this.LogTrace("Handling key value expression %+v", *expr) - - k := this.NewKeyValueExpression(fset, expr) - - keyExpr := this.handleExpr(fset, expr.Key) - if keyExpr != nil { - k.SetKey(keyExpr) - } - - valueExpr := this.handleExpr(fset, expr.Value) - if valueExpr != nil { - k.SetValue(valueExpr) - } - - return k -} - -func (this *GoLanguageFrontend) handleBasicLit(fset *token.FileSet, lit *ast.BasicLit) *cpg.Literal { - this.LogTrace("Handling literal %+v", *lit) - - var value cpg.Castable - var t *cpg.Type - - switch lit.Kind { - case token.STRING: - // strip the " - value = cpg.NewString(lit.Value[1 : len(lit.Value)-1]) - t = this.primitiveType("string") - case token.INT: - i, _ := strconv.ParseInt(lit.Value, 10, 64) - value = cpg.NewInteger(int(i)) - t = this.primitiveType("int") - case token.FLOAT: - // default seems to be float64 - f, _ := strconv.ParseFloat(lit.Value, 64) - value = cpg.NewDouble(f) - t = this.primitiveType("float64") - case token.IMAG: - // TODO - t = this.unknownType() - case token.CHAR: - value = cpg.NewString(lit.Value) - t = this.primitiveType("rune") - break - } - - l := this.NewLiteral(fset, lit, value, t) - - return l -} - -// handleCompositeLit handles a composite literal, which we need to translate into a combination of a -// ConstructExpression and a list of KeyValueExpressions. The problem is that we need to add the list -// as a first argument of the construct expression. -func (this *GoLanguageFrontend) handleCompositeLit(fset *token.FileSet, lit *ast.CompositeLit) *cpg.ConstructExpression { - this.LogTrace("Handling composite literal %+v", *lit) - - c := this.NewConstructExpression(fset, lit) - - // parse the type field, to see which kind of expression it is - var typ = this.handleType(fset, lit.Type) - - if typ != nil { - (*cpg.Node)(c).SetName(typ.GetName()) - (*cpg.Expression)(c).SetType(typ) - } - - l := this.NewInitializerListExpression(fset, lit) - - c.AddArgument((*cpg.Expression)(l)) - - // Normally, the construct expression would not have DFG edge, but in this case we are mis-using it - // to simulate an object literal, so we need to add a DFG here, otherwise a declaration is disconnected - // from its initialization. - c.AddPrevDFG((*cpg.Node)(l)) - - var exprs = []*cpg.Expression{} - for _, elem := range lit.Elts { - expr := this.handleExpr(fset, elem) - - if expr != nil { - exprs = append(exprs, expr) - } - } - - l.SetInitializers(exprs) - - return c -} - -// handleFuncLit handles a function literal, which we need to translate into a combination of a -// LambdaExpression and a function declaration. -func (this *GoLanguageFrontend) handleFuncLit(fset *token.FileSet, lit *ast.FuncLit) *cpg.LambdaExpression { - this.LogTrace("Handling function literal %#v", *lit) - - l := this.NewLambdaExpression(fset, lit) - - // Parse the expression as a function declaration with a little trick - funcDecl := this.handleFuncDecl(fset, &ast.FuncDecl{Type: lit.Type, Body: lit.Body, Name: ast.NewIdent("")}) - - this.LogTrace("Function of literal is: %#v", funcDecl) - - l.SetFunction(funcDecl) - - return l -} - -func (this *GoLanguageFrontend) handleIdent(fset *token.FileSet, ident *ast.Ident) *cpg.Expression { - // Check, if this is 'nil', because then we handle it as a literal in the graph - if ident.Name == "nil" { - lit := this.NewLiteral(fset, ident, nil, this.unknownType()) - - (*cpg.Node)(lit).SetName(this.ParseName(ident.Name)) - - return (*cpg.Expression)(lit) - } - - ref := this.NewDeclaredReferenceExpression(fset, ident, ident.Name) - - tu := this.CurrentTU - - // check, if this refers to a package import - i := tu.GetIncludeByName(ident.Name) - - // then set the refersTo, because our regular CPG passes will not resolve them - if i != nil && !(*jnigi.ObjectRef)(i).IsNil() { - ref.SetRefersTo((*cpg.Declaration)(i)) - } - - return (*cpg.Expression)(ref) -} - -func (this *GoLanguageFrontend) handleTypeAssertExpr(fset *token.FileSet, assert *ast.TypeAssertExpr) *cpg.CastExpression { - cast := this.NewCastExpression(fset, assert) - - // Parse the inner expression - expr := this.handleExpr(fset, assert.X) - - // Parse the type - typ := this.handleType(fset, assert.Type) - - cast.SetExpression(expr) - - if typ != nil { - cast.SetCastType(typ) - } - - return cast -} - -func (this *GoLanguageFrontend) handleType(fset *token.FileSet, typeExpr ast.Expr) *cpg.Type { - var err error - - this.LogTrace("Parsing type %T: %s", typeExpr, code(fset, typeExpr)) - - lang, err := this.GetLanguage() - if err != nil { - panic(err) - } - - switch v := typeExpr.(type) { - case *ast.Ident: - var name string - if this.isBuiltinType(v.Name) { - name = v.Name - this.LogTrace("non-fqn type: %s", name) - } else { - name = fmt.Sprintf("%s.%s", this.File.Name.Name, v.Name) - this.LogTrace("fqn type: %s", name) - } - - return this.objectType(name) - case *ast.SelectorExpr: - // small shortcut - fqn := fmt.Sprintf("%s.%s", v.X.(*ast.Ident).Name, v.Sel.Name) - this.LogTrace("FQN type: %s", fqn) - return this.objectType(fqn) - case *ast.StarExpr: - t := this.handleType(fset, v.X) - - this.LogTrace("Pointer to %s", t.GetName()) - - return this.ref(t, "pointer") - case *ast.ArrayType: - t := this.handleType(fset, v.Elt) - - this.LogTrace("Array of %s", t.GetName()) - - return this.ref(t, "array") - case *ast.MapType: - // we cannot properly represent Golangs built-in map types, yet so we have - // to make a shortcut here and represent it as a Java-like map type. - t := this.objectType("map") - keyType := this.handleType(fset, v.Key) - valueType := this.handleType(fset, v.Value) - - // TODO(oxisto): Find a better way to represent casts - (*cpg.ObjectType)(t).AddGeneric(keyType) - (*cpg.ObjectType)(t).AddGeneric(valueType) - - return t - case *ast.ChanType: - // handle them similar to maps - t := this.objectType("chan") - chanType := this.handleType(fset, v.Value) - - (*cpg.ObjectType)(t).AddGeneric(chanType) - - return t - case *ast.FuncType: - var parametersTypesList, returnTypesList, name *jnigi.ObjectRef - var parameterTypes []*cpg.Type - var returnTypes []*cpg.Type - - for _, param := range v.Params.List { - parameterTypes = append(parameterTypes, this.handleType(fset, param.Type)) - } - - parametersTypesList, err = cpg.ListOf(parameterTypes) - if err != nil { - log.Fatal(err) - } - - if v.Results != nil { - for _, ret := range v.Results.List { - returnTypes = append(returnTypes, this.handleType(fset, ret.Type)) - } - } - - returnTypesList, err = cpg.ListOf(returnTypes) - if err != nil { - log.Fatal(err) - } - - name, err = cpg.StringOf(funcTypeName(parameterTypes, returnTypes)) - if err != nil { - log.Fatal(err) - } - - var t, err = env.NewObject(cpg.FunctionTypeClass, - name, - parametersTypesList.Cast("java/util/List"), - returnTypesList.Cast("java/util/List"), - lang) - if err != nil { - log.Fatal(err) - } - - return &cpg.Type{ObjectRef: t} - case *ast.InterfaceType: - var name = "interface{" - // We do not really support dedicated interfaces types, so all we can for now - // is parse it as an object type with a pseudo-name - for _, method := range v.Methods.List { - name += this.handleType(fset, method.Type).GetName().ToString() - } - - name += "}" - - return this.objectType(name) - case *ast.IndexExpr: - // This is a type with one type parameter. First we need to parse the "X" expression as a type - var t = this.handleType(fset, v.X) - - // Then we parse the "Index" as a type parameter - var genericType = this.handleType(fset, v.Index) - - (*cpg.ObjectType)(t).AddGeneric(genericType) - - return t - case *ast.IndexListExpr: - // This is a type with two type parameters. First we need to parse the "X" expression as a type - var t = this.handleType(fset, v.X) - - // Then we parse the "Indices" as a type parameter - for _, index := range v.Indices { - var genericType = this.handleType(fset, index) - - (*cpg.ObjectType)(t).AddGeneric(genericType) - } - - return t - default: - this.LogError("Not parsing type of type %T yet. Defaulting to unknown type", v) - } - - return this.unknownType() -} - -func (this *GoLanguageFrontend) isBuiltinType(s string) bool { - switch s { - case "bool": - fallthrough - case "byte": - fallthrough - case "complex128": - fallthrough - case "complex64": - fallthrough - case "error": - fallthrough - case "float32": - fallthrough - case "float64": - fallthrough - case "int": - fallthrough - case "int16": - fallthrough - case "int32": - fallthrough - case "int64": - fallthrough - case "int8": - fallthrough - case "rune": - fallthrough - case "string": - fallthrough - case "uint": - fallthrough - case "uint16": - fallthrough - case "uint32": - fallthrough - case "uint64": - fallthrough - case "uint8": - fallthrough - case "uintptr": - return true - default: - return false - } -} - -func (this *GoLanguageFrontend) ParseName(fqn string) *cpg.Name { - var n *cpg.Name = (*cpg.Name)(jnigi.NewObjectRef(cpg.NameClass)) - err := env.CallStaticMethod(cpg.NameKtClass, "parseName", n, this.Cast(LanguageProviderClass), cpg.NewCharSequence(fqn)) - if err != nil { - log.Fatal(err) - } - - return n -} - -// funcTypeName produces a Go-style function type name such as `func(int, string) string` or `func(int) (error, string)` -func funcTypeName(paramTypes []*cpg.Type, returnTypes []*cpg.Type) string { - var rn []string - var pn []string - - for _, t := range paramTypes { - pn = append(pn, t.GetName().ToString()) - } - - for _, t := range returnTypes { - rn = append(rn, t.GetName().ToString()) - } - - var rs string - - if len(returnTypes) > 1 { - rs = fmt.Sprintf(" (%s)", strings.Join(rn, ", ")) - } else if len(returnTypes) > 0 { - rs = fmt.Sprintf(" %s", strings.Join(rn, ", ")) - } - - return fmt.Sprintf("func(%s)%s", strings.Join(pn, ", "), rs) -} diff --git a/cpg-language-go/src/main/golang/frontend/statement_builder.go b/cpg-language-go/src/main/golang/frontend/statement_builder.go deleted file mode 100644 index 93db27d63f..0000000000 --- a/cpg-language-go/src/main/golang/frontend/statement_builder.go +++ /dev/null @@ -1,93 +0,0 @@ -// -// Copyright (c) 2022, 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 frontend - -import ( - "cpg" - "fmt" - "go/ast" - "go/token" - - "tekao.net/jnigi" -) - -func (frontend *GoLanguageFrontend) NewCompoundStatement(fset *token.FileSet, astNode ast.Node) *cpg.CompoundStatement { - return (*cpg.CompoundStatement)(frontend.NewStatement("CompoundStatement", fset, astNode)) -} - -func (frontend *GoLanguageFrontend) NewReturnStatement(fset *token.FileSet, astNode ast.Node) *cpg.ReturnStatement { - return (*cpg.ReturnStatement)(frontend.NewStatement("ReturnStatement", fset, astNode)) -} - -func (frontend *GoLanguageFrontend) NewDeclarationStatement(fset *token.FileSet, astNode ast.Node) *cpg.DeclarationStatement { - return (*cpg.DeclarationStatement)(frontend.NewStatement("DeclarationStatement", fset, astNode)) -} - -func (frontend *GoLanguageFrontend) NewIfStatement(fset *token.FileSet, astNode ast.Node) *cpg.IfStatement { - return (*cpg.IfStatement)(frontend.NewStatement("IfStatement", fset, astNode)) -} - -func (frontend *GoLanguageFrontend) NewForStatement(fset *token.FileSet, astNode ast.Node) *cpg.ForStatement { - return (*cpg.ForStatement)(frontend.NewStatement("ForStatement", fset, astNode)) -} - -func (frontend *GoLanguageFrontend) NewForEachStatement(fset *token.FileSet, astNode ast.Node) *cpg.ForEachStatement { - return (*cpg.ForEachStatement)(frontend.NewStatement("ForEachStatement", fset, astNode)) -} - -func (frontend *GoLanguageFrontend) NewSwitchStatement(fset *token.FileSet, astNode ast.Node) *cpg.SwitchStatement { - return (*cpg.SwitchStatement)(frontend.NewStatement("SwitchStatement", fset, astNode)) -} - -func (frontend *GoLanguageFrontend) NewCaseStatement(fset *token.FileSet, astNode ast.Node) *cpg.CaseStatement { - return (*cpg.CaseStatement)(frontend.NewStatement("CaseStatement", fset, astNode)) -} - -func (frontend *GoLanguageFrontend) NewDefaultStatement(fset *token.FileSet, astNode ast.Node) *cpg.DefaultStatement { - return (*cpg.DefaultStatement)(frontend.NewStatement("DefaultStatement", fset, astNode)) -} - -func (frontend *GoLanguageFrontend) NewStatement(typ string, fset *token.FileSet, astNode ast.Node, args ...any) *jnigi.ObjectRef { - var node = jnigi.NewObjectRef(fmt.Sprintf("%s/%s", cpg.StatementsPackage, typ)) - - // Prepend the frontend as the receiver - args = append([]any{frontend.Cast(cpg.GraphPackage + "/MetadataProvider")}, args...) - - err := env.CallStaticMethod( - cpg.GraphPackage+"/StatementBuilderKt", - fmt.Sprintf("new%s", typ), node, - args..., - ) - if err != nil { - panic(err) - } - - updateCode(fset, (*cpg.Node)(node), astNode) - updateLocation(fset, (*cpg.Node)(node), astNode) - - return node -} diff --git a/cpg-language-go/src/main/golang/frontend/type_builder.go b/cpg-language-go/src/main/golang/frontend/type_builder.go deleted file mode 100644 index 649d396d69..0000000000 --- a/cpg-language-go/src/main/golang/frontend/type_builder.go +++ /dev/null @@ -1,91 +0,0 @@ -// -// Copyright (c) 2023, 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 frontend - -import ( - "cpg" - "fmt" - "log" - "strings" - "tekao.net/jnigi" -) - -func (frontend *GoLanguageFrontend) unknownType() *cpg.Type { - return frontend.newType("MetadataProvider", "UnknownType") -} - -func (frontend *GoLanguageFrontend) primitiveType(typeName string) *cpg.Type { - return frontend.newType("LanguageProvider", "PrimitiveType", cpg.NewCharSequence(typeName)) -} - -func (frontend *GoLanguageFrontend) objectType(typeName string) *cpg.Type { - return frontend.newType("LanguageProvider", "ObjectType", cpg.NewCharSequence(typeName)) -} - -func (frontend *GoLanguageFrontend) newType(providerClass string, typ string, args ...any) *cpg.Type { - var node cpg.Type - - // Prepend the frontend as the receiver - args = append([]any{frontend.Cast(cpg.GraphPackage + fmt.Sprintf("/%s", providerClass))}, args...) - - err := env.CallStaticMethod( - cpg.GraphPackage+"/TypeBuilderKt", - fmt.Sprintf("%s%s", strings.ToLower(string(typ[0])), typ[1:]), &node, - args..., - ) - if err != nil { - log.Fatal(err) - } - - return &node -} - -func (frontend *GoLanguageFrontend) ref(t *cpg.Type, typ string) *cpg.Type { - var ( - refType *jnigi.ObjectRef - args []any - ) - - refType = jnigi.NewObjectRef(cpg.TypeClass) - - args = []any{ - frontend.Cast(cpg.GraphPackage + fmt.Sprintf("/%s", "ContextProvider")), - t, - } - - err := env.CallStaticMethod( - cpg.GraphPackage+"/TypeBuilderKt", - typ, - refType, - args..., - ) - if err != nil { - panic(err) - } - - return &cpg.Type{ObjectRef: refType} -} diff --git a/cpg-language-go/src/main/golang/go.mod b/cpg-language-go/src/main/golang/go.mod index 22bc2aeaf4..a37f780424 100644 --- a/cpg-language-go/src/main/golang/go.mod +++ b/cpg-language-go/src/main/golang/go.mod @@ -1,8 +1,8 @@ module cpg -go 1.19 +go 1.20 require ( + github.com/mattn/go-pointer v0.0.1 golang.org/x/mod v0.12.0 - tekao.net/jnigi v0.0.0-20230402215112-69b87aaf8714 ) diff --git a/cpg-language-go/src/main/golang/go.sum b/cpg-language-go/src/main/golang/go.sum index 8d1114cd60..9333aa5928 100644 --- a/cpg-language-go/src/main/golang/go.sum +++ b/cpg-language-go/src/main/golang/go.sum @@ -1,18 +1,4 @@ -golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA= -golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs= -golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= -golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU= -golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +github.com/mattn/go-pointer v0.0.1 h1:n+XhsuGeVO6MEAp7xyEukFINEa+Quek5psIR/ylA6o0= +github.com/mattn/go-pointer v0.0.1/go.mod h1:2zXcozF6qYGgmsG+SeTZz3oAbFLdD3OWqnUbNvJZAlc= golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -tekao.net/jnigi v0.0.0-20220921102452-ce6d0be0c331 h1:p5apvrQZPCacG+Ux6GMzLWX4mUZOPlguj0MrONXutrQ= -tekao.net/jnigi v0.0.0-20220921102452-ce6d0be0c331/go.mod h1:SmVvXetJ8N0ov5c2eOC+IxmkdYGEyuXghTuBq5HWZ/Y= -tekao.net/jnigi v0.0.0-20221227053512-56e0101fa996 h1:Vl0GEBxRKyS1+/fjd9H6ptV7t/CAmfgxtsanvqsCob8= -tekao.net/jnigi v0.0.0-20221227053512-56e0101fa996/go.mod h1:SmVvXetJ8N0ov5c2eOC+IxmkdYGEyuXghTuBq5HWZ/Y= -tekao.net/jnigi v0.0.0-20230402215112-69b87aaf8714 h1:bfygqxA+ZWLWg06ti7t7lYs79xogFFLu1Xraqb30Nrc= -tekao.net/jnigi v0.0.0-20230402215112-69b87aaf8714/go.mod h1:SmVvXetJ8N0ov5c2eOC+IxmkdYGEyuXghTuBq5HWZ/Y= diff --git a/cpg-language-go/src/main/golang/helper.go b/cpg-language-go/src/main/golang/helper.go deleted file mode 100644 index 52a31156d1..0000000000 --- a/cpg-language-go/src/main/golang/helper.go +++ /dev/null @@ -1,58 +0,0 @@ -// -// Copyright (c) 2022, 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 cpg - -import "tekao.net/jnigi" - -type Castable interface { - Cast(className string) *jnigi.ObjectRef -} - -func ListOf[T Castable](slice []T) (list *jnigi.ObjectRef, err error) { - list, err = env.NewObject("java/util/ArrayList") - if err != nil { - return nil, err - } - - for _, t := range slice { - var dummy bool - if err := list.CallMethod(env, "add", &dummy, t.Cast("java/lang/Object")); err != nil { - return nil, err - } - } - - return -} - -func StringOf(str string) (obj *jnigi.ObjectRef, err error) { - obj, err = env.NewObject("java/lang/String", []byte(str)) - if err != nil { - return nil, err - } - - return -} diff --git a/cpg-language-go/src/main/golang/language.go b/cpg-language-go/src/main/golang/language.go deleted file mode 100644 index 8e92564fd1..0000000000 --- a/cpg-language-go/src/main/golang/language.go +++ /dev/null @@ -1,75 +0,0 @@ -// -// Copyright (c) 2021, 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 cpg - -import ( - "tekao.net/jnigi" -) - -type Language Node - -const TranslationContextClass = CPGPackage + "/TranslationContext" - -const FrontendsPackage = CPGPackage + "/frontends" -const GolangPackage = FrontendsPackage + "/golang" -const LanguageClass = FrontendsPackage + "/Language" -const LanguageFrontendClass = FrontendsPackage + "/LanguageFrontend" -const GoLanguageFrontendClass = GolangPackage + "/GoLanguageFrontend" - -func (l *Language) ConvertToGo(o *jnigi.ObjectRef) error { - *l = (Language)(*o) - return nil -} - -func (l *Language) ConvertToJava() (obj *jnigi.ObjectRef, err error) { - return (*jnigi.ObjectRef)(l), nil -} - -func (l *Language) GetClassName() string { - return LanguageClass -} - -func (l *Language) IsArray() bool { - return false -} - -func (ctx *TranslationContext) ConvertToGo(o *jnigi.ObjectRef) error { - *ctx = (TranslationContext)(*o) - return nil -} - -func (ctx *TranslationContext) ConvertToJava() (obj *jnigi.ObjectRef, err error) { - return (*jnigi.ObjectRef)(ctx), nil -} - -func (*TranslationContext) GetClassName() string { - return TranslationContextClass -} - -func (*TranslationContext) IsArray() bool { - return false -} diff --git a/cpg-language-go/src/main/golang/lib/cpg/main.go b/cpg-language-go/src/main/golang/lib/cpg/main.go index 273c321112..f276a47a1e 100644 --- a/cpg-language-go/src/main/golang/lib/cpg/main.go +++ b/cpg-language-go/src/main/golang/lib/cpg/main.go @@ -27,88 +27,865 @@ package main import ( - "cpg" - "cpg/frontend" + "C" + "bytes" + "fmt" "go/ast" "go/parser" + "go/printer" "go/token" - - "log" + "reflect" + "strings" "unsafe" - "tekao.net/jnigi" -) + "golang.org/x/mod/modfile" + "golang.org/x/mod/module" -//#include -import "C" + pointer "github.com/mattn/go-pointer" +) func main() { } -//export Java_de_fraunhofer_aisec_cpg_frontends_golang_GoLanguageFrontend_parseInternal -func Java_de_fraunhofer_aisec_cpg_frontends_golang_GoLanguageFrontend_parseInternal(envPointer *C.JNIEnv, thisPtr C.jobject, arg1 C.jobject, arg2 C.jobject, arg3 C.jobject) C.jobject { - env := jnigi.WrapEnv(unsafe.Pointer(envPointer)) +//export GetType +func GetType(ptr unsafe.Pointer) *C.char { + v := pointer.Restore(ptr) + return C.CString(fmt.Sprintf("%T", v)) +} - goFrontend := &frontend.GoLanguageFrontend{ - jnigi.WrapJObject( - uintptr(thisPtr), - cpg.GoLanguageFrontendClass, - false, - ), - nil, - nil, - ast.CommentMap{}, - "", - nil, - } +//export NewFileSet +func NewFileSet() unsafe.Pointer { + return save(token.NewFileSet()) +} - srcObject := jnigi.WrapJObject(uintptr(arg1), "java/lang/String", false) - pathObject := jnigi.WrapJObject(uintptr(arg2), "java/lang/String", false) - topLevelObject := jnigi.WrapJObject(uintptr(arg3), "java/lang/String", false) +//export goParserParseFile +func goParserParseFile(fset unsafe.Pointer, path *C.char, src *C.char) unsafe.Pointer { + f, err := parser.ParseFile( + pointer.Restore(fset).(*token.FileSet), + C.GoString(path), C.GoString(src), parser.ParseComments) + if err != nil { + panic(err) + } - frontend.InitEnv(env) - cpg.InitEnv(env) + return save(f) +} - var src []byte - err := srcObject.CallMethod(env, "getBytes", &src) +//export modfileParse +func modfileParse(path *C.char, bytes *C.char) unsafe.Pointer { + f, err := modfile.Parse(C.GoString(path), []byte(C.GoString(bytes)), nil) if err != nil { - log.Fatal(err) + panic(err) } - var path []byte - err = pathObject.CallMethod(env, "getBytes", &path) - if err != nil { - log.Fatal(err) + return save(f) +} + +//export modfileGetFileModule +func modfileGetFileModule(ptr unsafe.Pointer) unsafe.Pointer { + f := restore[*modfile.File](ptr) + return save(f.Module) +} + +//export modfileGetModuleMod +func modfileGetModuleMod(ptr unsafe.Pointer) unsafe.Pointer { + m := restore[*modfile.Module](ptr) + return save(m.Mod) +} + +//export moduleGetVersionPath +func moduleGetVersionPath(ptr unsafe.Pointer) *C.char { + v := restore[module.Version](ptr) + return C.CString(v.Path) +} + +//export NewCommentMap +func NewCommentMap(ptr1 unsafe.Pointer, ptr2 unsafe.Pointer, ptr3 unsafe.Pointer) unsafe.Pointer { + fset := restore[*token.FileSet](ptr1) + node := restore[ast.Node](ptr2) + comments := restore[*[]*ast.CommentGroup](ptr3) + m := ast.NewCommentMap(fset, node, *comments) + return save(m) +} + +//export GetFileSetPosition +func GetFileSetPosition(ptr unsafe.Pointer, pos int) unsafe.Pointer { + fset := restore[*token.FileSet](ptr) + position := fset.Position(token.Pos(pos)) + return save(&position) +} + +//export GetFileSetFileName +func GetFileSetFileName(ptr unsafe.Pointer, pos int) *C.char { + fset := restore[*token.FileSet](ptr) + file := fset.File(token.Pos(pos)) + if file != nil { + return C.CString(file.Name()) + } else { + return nil } +} - var topLevel []byte - err = topLevelObject.CallMethod(env, "getBytes", &topLevel) - if err != nil { - log.Fatal(err) +//export GetFileSetNodeCode +func GetFileSetNodeCode(ptr1 unsafe.Pointer, ptr2 unsafe.Pointer) *C.char { + fset := restore[*token.FileSet](ptr1) + node := restore[ast.Node](ptr2) + var codeBuf bytes.Buffer + _ = printer.Fprint(&codeBuf, fset, node) + + return C.CString(codeBuf.String()) +} + +//export GetPositionLine +func GetPositionLine(ptr unsafe.Pointer) int { + pos := restore[*token.Position](ptr) + return pos.Line +} + +//export GetPositionColumn +func GetPositionColumn(ptr unsafe.Pointer) int { + pos := restore[*token.Position](ptr) + return pos.Column +} + +//export GetNodePos +func GetNodePos(ptr unsafe.Pointer) int { + node := pointer.Restore(ptr).(ast.Node) + return int(node.Pos()) +} + +//export GetNodeEnd +func GetNodeEnd(ptr unsafe.Pointer) C.int { + node := pointer.Restore(ptr).(ast.Node) + return C.int(node.End()) +} + +//export GetCommentMapNodeComment +func GetCommentMapNodeComment(ptr1 unsafe.Pointer, ptr2 unsafe.Pointer) *C.char { + cm := restore[ast.CommentMap](ptr1) + node := restore[ast.Node](ptr2) + var comment = "" + + // Lookup ast node in comment map. One cannot use Filter() because this would actually filter all the comments + // that are "below" this AST node as well, e.g. in its children. We only want the comments on the node itself. + // Therefore we must convert the CommentMap back into an actual map to access the stored entry for the node. + comments, ok := (map[ast.Node][]*ast.CommentGroup)(cm)[node] + if !ok { + return nil } - goFrontend.TopLevel = string(topLevel) + for _, c := range comments { + text := strings.TrimRight(c.Text(), "\n") + comment += text + } - fset := token.NewFileSet() - file, err := parser.ParseFile(fset, string(path), string(src), parser.ParseComments) - if err != nil { - log.Fatal(err) + return C.CString(comment) +} + +//export GetFileName +func GetFileName(ptr unsafe.Pointer) unsafe.Pointer { + file := pointer.Restore(ptr).(*ast.File) + return save(file.Name) +} + +//export GetNumFileDecls +func GetNumFileDecls(ptr unsafe.Pointer) C.int { + return num[*ast.File](ptr, func(t *ast.File) []ast.Decl { + return t.Decls + }) +} + +//export GetFileDecl +func GetFileDecl(ptr unsafe.Pointer, i int) unsafe.Pointer { + return item[*ast.File](ptr, i, func(t *ast.File) []ast.Decl { + return t.Decls + }) +} + +//export GetNumFileImports +func GetNumFileImports(ptr unsafe.Pointer) C.int { + return num[*ast.File](ptr, func(t *ast.File) []*ast.ImportSpec { + return t.Imports + }) +} + +//export GetFileImport +func GetFileImport(ptr unsafe.Pointer, i int) unsafe.Pointer { + return item[*ast.File](ptr, i, func(t *ast.File) []*ast.ImportSpec { + return t.Imports + }) +} + +//export GetFileComments +func GetFileComments(ptr unsafe.Pointer) unsafe.Pointer { + file := restore[*ast.File](ptr) + return save(&file.Comments) +} + +//export GetNumFieldListList +func GetNumFieldListList(ptr unsafe.Pointer) C.int { + return num[*ast.FieldList](ptr, func(t *ast.FieldList) []*ast.Field { + return t.List + }) +} + +//export GetFieldListList +func GetFieldListList(ptr unsafe.Pointer, i int) unsafe.Pointer { + return item[*ast.FieldList](ptr, i, func(t *ast.FieldList) []*ast.Field { + return t.List + }) +} + +//export GetBinaryExprX +func GetBinaryExprX(ptr unsafe.Pointer) unsafe.Pointer { + bin := restore[*ast.BinaryExpr](ptr) + return save(bin.X) +} + +//export GetBinaryExprOpString +func GetBinaryExprOpString(ptr unsafe.Pointer) *C.char { + bin := restore[*ast.BinaryExpr](ptr) + return C.CString(bin.Op.String()) +} + +//export GetBinaryExprY +func GetBinaryExprY(ptr unsafe.Pointer) unsafe.Pointer { + bin := restore[*ast.BinaryExpr](ptr) + return save(bin.Y) +} + +//export GetDeclStmtDecl +func GetDeclStmtDecl(ptr unsafe.Pointer) unsafe.Pointer { + stmt := restore[*ast.DeclStmt](ptr) + return save(stmt.Decl) +} + +//export GetExprStmtX +func GetExprStmtX(ptr unsafe.Pointer) unsafe.Pointer { + stmt := restore[*ast.ExprStmt](ptr) + return save(stmt.X) +} + +//export GetCallExprFun +func GetCallExprFun(ptr unsafe.Pointer) unsafe.Pointer { + c := restore[*ast.CallExpr](ptr) + return save(c.Fun) +} + +//export GetNumCallExprArgs +func GetNumCallExprArgs(ptr unsafe.Pointer) C.int { + return num[*ast.CallExpr](ptr, func(t *ast.CallExpr) []ast.Expr { + return t.Args + }) +} + +//export GetCallExprArg +func GetCallExprArg(ptr unsafe.Pointer, i int) unsafe.Pointer { + return item[*ast.CallExpr](ptr, i, func(t *ast.CallExpr) []ast.Expr { + return t.Args + }) +} + +//export GetForStmtInit +func GetForStmtInit(ptr unsafe.Pointer) unsafe.Pointer { + stmt := restore[*ast.ForStmt](ptr) + return save(stmt.Init) +} + +//export GetForStmtCond +func GetForStmtCond(ptr unsafe.Pointer) unsafe.Pointer { + stmt := restore[*ast.ForStmt](ptr) + return save(stmt.Cond) +} + +//export GetForStmtPost +func GetForStmtPost(ptr unsafe.Pointer) unsafe.Pointer { + stmt := restore[*ast.ForStmt](ptr) + return save(stmt.Post) +} + +//export GetForStmtBody +func GetForStmtBody(ptr unsafe.Pointer) unsafe.Pointer { + stmt := restore[*ast.ForStmt](ptr) + return save(stmt.Body) +} + +//export GetGoStmtCall +func GetGoStmtCall(ptr unsafe.Pointer) unsafe.Pointer { + stmt := restore[*ast.GoStmt](ptr) + return save(stmt.Call) +} + +//export GetIncDecStmtTokString +func GetIncDecStmtTokString(ptr unsafe.Pointer) *C.char { + stmt := restore[*ast.IncDecStmt](ptr) + return C.CString(stmt.Tok.String()) +} + +//export GetIncDecStmtX +func GetIncDecStmtX(ptr unsafe.Pointer) unsafe.Pointer { + stmt := restore[*ast.IncDecStmt](ptr) + return save(stmt.X) +} + +//export GetIfStmtInit +func GetIfStmtInit(ptr unsafe.Pointer) unsafe.Pointer { + stmt := restore[*ast.IfStmt](ptr) + return save(stmt.Init) +} + +//export GetIfStmtCond +func GetIfStmtCond(ptr unsafe.Pointer) unsafe.Pointer { + stmt := restore[*ast.IfStmt](ptr) + return save(stmt.Cond) +} + +//export GetIfStmtBody +func GetIfStmtBody(ptr unsafe.Pointer) unsafe.Pointer { + stmt := restore[*ast.IfStmt](ptr) + return save(stmt.Body) +} + +//export GetIfStmtElse +func GetIfStmtElse(ptr unsafe.Pointer) unsafe.Pointer { + stmt := restore[*ast.IfStmt](ptr) + return save(stmt.Else) +} + +//export GetLabeledStmtLabel +func GetLabeledStmtLabel(ptr unsafe.Pointer) unsafe.Pointer { + stmt := restore[*ast.LabeledStmt](ptr) + return save(stmt.Label) +} + +//export GetLabeledStmtStmt +func GetLabeledStmtStmt(ptr unsafe.Pointer) unsafe.Pointer { + stmt := restore[*ast.LabeledStmt](ptr) + return save(stmt.Stmt) +} + +//export GetRangeStmtTokString +func GetRangeStmtTokString(ptr unsafe.Pointer) *C.char { + stmt := restore[*ast.RangeStmt](ptr) + return C.CString(stmt.Tok.String()) +} + +//export GetRangeStmtKey +func GetRangeStmtKey(ptr unsafe.Pointer) unsafe.Pointer { + stmt := restore[*ast.RangeStmt](ptr) + return save(stmt.Key) +} + +//export GetRangeStmtValue +func GetRangeStmtValue(ptr unsafe.Pointer) unsafe.Pointer { + stmt := restore[*ast.RangeStmt](ptr) + return save(stmt.Value) +} + +//export GetRangeStmtX +func GetRangeStmtX(ptr unsafe.Pointer) unsafe.Pointer { + stmt := restore[*ast.RangeStmt](ptr) + return save(stmt.X) +} + +//export GetRangeStmtBody +func GetRangeStmtBody(ptr unsafe.Pointer) unsafe.Pointer { + stmt := restore[*ast.RangeStmt](ptr) + return save(stmt.Body) +} + +//export GetBasicLitKind +func GetBasicLitKind(ptr unsafe.Pointer) C.int { + lit := restore[*ast.BasicLit](ptr) + return C.int(lit.Kind) +} + +//export GetBasicLitValue +func GetBasicLitValue(ptr unsafe.Pointer) *C.char { + lit := restore[*ast.BasicLit](ptr) + return C.CString(lit.Value) +} + +//export GetCompositeLitType +func GetCompositeLitType(ptr unsafe.Pointer) unsafe.Pointer { + lit := restore[*ast.CompositeLit](ptr) + return save(lit.Type) +} + +//export GetNumCompositeLitElts +func GetNumCompositeLitElts(ptr unsafe.Pointer) C.int { + return num[*ast.CompositeLit](ptr, func(t *ast.CompositeLit) []ast.Expr { + return t.Elts + }) +} + +//export GetCompositeLitElt +func GetCompositeLitElt(ptr unsafe.Pointer, i int) unsafe.Pointer { + return item[*ast.CompositeLit](ptr, i, func(t *ast.CompositeLit) []ast.Expr { + return t.Elts + }) +} + +//export MakeFuncDeclFromFuncLit +func MakeFuncDeclFromFuncLit(ptr unsafe.Pointer) unsafe.Pointer { + lit := restore[*ast.FuncLit](ptr) + if lit == nil { + return nil + } + decl := &ast.FuncDecl{ + Doc: nil, + Recv: nil, + Name: ast.NewIdent(""), + Type: lit.Type, + Body: lit.Body, } + return save(decl) +} - goFrontend.CommentMap = ast.NewCommentMap(fset, file, file.Comments) +//export GetIdentNamePos +func GetIdentNamePos(ptr unsafe.Pointer) C.int { + ident := pointer.Restore(ptr).(*ast.Ident) + return C.int(ident.NamePos) +} - _, err = goFrontend.ParseModule(string(topLevel)) - if err != nil { - goFrontend.LogError("Error occurred while looking for Go modules file: %v", err) +//export GetIdentName +func GetIdentName(ptr unsafe.Pointer) *C.char { + ident := pointer.Restore(ptr).(*ast.Ident) + return C.CString(ident.Name) +} + +//export GetIndexExprX +func GetIndexExprX(ptr unsafe.Pointer) unsafe.Pointer { + expr := restore[*ast.IndexExpr](ptr) + return save(expr.X) +} + +//export GetIndexExprIndex +func GetIndexExprIndex(ptr unsafe.Pointer) unsafe.Pointer { + expr := restore[*ast.IndexExpr](ptr) + return save(expr.Index) +} + +//export GetKeyValueExprKey +func GetKeyValueExprKey(ptr unsafe.Pointer) unsafe.Pointer { + kv := restore[*ast.KeyValueExpr](ptr) + return save(kv.Key) +} + +//export GetKeyValueExprValue +func GetKeyValueExprValue(ptr unsafe.Pointer) unsafe.Pointer { + kv := restore[*ast.KeyValueExpr](ptr) + return save(kv.Value) +} + +//export GetSelectorExprX +func GetSelectorExprX(ptr unsafe.Pointer) unsafe.Pointer { + sel := restore[*ast.SelectorExpr](ptr) + return save(sel.X) +} + +//export GetSelectorExprSel +func GetSelectorExprSel(ptr unsafe.Pointer) unsafe.Pointer { + sel := restore[*ast.SelectorExpr](ptr) + return save(sel.Sel) +} + +//export GetSliceExprX +func GetSliceExprX(ptr unsafe.Pointer) unsafe.Pointer { + expr := restore[*ast.SliceExpr](ptr) + return save(expr.X) +} + +//export GetSliceExprLow +func GetSliceExprLow(ptr unsafe.Pointer) unsafe.Pointer { + expr := restore[*ast.SliceExpr](ptr) + return save(expr.Low) +} + +//export GetSliceExprHigh +func GetSliceExprHigh(ptr unsafe.Pointer) unsafe.Pointer { + expr := restore[*ast.SliceExpr](ptr) + return save(expr.High) +} + +//export GetSliceExprMax +func GetSliceExprMax(ptr unsafe.Pointer) unsafe.Pointer { + expr := restore[*ast.SliceExpr](ptr) + return save(expr.Max) +} + +//export GetStarExprX +func GetStarExprX(ptr unsafe.Pointer) unsafe.Pointer { + star := restore[*ast.StarExpr](ptr) + return save(star.X) +} + +//export GetTypeAssertExprX +func GetTypeAssertExprX(ptr unsafe.Pointer) unsafe.Pointer { + expr := restore[*ast.TypeAssertExpr](ptr) + return save(expr.X) +} + +//export GetTypeAssertExprType +func GetTypeAssertExprType(ptr unsafe.Pointer) unsafe.Pointer { + expr := restore[*ast.TypeAssertExpr](ptr) + return save(expr.Type) +} + +//export GetUnaryExprOpString +func GetUnaryExprOpString(ptr unsafe.Pointer) *C.char { + unary := restore[*ast.UnaryExpr](ptr) + return C.CString(unary.Op.String()) +} + +//export GetUnaryExprX +func GetUnaryExprX(ptr unsafe.Pointer) unsafe.Pointer { + unary := restore[*ast.UnaryExpr](ptr) + return save(unary.X) +} + +//export GetArrayTypeElt +func GetArrayTypeElt(ptr unsafe.Pointer) unsafe.Pointer { + typ := restore[*ast.ArrayType](ptr) + return save(typ.Elt) +} + +//export GetChanTypeValue +func GetChanTypeValue(ptr unsafe.Pointer) unsafe.Pointer { + typ := restore[*ast.ChanType](ptr) + return save(typ.Value) +} + +//export GetFuncTypeParams +func GetFuncTypeParams(ptr unsafe.Pointer) unsafe.Pointer { + typ := restore[*ast.FuncType](ptr) + return save(typ.Params) +} + +//export GetFuncTypeTypeParams +func GetFuncTypeTypeParams(ptr unsafe.Pointer) unsafe.Pointer { + typ := restore[*ast.FuncType](ptr) + return save(typ.TypeParams) +} + +//export GetFuncParams +func GetFuncParams(ptr unsafe.Pointer) unsafe.Pointer { + typ := restore[*ast.FuncType](ptr) + return save(typ.Params) +} + +//export GetFuncTypeResults +func GetFuncTypeResults(ptr unsafe.Pointer) unsafe.Pointer { + typ := restore[*ast.FuncType](ptr) + res := save(typ.Results) + return res +} + +//export GetMapTypeKey +func GetMapTypeKey(ptr unsafe.Pointer) unsafe.Pointer { + typ := restore[*ast.MapType](ptr) + return save(typ.Key) +} + +//export GetMapTypeValue +func GetMapTypeValue(ptr unsafe.Pointer) unsafe.Pointer { + typ := restore[*ast.MapType](ptr) + return save(typ.Value) +} + +//export GetStructTypeFields +func GetStructTypeFields(ptr unsafe.Pointer) unsafe.Pointer { + typ := restore[*ast.StructType](ptr) + return save(typ.Fields) +} + +//export GetStructTypeIncomplete +func GetStructTypeIncomplete(ptr unsafe.Pointer) C.char { + typ := restore[*ast.StructType](ptr) + if typ.Incomplete { + return C.char(1) + } else { + return C.char(0) } +} - goFrontend.File = file +//export GetNumFieldNames +func GetNumFieldNames(ptr unsafe.Pointer) C.int { + return num[*ast.Field](ptr, func(t *ast.Field) []*ast.Ident { + return t.Names + }) +} - tu, err := goFrontend.HandleFile(fset, file, string(path)) - if err != nil { - log.Fatal(err) +//export GetFieldName +func GetFieldName(ptr unsafe.Pointer, i int) unsafe.Pointer { + return item[*ast.Field](ptr, i, func(t *ast.Field) []*ast.Ident { + return t.Names + }) +} + +//export GetFieldType +func GetFieldType(ptr unsafe.Pointer) (typ unsafe.Pointer) { + field := pointer.Restore(ptr).(*ast.Field) + return save(field.Type) +} + +//export GetNumGenDeclSpecs +func GetNumGenDeclSpecs(ptr unsafe.Pointer) C.int { + return num[*ast.GenDecl](ptr, func(t *ast.GenDecl) []ast.Spec { + return t.Specs + }) +} + +//export GetGenDeclSpec +func GetGenDeclSpec(ptr unsafe.Pointer, i int) unsafe.Pointer { + return item[*ast.GenDecl](ptr, i, func(t *ast.GenDecl) []ast.Spec { + return t.Specs + }) +} + +//export GetInterfaceTypeMethods +func GetInterfaceTypeMethods(ptr unsafe.Pointer) unsafe.Pointer { + i := restore[*ast.InterfaceType](ptr) + return save(i.Methods) +} + +//export GetInterfaceTypeIncomplete +func GetInterfaceTypeIncomplete(ptr unsafe.Pointer) C.char { + i := restore[*ast.InterfaceType](ptr) + if i.Incomplete { + return C.char(1) + } else { + return C.char(0) + } +} + +//export GetFuncDeclRecv +func GetFuncDeclRecv(ptr unsafe.Pointer) unsafe.Pointer { + f := restore[*ast.FuncDecl](ptr) + return save(f.Recv) +} + +// GetFuncDeclName returns the name property of a [*ast.FuncDecl] as [*ast.Ident]. +// +//export GetFuncDeclName +func GetFuncDeclName(ptr unsafe.Pointer) unsafe.Pointer { + f := restore[*ast.FuncDecl](ptr) + return save(f.Name) +} + +//export GetFuncDeclType +func GetFuncDeclType(ptr unsafe.Pointer) unsafe.Pointer { + f := restore[*ast.FuncDecl](ptr) + return save(f.Type) +} + +// GetFuncDeclBody returns the body property of a [*ast.FuncDecl] as [*ast.BlockStmt]. +// +//export GetFuncDeclBody +func GetFuncDeclBody(ptr unsafe.Pointer) unsafe.Pointer { + f := restore[*ast.FuncDecl](ptr) + return save(f.Body) +} + +//export GetImportSpecName +func GetImportSpecName(ptr unsafe.Pointer) unsafe.Pointer { + spec := restore[*ast.ImportSpec](ptr) + return save(spec.Name) +} + +//export GetImportSpecPath +func GetImportSpecPath(ptr unsafe.Pointer) unsafe.Pointer { + spec := restore[*ast.ImportSpec](ptr) + return save(spec.Path) +} + +//export GetNumValueSpecNames +func GetNumValueSpecNames(ptr unsafe.Pointer) C.int { + return num[*ast.ValueSpec](ptr, func(t *ast.ValueSpec) []*ast.Ident { + return t.Names + }) +} + +//export GetValueSpecName +func GetValueSpecName(ptr unsafe.Pointer, i int) unsafe.Pointer { + return item[*ast.ValueSpec](ptr, i, func(t *ast.ValueSpec) []*ast.Ident { + return t.Names + }) +} + +//export GetValueSpecType +func GetValueSpecType(ptr unsafe.Pointer) unsafe.Pointer { + spec := restore[*ast.ValueSpec](ptr) + return save(spec.Type) +} + +//export GetNumValueSpecValues +func GetNumValueSpecValues(ptr unsafe.Pointer) C.int { + return num[*ast.ValueSpec](ptr, func(t *ast.ValueSpec) []ast.Expr { + return t.Values + }) +} + +//export GetValueSpecValue +func GetValueSpecValue(ptr unsafe.Pointer, i int) unsafe.Pointer { + return item[*ast.ValueSpec](ptr, i, func(t *ast.ValueSpec) []ast.Expr { + return t.Values + }) +} + +//export GetTypeSpecName +func GetTypeSpecName(ptr unsafe.Pointer) unsafe.Pointer { + spec := restore[*ast.TypeSpec](ptr) + return save(spec.Name) +} + +//export GetTypeSpecType +func GetTypeSpecType(ptr unsafe.Pointer) unsafe.Pointer { + spec := restore[*ast.TypeSpec](ptr) + return save(spec.Type) +} + +//export GetNumBlockStmtList +func GetNumBlockStmtList(ptr unsafe.Pointer) C.int { + stmt := restore[*ast.BlockStmt](ptr) + return C.int(len(stmt.List)) +} + +//export GetBlockStmtList +func GetBlockStmtList(ptr unsafe.Pointer, i int) unsafe.Pointer { + stmt := restore[*ast.BlockStmt](ptr) + return save(stmt.List[i]) +} + +//export GetBranchStmtTokString +func GetBranchStmtTokString(ptr unsafe.Pointer) *C.char { + stmt := restore[*ast.BranchStmt](ptr) + return C.CString(stmt.Tok.String()) +} + +//export GetBranchStmtLabel +func GetBranchStmtLabel(ptr unsafe.Pointer) unsafe.Pointer { + stmt := restore[*ast.BranchStmt](ptr) + return save(stmt.Label) +} + +//export GetNumCaseClauseList +func GetNumCaseClauseList(ptr unsafe.Pointer) C.int { + stmt := restore[*ast.CaseClause](ptr) + return C.int(len(stmt.List)) +} + +//export GetCaseClauseList +func GetCaseClauseList(ptr unsafe.Pointer, i int) unsafe.Pointer { + stmt := restore[*ast.CaseClause](ptr) + return save(stmt.List[i]) +} + +//export GetNumCaseClauseBody +func GetNumCaseClauseBody(ptr unsafe.Pointer) C.int { + stmt := restore[*ast.CaseClause](ptr) + return C.int(len(stmt.Body)) +} + +//export GetCaseClauseBody +func GetCaseClauseBody(ptr unsafe.Pointer, i int) unsafe.Pointer { + stmt := restore[*ast.CaseClause](ptr) + return save(stmt.Body[i]) +} + +//export GetNumAssignStmtLhs +func GetNumAssignStmtLhs(ptr unsafe.Pointer) C.int { + return num[*ast.AssignStmt](ptr, func(t *ast.AssignStmt) []ast.Expr { + return t.Lhs + }) +} + +//export GetAssignStmtLhs +func GetAssignStmtLhs(ptr unsafe.Pointer, i int) unsafe.Pointer { + return item[*ast.AssignStmt](ptr, i, func(t *ast.AssignStmt) []ast.Expr { + return t.Lhs + }) +} + +//export GetAssignStmtTok +func GetAssignStmtTok(ptr unsafe.Pointer) C.int { + stmt := restore[*ast.AssignStmt](ptr) + return C.int(stmt.Tok) +} + +//export GetNumAssignStmtRhs +func GetNumAssignStmtRhs(ptr unsafe.Pointer) C.int { + return num[*ast.AssignStmt](ptr, func(t *ast.AssignStmt) []ast.Expr { + return t.Rhs + }) +} + +//export GetAssignStmtRhs +func GetAssignStmtRhs(ptr unsafe.Pointer, i int) unsafe.Pointer { + return item[*ast.AssignStmt](ptr, i, func(t *ast.AssignStmt) []ast.Expr { + return t.Rhs + }) +} + +//export GetDeferStmtCall +func GetDeferStmtCall(ptr unsafe.Pointer) unsafe.Pointer { + stmt := restore[*ast.DeferStmt](ptr) + return save(stmt.Call) +} + +//export GetNumReturnStmtResults +func GetNumReturnStmtResults(ptr unsafe.Pointer) C.int { + return num[*ast.ReturnStmt](ptr, func(t *ast.ReturnStmt) []ast.Expr { + return t.Results + }) +} + +//export GetReturnStmtResult +func GetReturnStmtResult(ptr unsafe.Pointer, i int) unsafe.Pointer { + return item[*ast.ReturnStmt](ptr, i, func(t *ast.ReturnStmt) []ast.Expr { + return t.Results + }) +} + +//export GetSwitchStmtInit +func GetSwitchStmtInit(ptr unsafe.Pointer) unsafe.Pointer { + stmt := restore[*ast.SwitchStmt](ptr) + return save(stmt.Init) +} + +//export GetSwitchStmtTag +func GetSwitchStmtTag(ptr unsafe.Pointer) unsafe.Pointer { + stmt := restore[*ast.SwitchStmt](ptr) + return save(stmt.Tag) +} + +//export GetSwitchStmtBody +func GetSwitchStmtBody(ptr unsafe.Pointer) unsafe.Pointer { + stmt := restore[*ast.SwitchStmt](ptr) + return save(stmt.Body) +} + +func restore[T any](ptr unsafe.Pointer) T { + return pointer.Restore(ptr).(T) +} + +func save(val any) unsafe.Pointer { + rv := reflect.ValueOf(val) + if val == nil || (rv.Kind() == reflect.Pointer && rv.IsNil()) { + return nil + } else { + return pointer.Save(val) } +} + +func num[T any, S any](ptr unsafe.Pointer, fieldFunc func(t T) []S) C.int { + t := restore[T](ptr) + + return C.int(len(fieldFunc(t))) +} + +func item[T any, S any](ptr unsafe.Pointer, i int, fieldFunc func(t T) []S) unsafe.Pointer { + t := restore[T](ptr) - return C.jobject((*jnigi.ObjectRef)(tu).JObject()) + return save(fieldFunc(t)[i]) } diff --git a/cpg-language-go/src/main/golang/location.go b/cpg-language-go/src/main/golang/location.go deleted file mode 100644 index c4b82e984e..0000000000 --- a/cpg-language-go/src/main/golang/location.go +++ /dev/null @@ -1,62 +0,0 @@ -// -// Copyright (c) 2021, 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 cpg - -import ( - "go/ast" - "go/token" - "log" - - "tekao.net/jnigi" -) - -type PhysicalLocation jnigi.ObjectRef -type Region jnigi.ObjectRef - -const SarifPackage = CPGPackage + "/sarif" -const RegionClass = SarifPackage + "/Region" -const PhysicalLocationClass = SarifPackage + "/PhysicalLocation" - -func NewRegion(fset *token.FileSet, astNode ast.Node, startLine int, startColumn int, endLine int, endColumn int) *Region { - c, err := env.NewObject(RegionClass, startLine, startColumn, endLine, endColumn) - if err != nil { - log.Fatal(err) - - } - - return (*Region)(c) -} - -func NewPhysicalLocation(fset *token.FileSet, astNode ast.Node, uri *jnigi.ObjectRef, region *Region) *PhysicalLocation { - c, err := env.NewObject(PhysicalLocationClass, (*jnigi.ObjectRef)(uri), (*jnigi.ObjectRef)(region)) - if err != nil { - log.Fatal(err) - - } - - return (*PhysicalLocation)(c) -} diff --git a/cpg-language-go/src/main/golang/node.go b/cpg-language-go/src/main/golang/node.go deleted file mode 100644 index 11f7f260e6..0000000000 --- a/cpg-language-go/src/main/golang/node.go +++ /dev/null @@ -1,141 +0,0 @@ -// -// Copyright (c) 2021, 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 cpg - -import ( - "log" - - "tekao.net/jnigi" -) - -type Node jnigi.ObjectRef -type Name jnigi.ObjectRef - -const CPGPackage = "de/fraunhofer/aisec/cpg" -const GraphPackage = CPGPackage + "/graph" -const NodeClass = GraphPackage + "/Node" -const NameClass = GraphPackage + "/Name" -const NameKtClass = GraphPackage + "/NameKt" - -func (n *Node) Cast(className string) *jnigi.ObjectRef { - return (*jnigi.ObjectRef)(n).Cast(className) -} - -func (n *Node) SetLanguge(l *Language) error { - return (*jnigi.ObjectRef)(n).CallMethod(env, "setLanguage", nil, l) -} - -func (n *Node) SetCode(s string) error { - return (*jnigi.ObjectRef)(n).SetField(env, "code", NewString(s)) -} - -func (n *Node) SetComment(s string) error { - return (*jnigi.ObjectRef)(n).SetField(env, "comment", NewString(s)) -} - -func (n *Node) SetLocation(location *PhysicalLocation) error { - return (*jnigi.ObjectRef)(n).SetField(env, "location", (*jnigi.ObjectRef)(location)) -} - -func (n *Node) SetName(name *Name) { - err := (*jnigi.ObjectRef)(n).CallMethod(env, "setName", nil, name) - if err != nil { - log.Fatal(err) - } -} - -func (n *Node) GetName() (fn *Name) { - var o = jnigi.NewObjectRef(NameClass) - err := (*jnigi.ObjectRef)(n).CallMethod(env, "getName", o) - if err != nil { - log.Fatal(err) - } - - return (*Name)(o) -} - -func (n *Name) ConvertToGo(o *jnigi.ObjectRef) error { - *n = (Name)(*o) - return nil -} - -func (n *Name) ConvertToJava() (obj *jnigi.ObjectRef, err error) { - return (*jnigi.ObjectRef)(n), nil -} - -func (n *Name) GetClassName() string { - return NameClass -} - -func (n *Name) Cast(className string) *jnigi.ObjectRef { - return (*jnigi.ObjectRef)(n).Cast(className) -} - -func (n *Name) IsArray() bool { - return false -} - -func (n *Name) String() string { - return n.ToString() -} - -func (n *Name) ToString() string { - var o = jnigi.NewObjectRef("java/lang/String") - _ = (*jnigi.ObjectRef)(n).CallMethod(env, "toString", o) - - if o == nil { - return "" - } - - var b []byte - err := o.CallMethod(env, "getBytes", &b) - if err != nil { - log.Fatal(err) - } - - return string(b) -} - -func (n *Name) GetLocalName() string { - var o = jnigi.NewObjectRef("java/lang/String") - err := (*jnigi.ObjectRef)(n).CallMethod(env, "getLocalName", o) - if err != nil { - log.Fatal(err) - } - - if o == nil { - return "" - } - - var b []byte - err = o.CallMethod(env, "getBytes", &b) - if err != nil { - log.Fatal(err) - } - - return string(b) -} diff --git a/cpg-language-go/src/main/golang/scope.go b/cpg-language-go/src/main/golang/scope.go deleted file mode 100644 index 4915f127dd..0000000000 --- a/cpg-language-go/src/main/golang/scope.go +++ /dev/null @@ -1,92 +0,0 @@ -// -// Copyright (c) 2021, 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 cpg - -import "tekao.net/jnigi" - -type ScopeManager jnigi.ObjectRef -type Scope jnigi.ObjectRef - -const ScopesPackage = GraphPackage + "/scopes" -const ScopeManagerClass = CPGPackage + "/ScopeManager" -const ScopeClass = ScopesPackage + "/Scope" - -func (s *ScopeManager) EnterScope(n *Node) { - (*jnigi.ObjectRef)(s).CallMethod(env, "enterScope", nil, (*jnigi.ObjectRef)(n).Cast(NodeClass)) -} - -func (s *ScopeManager) LeaveScope(n *Node) (err error) { - var scope = jnigi.NewObjectRef(ScopeClass) - err = (*jnigi.ObjectRef)(s).CallMethod(env, "leaveScope", scope, (*jnigi.ObjectRef)(n).Cast(NodeClass)) - - return err -} - -func (s *ScopeManager) ResetToGlobal(n *Node) { - (*jnigi.ObjectRef)(s).CallMethod(env, "resetToGlobal", nil, (*jnigi.ObjectRef)(n).Cast(TranslationUnitDeclarationClass)) -} - -func (s *ScopeManager) GetCurrentScope() *Scope { - var o = jnigi.NewObjectRef(ScopeClass) - (*jnigi.ObjectRef)(s).CallMethod(env, "getCurrentScope", o) - - return (*Scope)(o) -} - -func (s *ScopeManager) GetCurrentFunction() *FunctionDeclaration { - var o = jnigi.NewObjectRef(FunctionDeclarationClass) - (*jnigi.ObjectRef)(s).CallMethod(env, "getCurrentFunction", o) - - return (*FunctionDeclaration)(o) -} - -func (s *ScopeManager) GetCurrentBlock() *CompoundStatement { - var o = jnigi.NewObjectRef(CompoundStatementClass) - (*jnigi.ObjectRef)(s).CallMethod(env, "getCurrentBlock", o) - - return (*CompoundStatement)(o) -} - -func (s *ScopeManager) GetRecordForName(scope *Scope, recordName *Name) (record *RecordDeclaration, err error) { - var o = jnigi.NewObjectRef(RecordDeclarationClass) - - err = (*jnigi.ObjectRef)(s).CallMethod(env, - "getRecordForName", - o, - (*jnigi.ObjectRef)(scope).Cast(ScopeClass), - recordName) - - record = (*RecordDeclaration)(o) - - return -} - -func (s *ScopeManager) AddDeclaration(d *Declaration) (err error) { - err = (*jnigi.ObjectRef)(s).CallMethod(env, "addDeclaration", nil, (*jnigi.ObjectRef)(d).Cast(DeclarationClass)) - - return -} diff --git a/cpg-language-go/src/main/golang/statements.go b/cpg-language-go/src/main/golang/statements.go deleted file mode 100644 index 85bf95dbb2..0000000000 --- a/cpg-language-go/src/main/golang/statements.go +++ /dev/null @@ -1,118 +0,0 @@ -// -// Copyright (c) 2021, 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 cpg - -import ( - "tekao.net/jnigi" -) - -type Statement Node -type CompoundStatement Statement -type ReturnStatement Statement -type DeclarationStatement Statement -type IfStatement Statement -type SwitchStatement Statement -type CaseStatement Statement -type DefaultStatement Statement -type ForStatement Statement -type ForEachStatement Statement - -const StatementsPackage = GraphPackage + "/statements" -const StatementClass = StatementsPackage + "/Statement" -const CompoundStatementClass = StatementsPackage + "/CompoundStatement" - -func (f *CompoundStatement) AddStatement(s *Statement) { - (*jnigi.ObjectRef)(f).CallMethod(env, "addStatement", nil, (*jnigi.ObjectRef)(s).Cast(StatementClass)) -} - -func (f *DeclarationStatement) SetSingleDeclaration(d *Declaration) { - (*jnigi.ObjectRef)(f).CallMethod(env, "setSingleDeclaration", nil, (*jnigi.ObjectRef)(d).Cast(DeclarationClass)) -} - -func (f *DeclarationStatement) AddToPropertyEdgeDeclaration(d *Declaration) { - (*jnigi.ObjectRef)(f).CallMethod(env, "addToPropertyEdgeDeclaration", nil, (*jnigi.ObjectRef)(d).Cast(DeclarationClass)) -} - -func (m *IfStatement) SetThenStatement(s *Statement) { - (*jnigi.ObjectRef)(m).SetField(env, "thenStatement", (*jnigi.ObjectRef)(s).Cast(StatementClass)) -} - -func (m *IfStatement) SetElseStatement(s *Statement) { - (*jnigi.ObjectRef)(m).SetField(env, "elseStatement", (*jnigi.ObjectRef)(s).Cast(StatementClass)) -} - -func (m *IfStatement) SetCondition(e *Expression) { - (*jnigi.ObjectRef)(m).SetField(env, "condition", (*jnigi.ObjectRef)(e).Cast(ExpressionClass)) -} - -func (i *IfStatement) SetInitializerStatement(s *Statement) { - (*jnigi.ObjectRef)(i).SetField(env, "initializerStatement", (*jnigi.ObjectRef)(s).Cast(StatementClass)) -} - -func (s *SwitchStatement) SetCondition(e *Expression) { - (*jnigi.ObjectRef)(s).SetField(env, "selector", (*jnigi.ObjectRef)(e).Cast(ExpressionClass)) -} - -func (sw *SwitchStatement) SetStatement(s *Statement) { - (*jnigi.ObjectRef)(sw).SetField(env, "statement", (*jnigi.ObjectRef)(s).Cast(StatementClass)) -} - -func (sw *SwitchStatement) SetInitializerStatement(s *Statement) { - (*jnigi.ObjectRef)(sw).SetField(env, "initializerStatement", (*jnigi.ObjectRef)(s).Cast(StatementClass)) -} - -func (fw *ForStatement) SetInitializerStatement(s *Statement) { - (*jnigi.ObjectRef)(fw).SetField(env, "initializerStatement", (*jnigi.ObjectRef)(s).Cast(StatementClass)) -} - -func (fw *ForStatement) SetCondition(e *Expression) { - (*jnigi.ObjectRef)(fw).SetField(env, "condition", (*jnigi.ObjectRef)(e).Cast(ExpressionClass)) -} - -func (fw *ForStatement) SetStatement(s *Statement) { - (*jnigi.ObjectRef)(fw).SetField(env, "statement", (*jnigi.ObjectRef)(s).Cast(StatementClass)) -} - -func (fw *ForEachStatement) SetStatement(s *Statement) { - (*jnigi.ObjectRef)(fw).SetField(env, "statement", (*jnigi.ObjectRef)(s).Cast(StatementClass)) -} - -func (fw *ForEachStatement) SetIterable(s *Statement) { - (*jnigi.ObjectRef)(fw).CallMethod(env, "setIterable", nil, (*jnigi.ObjectRef)(s).Cast(StatementClass)) -} - -func (fw *ForEachStatement) SetVariable(s *Statement) { - (*jnigi.ObjectRef)(fw).SetField(env, "variable", (*jnigi.ObjectRef)(s).Cast(StatementClass)) -} - -func (fw *ForStatement) SetIterationStatement(s *Statement) { - (*jnigi.ObjectRef)(fw).SetField(env, "iterationStatement", (*jnigi.ObjectRef)(s).Cast(StatementClass)) -} - -func (r *ReturnStatement) SetReturnValue(e *Expression) { - (*jnigi.ObjectRef)(r).CallMethod(env, "setReturnValue", nil, (*jnigi.ObjectRef)(e).Cast(ExpressionClass)) -} diff --git a/cpg-language-go/src/main/golang/types.go b/cpg-language-go/src/main/golang/types.go deleted file mode 100644 index f7f037dc67..0000000000 --- a/cpg-language-go/src/main/golang/types.go +++ /dev/null @@ -1,149 +0,0 @@ -// -// Copyright (c) 2021, 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 cpg - -import ( - "C" - "tekao.net/jnigi" -) -import ( - "log" -) - -var env *jnigi.Env - -type Type struct{ *jnigi.ObjectRef } -type ObjectType Type - -type TranslationContext jnigi.ObjectRef - -const TypesPackage = GraphPackage + "/types" -const TypeClass = TypesPackage + "/Type" -const ObjectTypeClass = TypesPackage + "/ObjectType" -const UnknownTypeClass = TypesPackage + "/UnknownType" -const TypeParserClass = TypesPackage + "/TypeParser" -const PointerTypeClass = TypesPackage + "/PointerType" -const FunctionTypeClass = TypesPackage + "/FunctionType" -const PointerOriginClass = PointerTypeClass + "$PointerOrigin" - -func (t *Type) ConvertToGo(o *jnigi.ObjectRef) error { - t.ObjectRef = o - return nil -} - -func (t *Type) ConvertToJava() (obj *jnigi.ObjectRef, err error) { - return t.ObjectRef, nil -} - -func (*Type) GetClassName() string { - return TypeClass -} - -func (*Type) IsArray() bool { - return false -} - -func (*ObjectType) GetClassName() string { - return ObjectTypeClass -} - -type UnknownType struct { - Type -} - -func (*UnknownType) GetClassName() string { - return UnknownTypeClass -} - -type HasType jnigi.ObjectRef - -func InitEnv(e *jnigi.Env) { - env = e -} - -func (t *Type) GetRoot() *Type { - var root Type - err := t.CallMethod(env, "getRoot", &root) - if err != nil { - log.Fatal(err) - } - - return &root -} - -func (h *HasType) SetType(t *Type) { - if t != nil || !t.IsNil() { - err := (*jnigi.ObjectRef)(h).CallMethod(env, "setType", nil, t.Cast(TypeClass)) - if err != nil { - panic(err) - } - } - -} - -func (h *HasType) GetType() *Type { - var t Type - err := (*jnigi.ObjectRef)(h).CallMethod(env, "getType", &t) - if err != nil { - log.Fatal(err) - } - - return &t -} - -func (t *Type) Reference(o *jnigi.ObjectRef) *Type { - var refType Type - err := t.CallMethod(env, "reference", &refType, (*jnigi.ObjectRef)(o).Cast(PointerOriginClass)) - - if err != nil { - log.Fatal(err) - } - - return &refType -} - -func (t *Type) GetName() (fn *Name) { - return (*Node)(t.ObjectRef).GetName() -} - -func (t *ObjectType) AddGeneric(g *Type) { - err := t.Cast(ObjectTypeClass).CallMethod(env, "addGeneric", nil, g.Cast(TypeClass)) - if err != nil { - log.Fatal(err) - } -} - -func FunctionType_ComputeType(decl *FunctionDeclaration) (t *Type, err error) { - var funcType Type - - err = env.CallStaticMethod(FunctionTypeClass, "computeType", &t, decl) - if err != nil { - return nil, err - } - - return &funcType, nil -} diff --git a/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/DeclarationHandler.kt b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/DeclarationHandler.kt new file mode 100644 index 0000000000..3d102dc966 --- /dev/null +++ b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/DeclarationHandler.kt @@ -0,0 +1,214 @@ +/* + * Copyright (c) 2023, 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.frontends.golang + +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.CompoundStatement +import de.fraunhofer.aisec.cpg.graph.statements.ReturnStatement +import de.fraunhofer.aisec.cpg.graph.types.Type +import de.fraunhofer.aisec.cpg.graph.types.UnknownType + +class DeclarationHandler(frontend: GoLanguageFrontend) : + Handler( + ::ProblemDeclaration, + frontend + ) { + + init { + map[GoStandardLibrary.Ast.FuncDecl::class.java] = HandlerInterface { + handleFuncDecl(it as GoStandardLibrary.Ast.FuncDecl) + } + map[GoStandardLibrary.Ast.GenDecl::class.java] = HandlerInterface { + handleGenDecl(it as GoStandardLibrary.Ast.GenDecl) + } + map.put(GoStandardLibrary.Ast.Decl::class.java, ::handleNode) + } + + private fun handleNode(decl: GoStandardLibrary.Ast.Decl): Declaration { + val message = "Not parsing declaration of type ${decl.goType} yet" + log.error(message) + + return newProblemDeclaration(message) + } + + private fun handleFuncDecl(funcDecl: GoStandardLibrary.Ast.FuncDecl): FunctionDeclaration { + val recv = funcDecl.recv + val func = + if (recv != null) { + val method = newMethodDeclaration(funcDecl.name.name, rawNode = funcDecl) + val recvField = recv.list.firstOrNull() + val recordType = recvField?.type?.let { frontend.typeOf(it) } ?: unknownType() + + // The name of the Go receiver is optional. In fact, if the name is not + // specified we probably do not need any receiver variable at all, + // because the syntax is only there to ensure that this method is part + // of the struct, but it is not modifying the receiver. + if (recvField?.names?.isNotEmpty() == true) { + method.receiver = + newVariableDeclaration( + recvField.names[0].name, + recordType, + rawNode = recvField + ) + } + + if (recordType !is UnknownType) { + val recordName = recordType.name + + // TODO: this will only find methods within the current translation unit. + // this is a limitation that we have for C++ as well + val record = + frontend.scopeManager.currentScope?.let { + frontend.scopeManager.getRecordForName(it, recordName) + } + + // now this gets a little bit hacky, we will add it to the record declaration + // this is strictly speaking not 100 % true, since the method property edge is + // marked as AST and in Go a method is not part of the struct's AST but is + // declared outside. In the future, we need to differentiate between just the + // associated members of the class and the pure AST nodes declared in the + // struct + // itself + record?.addMethod(method) + } + method + } else { + var localNameOnly = false + + if (funcDecl.name.name == "") { + localNameOnly = true + } + + newFunctionDeclaration(funcDecl.name.name, null, funcDecl, localNameOnly) + } + + frontend.scopeManager.enterScope(func) + + if (func is MethodDeclaration && func.receiver != null) { + // Add the receiver do the scope manager, so we can resolve the receiver value + frontend.scopeManager.addDeclaration(func.receiver) + } + + val returnTypes = mutableListOf() + + // Build return types (and variables) + val results = funcDecl.type.results + if (results != null) { + for (returnVar in results.list) { + returnTypes += frontend.typeOf(returnVar.type) + + // If the function has named return variables, be sure to declare them as well + if (returnVar.names.isNotEmpty()) { + val param = + newVariableDeclaration( + returnVar.names[0].name, + frontend.typeOf(returnVar.type), + rawNode = returnVar + ) + + // Add parameter to scope + frontend.scopeManager.addDeclaration(param) + } + } + } + + func.type = frontend.typeOf(funcDecl.type) + func.returnTypes = returnTypes + + // Parse parameters + for (param in funcDecl.type.params.list) { + var name = "" + + // Somehow parameters end up having no name sometimes, have not fully understood why. + if (param.names.isNotEmpty()) { + name = param.names[0].name + + // If the name is an underscore, it means that the parameter is + // unnamed. In order to avoid confusing and some compatibility with + // other languages, we are just setting the name to an empty string + // in this case. + if (name == "_") { + name = "" + } + } else { + log.warn("Some param has no name, which is a bit weird: $param") + } + + // Check for varargs. In this case we want to parse the element type + // (and make it an array afterwards) + var variadic = false + val type = + if (param.type is GoStandardLibrary.Ast.Ellipsis) { + variadic = true + frontend.typeOf((param.type as GoStandardLibrary.Ast.Ellipsis).elt).array() + } else { + frontend.typeOf(param.type) + } + + val p = newParamVariableDeclaration(name, type, variadic, rawNode = param) + + frontend.scopeManager.addDeclaration(p) + + frontend.setComment(p, param) + } + + // Check, if the last statement is a return statement, otherwise we insert an implicit one + val body = frontend.statementHandler.handle(funcDecl.body) + if (body is CompoundStatement) { + val last = body.statements.lastOrNull() + if (last !is ReturnStatement) { + val ret = newReturnStatement() + ret.isImplicit = true + body += ret + } + } + + func.body = body + + frontend.scopeManager.leaveScope(func) + + return func + } + + private fun handleGenDecl(genDecl: GoStandardLibrary.Ast.GenDecl): DeclarationSequence { + val sequence = DeclarationSequence() + + for (spec in genDecl.specs) { + frontend.specificationHandler.handle(spec)?.let { + sequence += it + + // Go associates the comment to the genDecl, so we need to explicitly launch + // setComment here. + frontend.setComment(it, genDecl) + } + } + + return sequence + } +} diff --git a/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/ExpressionHandler.kt b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/ExpressionHandler.kt new file mode 100644 index 0000000000..996360809d --- /dev/null +++ b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/ExpressionHandler.kt @@ -0,0 +1,380 @@ +/* + * Copyright (c) 2023, 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.frontends.golang + +import de.fraunhofer.aisec.cpg.frontends.golang.GoStandardLibrary.Ast.BasicLit.Kind.* +import de.fraunhofer.aisec.cpg.graph.* +import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration +import de.fraunhofer.aisec.cpg.graph.statements.expressions.* +import de.fraunhofer.aisec.cpg.graph.types.PointerType +import de.fraunhofer.aisec.cpg.graph.types.Type + +class ExpressionHandler(frontend: GoLanguageFrontend) : + GoHandler(::ProblemExpression, frontend) { + + override fun handleNode(expr: GoStandardLibrary.Ast.Expr): Expression { + return when (expr) { + is GoStandardLibrary.Ast.BasicLit -> handleBasicLit(expr) + is GoStandardLibrary.Ast.BinaryExpr -> handleBinaryExpr(expr) + is GoStandardLibrary.Ast.CompositeLit -> handleCompositeLit(expr) + is GoStandardLibrary.Ast.FuncLit -> handleFuncLit(expr) + is GoStandardLibrary.Ast.Ident -> handleIdent(expr) + is GoStandardLibrary.Ast.IndexExpr -> handleIndexExpr(expr) + is GoStandardLibrary.Ast.CallExpr -> handleCallExpr(expr) + is GoStandardLibrary.Ast.KeyValueExpr -> handleKeyValueExpr(expr) + is GoStandardLibrary.Ast.SelectorExpr -> handleSelectorExpr(expr) + is GoStandardLibrary.Ast.SliceExpr -> handleSliceExpr(expr) + is GoStandardLibrary.Ast.TypeAssertExpr -> handleTypeAssertExpr(expr) + is GoStandardLibrary.Ast.UnaryExpr -> handleUnaryExpr(expr) + else -> { + return handleNotSupported(expr, expr.goType) + } + } + } + + private fun handleBasicLit(basicLit: GoStandardLibrary.Ast.BasicLit): Literal<*> { + val rawValue = basicLit.value + val value: Any? + val type: Type + when (basicLit.kind) { + STRING -> { + value = + rawValue.substring( + 1.coerceAtMost(rawValue.length - 1), + (rawValue.length - 1).coerceAtLeast(0) + ) + type = primitiveType("string") + } + INT -> { + value = rawValue.toInt() + type = primitiveType("int") + } + FLOAT -> { + value = rawValue.toDouble() + type = primitiveType("float64") + } + CHAR -> { + value = rawValue.firstOrNull() + type = primitiveType("rune") + } + else -> { + value = rawValue + type = unknownType() + } + } + + val lit = newLiteral(value, rawNode = basicLit) + lit.type = type + + return lit + } + + private fun handleBinaryExpr(binaryExpr: GoStandardLibrary.Ast.BinaryExpr): BinaryOperator { + val binOp = newBinaryOperator(binaryExpr.opString, rawNode = binaryExpr) + binOp.lhs = handle(binaryExpr.x) ?: newProblemExpression("missing LHS") + binOp.rhs = handle(binaryExpr.y) ?: newProblemExpression("missing RHS") + + return binOp + } + + private fun handleIdent(ident: GoStandardLibrary.Ast.Ident): Expression { + // Check, if this is 'nil', because then we handle it as a literal in the graph + if (ident.name == "nil") { + val literal = newLiteral(null, rawNode = ident) + literal.name = parseName(ident.name) + + return literal + } + + val ref = newDeclaredReferenceExpression(ident.name, rawNode = ident) + + // Check, if this refers to a package import + val import = frontend.currentTU?.getIncludeByName(ident.name) + // Then set the refersTo, because our regular CPG passes will not resolve them + if (import != null) { + ref.refersTo = import + } + + return ref + } + + private fun handleIndexExpr( + indexExpr: GoStandardLibrary.Ast.IndexExpr + ): ArraySubscriptionExpression { + val ase = newArraySubscriptionExpression(rawNode = indexExpr) + ase.arrayExpression = frontend.expressionHandler.handle(indexExpr.x) + ase.subscriptExpression = frontend.expressionHandler.handle(indexExpr.index) + + return ase + } + + private fun handleCallExpr(callExpr: GoStandardLibrary.Ast.CallExpr): Expression { + // In Go, regular cast expressions (not type asserts are modelled as calls). + // In this case, the Fun contains a type expression. + when (callExpr.`fun`) { + is GoStandardLibrary.Ast.ArrayType, + is GoStandardLibrary.Ast.ChanType, + is GoStandardLibrary.Ast.FuncType, + is GoStandardLibrary.Ast.InterfaceType, + is GoStandardLibrary.Ast.StructType, + is GoStandardLibrary.Ast.MapType, -> { + val cast = newCastExpression(rawNode = callExpr) + cast.castType = frontend.typeOf(callExpr.`fun`) + + if (callExpr.args.isNotEmpty()) { + frontend.expressionHandler.handle(callExpr.args[0])?.let { + cast.expression = it + } + } + + return cast + } + } + + // Parse the Fun field, to see which kind of expression it is + val callee = + this.handle(callExpr.`fun`) + ?: return ProblemExpression("Could not parse call expr without fun") + + // Handle special functions, such as make and new in a special way + val name = callee.name.localName + if (name == "new") { + return handleNewExpr(callExpr) + } else if (name == "make") { + return handleMakeExpr(callExpr) + } + + // Differentiate between calls and member calls based on the callee + val call = + if (callee is MemberExpression) { + newMemberCallExpression(callee, rawNode = callExpr) + } else { + newCallExpression(callee, name, rawNode = callExpr) + } + + // Parse and add call arguments + for (arg in callExpr.args) { + handle(arg)?.let { call += it } + } + + return call + } + + private fun handleKeyValueExpr( + keyValueExpr: GoStandardLibrary.Ast.KeyValueExpr + ): KeyValueExpression { + val key = handle(keyValueExpr.key) ?: newProblemExpression("could not parse key") + val value = handle(keyValueExpr.value) ?: newProblemExpression("could not parse value") + + return newKeyValueExpression(key, value, rawNode = keyValueExpr) + } + + private fun handleNewExpr(callExpr: GoStandardLibrary.Ast.CallExpr): Expression { + if (callExpr.args.isEmpty()) { + return newProblemExpression("could not create NewExpression with empty arguments") + } + + val n = newNewExpression(rawNode = callExpr) + + // First argument is type + val type = frontend.typeOf(callExpr.args[0]) + + // new is a pointer, so need to reference the type with a pointer + n.type = type.reference(PointerType.PointerOrigin.POINTER) + + // a new expression also needs an initializer, which is usually a ConstructExpression + val construct = newConstructExpression(rawNode = callExpr) + construct.type = type + + n.initializer = construct + + return n + } + + private fun handleMakeExpr(callExpr: GoStandardLibrary.Ast.CallExpr): Expression { + val args = callExpr.args + + if (args.isEmpty()) { + return newProblemExpression("too few arguments for make expression") + } + + val expression = + // Actually make() can make more than just arrays, i.e. channels and maps + if (args[0] is GoStandardLibrary.Ast.ArrayType) { + val array = newArrayCreationExpression(rawNode = callExpr) + + // second argument is a dimension (if this is an array), usually a literal + if (args.size > 1) { + handle(args[1])?.let { array.addDimension(it) } + } + array + } else { + // Create at least a generic construct expression for the given map or channel type + // and provide the remaining arguments + val construct = newConstructExpression(rawNode = callExpr) + + // Pass the remaining arguments + for (expr in args.subList(1.coerceAtMost(args.size - 1), args.size - 1)) { + handle(expr)?.let { construct += it } + } + + construct + } + + // First argument is always the type + expression.type = frontend.typeOf(callExpr.args[0]) + + return expression + } + + private fun handleSelectorExpr( + selectorExpr: GoStandardLibrary.Ast.SelectorExpr + ): DeclaredReferenceExpression { + val base = handle(selectorExpr.x) ?: newProblemExpression("missing base") + + // Check, if this just a regular reference to a variable with a package scope and not a + // member expression + var isMemberExpression = true + for (imp in frontend.currentFile?.imports ?: listOf()) { + if (base.name.localName == frontend.getImportName(imp)) { + // found a package name, so this is NOT a member expression + isMemberExpression = false + } + } + + val ref = + if (isMemberExpression) { + newMemberExpression(selectorExpr.sel.name, base, rawNode = selectorExpr) + } else { + // we need to set the name to a FQN-style, including the package scope. the call + // resolver will then resolve this + val fqn = "${base.name}.${selectorExpr.sel.name}" + + newDeclaredReferenceExpression(fqn, rawNode = selectorExpr) + } + + return ref + } + + /** + * This function handles a ast.SliceExpr, which is an extended version of ast.IndexExpr. We are + * modelling this as a combination of a [ArraySubscriptionExpression] that contains a + * [RangeExpression] as its subscriptExpression to share some code between this and an index + * expression. + */ + private fun handleSliceExpr( + sliceExpr: GoStandardLibrary.Ast.SliceExpr + ): ArraySubscriptionExpression { + val ase = newArraySubscriptionExpression(rawNode = sliceExpr) + ase.arrayExpression = + frontend.expressionHandler.handle(sliceExpr.x) + ?: newProblemExpression("missing array expression") + + // Build the slice expression + val range = newRangeExpression(rawNode = sliceExpr) + sliceExpr.low?.let { range.floor = frontend.expressionHandler.handle(it) } + sliceExpr.high?.let { range.ceiling = frontend.expressionHandler.handle(it) } + sliceExpr.max?.let { range.third = frontend.expressionHandler.handle(it) } + + ase.subscriptExpression = range + + return ase + } + + private fun handleTypeAssertExpr( + typeAssertExpr: GoStandardLibrary.Ast.TypeAssertExpr + ): CastExpression { + val cast = newCastExpression(rawNode = typeAssertExpr) + + // Parse the inner expression + cast.expression = + handle(typeAssertExpr.x) ?: newProblemExpression("missing inner expression") + + // The type can be null, but only in certain circumstances, i.e, a type switch (which we do + // not support yet) + typeAssertExpr.type?.let { cast.castType = frontend.typeOf(it) } + + return cast + } + + private fun handleUnaryExpr(unaryExpr: GoStandardLibrary.Ast.UnaryExpr): UnaryOperator { + val op = + newUnaryOperator( + unaryExpr.opString, + postfix = false, + prefix = false, + rawNode = unaryExpr + ) + handle(unaryExpr.x)?.let { op.input = it } + + return op + } + + /** + * handleCompositeLit handles a composite literal, which we need to translate into a combination + * of a ConstructExpression and a list of KeyValueExpressions. The problem is that we need to + * add the list as a first argument of the construct expression. + */ + private fun handleCompositeLit( + compositeLit: GoStandardLibrary.Ast.CompositeLit + ): ConstructExpression { + // Parse the type field, to see which kind of expression it is + val type = frontend.typeOf(compositeLit.type) + + val construct = newConstructExpression(type.name, rawNode = compositeLit) + construct.type = type + + val list = newInitializerListExpression(rawNode = compositeLit) + construct += list + + // Normally, the construct expression would not have DFG edge, but in this case we are + // mis-using it to simulate an object literal, so we need to add a DFG here, otherwise a + // declaration is disconnected from its initialization. + construct.addPrevDFG(list) + + val expressions = mutableListOf() + for (elem in compositeLit.elts) { + handle(elem)?.let { expressions += it } + } + + list.initializers = expressions + + return construct + } + + /* + // handleFuncLit handles a function literal, which we need to translate into a combination of a + // LambdaExpression and a function declaration. + */ + fun handleFuncLit(funcLit: GoStandardLibrary.Ast.FuncLit): LambdaExpression { + val lambda = newLambdaExpression(rawNode = funcLit) + // Parse the expression as a function declaration with a little trick + lambda.function = + frontend.declarationHandler.handle(funcLit.toDecl()) as? FunctionDeclaration + + return lambda + } +} diff --git a/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoHandler.kt b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoHandler.kt new file mode 100644 index 0000000000..cfac505f13 --- /dev/null +++ b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoHandler.kt @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2023, 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.frontends.golang + +import de.fraunhofer.aisec.cpg.frontends.Handler +import de.fraunhofer.aisec.cpg.graph.Node +import de.fraunhofer.aisec.cpg.graph.ProblemNode +import de.fraunhofer.aisec.cpg.helpers.Util +import java.util.function.Supplier + +abstract class GoHandler( + configConstructor: Supplier, + lang: GoLanguageFrontend +) : Handler(configConstructor, lang) { + /** + * We intentionally override the logic of [Handler.handle] because we do not want the map-based + * logic, but rather want to make use of the Kotlin-when syntax. + * + * We also want non-nullable result handlers + */ + override fun handle(ctx: HandlerNode): ResultNode { + val node = handleNode(ctx) + + // The language frontend might set a location, which we should respect. Otherwise, we will + // set the location here. + if (node.location == null) { + frontend.setCodeAndLocation(node, ctx) + } + + frontend.setComment(node, ctx) + frontend.process(ctx, node) + + return node + } + + abstract fun handleNode(node: HandlerNode): ResultNode + + /** + * This function should be called by classes that derive from [GoHandler] to denote, that the + * supplied node (type) is not supported. + */ + protected fun handleNotSupported(node: HandlerNode, name: String): ResultNode { + Util.errorWithFileLocation( + frontend, + node, + log, + "Parsing of type $name is not supported (yet)" + ) + + val cpgNode = this.configConstructor.get() + if (cpgNode is ProblemNode) { + cpgNode.problem = "Parsing of type $name is not supported (yet)" + } + + return cpgNode + } +} 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 ae69dd649f..33fc7bb06c 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 @@ -30,89 +30,252 @@ import de.fraunhofer.aisec.cpg.frontends.Language import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend import de.fraunhofer.aisec.cpg.frontends.SupportsParallelParsing import de.fraunhofer.aisec.cpg.frontends.TranslationException -import de.fraunhofer.aisec.cpg.graph.Node +import de.fraunhofer.aisec.cpg.frontends.golang.GoStandardLibrary.Modfile +import de.fraunhofer.aisec.cpg.frontends.golang.GoStandardLibrary.Parser +import de.fraunhofer.aisec.cpg.graph.* +import de.fraunhofer.aisec.cpg.graph.declarations.DeclarationSequence import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration +import de.fraunhofer.aisec.cpg.graph.newNamespaceDeclaration +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Literal +import de.fraunhofer.aisec.cpg.graph.types.FunctionType import de.fraunhofer.aisec.cpg.graph.types.Type import de.fraunhofer.aisec.cpg.graph.unknownType +import de.fraunhofer.aisec.cpg.passes.EvaluationOrderGraphPass +import de.fraunhofer.aisec.cpg.passes.GoEvaluationOrderGraphPass import de.fraunhofer.aisec.cpg.passes.GoExtraPass import de.fraunhofer.aisec.cpg.passes.order.RegisterExtraPass +import de.fraunhofer.aisec.cpg.passes.order.ReplacePass import de.fraunhofer.aisec.cpg.sarif.PhysicalLocation +import de.fraunhofer.aisec.cpg.sarif.Region import java.io.File -import java.io.FileOutputStream +import java.net.URI +/** + * A language frontend for the [GoLanguage]. It makes use the internal + * [go/ast](https://pkg.go.dev/go/ast) package of the Go runtime to parse the AST of a Go program. + * We make use of JNA to call a dynamic library which exports C function wrappers around the Go API. + * This is needed because we cannot directly export Go structs and pointers to C. + */ @SupportsParallelParsing(false) @RegisterExtraPass(GoExtraPass::class) +@ReplacePass( + lang = GoLanguage::class, + old = EvaluationOrderGraphPass::class, + with = GoEvaluationOrderGraphPass::class +) class GoLanguageFrontend(language: Language, ctx: TranslationContext) : - LanguageFrontend(language, ctx) { - companion object { - - init { - try { - val arch = - System.getProperty("os.arch") - .replace("aarch64", "arm64") - .replace("x86_64", "amd64") - val ext: String = - if (System.getProperty("os.name").startsWith("Mac")) { - ".dylib" - } else { - ".so" - } + LanguageFrontend(language, ctx) { + + private var currentFileSet: GoStandardLibrary.Ast.FileSet? = null + private var currentModule: GoStandardLibrary.Modfile.File? = null + private var commentMap: GoStandardLibrary.Ast.CommentMap? = null + var currentFile: GoStandardLibrary.Ast.File? = null + + val declarationHandler = DeclarationHandler(this) + val specificationHandler = SpecificationHandler(this) + var statementHandler = StatementHandler(this) + var expressionHandler = ExpressionHandler(this) + + @Throws(TranslationException::class) + override fun parse(file: File): TranslationUnitDeclaration { + // Make sure, that our top level is set either way + val topLevel = + if (config.topLevel != null) { + config.topLevel + } else { + file.parentFile + }!! + + val std = GoStandardLibrary.INSTANCE + + // Try to parse a possible go.mod + val goModFile = topLevel.resolve("go.mod") + if (goModFile.exists()) { + currentModule = Modfile.parse(goModFile.absolutePath, goModFile.readText()) + } + + val fset = std.NewFileSet() + val f = Parser.parseFile(fset, file.absolutePath, file.readText()) + + this.commentMap = std.NewCommentMap(fset, f, f.comments) - val stream = - GoLanguageFrontend::class.java.getResourceAsStream("/libcpgo-$arch$ext") + currentFile = f + currentFileSet = fset - val tmp = File.createTempFile("libcpgo", ext) - tmp.deleteOnExit() - val fos = FileOutputStream(tmp) - stream.copyTo(FileOutputStream(tmp)) + val tu = newTranslationUnitDeclaration(file.absolutePath, rawNode = f) + scopeManager.resetToGlobal(tu) + currentTU = tu - fos.close() - stream.close() + for (spec in f.imports) { + val import = specificationHandler.handle(spec) + scopeManager.addDeclaration(import) + } + + val p = newNamespaceDeclaration(f.name.name) + scopeManager.enterScope(p) - log.info("Loading cpgo library from ${tmp.absoluteFile}") + try { + // we need to construct the package "path" (e.g. "encoding/json") out of the + // module path as well as the current directory in relation to the topLevel + var packagePath = file.parentFile.relativeTo(topLevel) - System.load(tmp.absolutePath) - } catch (ex: Exception) { - log.error( - "Error while loading cpgo library. Go frontend will not work correctly", - ex - ) + // If we are in a module, we need to prepend the module path to it + currentModule?.let { packagePath = File(it.module.mod.path).resolve(packagePath) } + + p.path = packagePath.path + } catch (ex: IllegalArgumentException) { + log.error( + "Could not relativize package path to top level. Cannot set package path.", + ex + ) + } + + for (decl in f.decls) { + // Retrieve all top level declarations. One "Decl" could potentially + // contain multiple CPG declarations. + val declaration = declarationHandler.handle(decl) + if (declaration is DeclarationSequence) { + declaration.declarations.forEach { scopeManager.addDeclaration(it) } + } else { + scopeManager.addDeclaration(declaration) } } + + scopeManager.leaveScope(p) + + scopeManager.addDeclaration(p) + + return tu } - @Throws(TranslationException::class) - override fun parse(file: File): TranslationUnitDeclaration { - return parseInternal( - file.readText(Charsets.UTF_8), - file.absolutePath, - config.topLevel?.absolutePath ?: file.parent - ) + override fun typeOf(type: GoStandardLibrary.Ast.Expr): Type { + return when (type) { + is GoStandardLibrary.Ast.Ident -> { + val name: String = + if (isBuiltinType(type.name)) { + // Definitely not an FQN type + type.name + } else { + // FQN'ize this name (with the current file) + "${currentFile?.name?.name}.${type.name}" // this.File.Name.Name + } + + objectType(name) + } + is GoStandardLibrary.Ast.ArrayType -> { + return typeOf(type.elt).array() + } + is GoStandardLibrary.Ast.ChanType -> { + // Handle them similar to a map type (see below) + return objectType("chan", listOf(typeOf(type.value))) + } + is GoStandardLibrary.Ast.FuncType -> { + val paramTypes = type.params.list.map { typeOf(it.type) } + val returnTypes = type.results?.list?.map { typeOf(it.type) } ?: listOf() + val name = funcTypeName(paramTypes, returnTypes) + + return FunctionType(name, paramTypes, returnTypes, this.language) + } + is GoStandardLibrary.Ast.MapType -> { + // We cannot properly represent Go's built-in map types, yet so we have + // to make a shortcut here and represent it as a Java-like map type. + return objectType("map", listOf(typeOf(type.key), typeOf(type.value))) + } + is GoStandardLibrary.Ast.StarExpr -> { + typeOf(type.x).pointer() + } + else -> { + log.warn("Not parsing type of type ${type.goType} yet") + unknownType() + } + } } - override fun typeOf(type: Any): Type { - // this is handled by native code - return unknownType() + private fun isBuiltinType(name: String): Boolean { + return when (name) { + "bool", + "byte", + "complex128", + "complex64", + "error", + "float32", + "float64", + "int", + "int8", + "int16", + "int32", + "int64", + "rune", + "string", + "uint", + "uint8", + "uint16", + "uint32", + "uint64", + "uintptr" -> true + else -> false + } } - override fun codeOf(astNode: Any): String? { - // this is handled by native code - return null + override fun codeOf(astNode: GoStandardLibrary.Ast.Node): String? { + return currentFileSet?.code(astNode) } - override fun locationOf(astNode: Any): PhysicalLocation? { - // this is handled by native code - return null + override fun locationOf(astNode: GoStandardLibrary.Ast.Node): PhysicalLocation? { + val start = currentFileSet?.position(astNode.pos) ?: return null + val end = currentFileSet?.position(astNode.end) ?: return null + val url = currentFileSet?.fileName(astNode.pos)?.let { URI(it) } ?: return null + + return PhysicalLocation(url, Region(start.line, start.column, end.line, end.column)) } - override fun setComment(node: Node, astNode: Any) { - // this is handled by native code + override fun setComment(node: Node, astNode: GoStandardLibrary.Ast.Node) { + // Since we are potentially calling this function more than once on a node because of the + // way go is structured (one decl can contain multiple specs), we need to make sure, that we + // are not "overriding" more specific comments with more global ones. + if (node.comment == null) { + val comment = this.commentMap?.comment(astNode) + node.comment = comment + } } - private external fun parseInternal( - s: String?, - path: String, - topLevel: String - ): TranslationUnitDeclaration + /** + * This function produces a Go-style function type name such as `func(int, string) string` or + * `func(int) (error, string)` + */ + private fun funcTypeName(paramTypes: List, returnTypes: List): String { + val rn = mutableListOf() + val pn = mutableListOf() + + for (t in paramTypes) { + pn += t.name.toString() + } + + for (t in returnTypes) { + rn += t.name.toString() + } + + val rs = + if (returnTypes.size > 1) { + rn.joinToString(", ", prefix = " (", postfix = ")") + } else if (returnTypes.isNotEmpty()) { + rn.joinToString(", ", prefix = " ") + } else { + "" + } + + return pn.joinToString(", ", prefix = "func(", postfix = ")$rs") + } + + fun getImportName(spec: GoStandardLibrary.Ast.ImportSpec): String { + val name = spec.name + if (name != null) { + return name.name + } + + val path = expressionHandler.handle(spec.path) as? Literal<*> + val paths = (path?.value as? String)?.split("/") ?: listOf() + + return paths.lastOrNull() ?: "" + } } diff --git a/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoStandardLibrary.kt b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoStandardLibrary.kt new file mode 100644 index 0000000000..f7ca20b70b --- /dev/null +++ b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoStandardLibrary.kt @@ -0,0 +1,1060 @@ +/* + * Copyright (c) 2023, 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.frontends.golang + +import com.sun.jna.* +import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend +import de.fraunhofer.aisec.cpg.frontends.TranslationException +import java.io.FileOutputStream + +/** + * This interface represents parts of the Go standard library used by C wrapper functions and JNA. + */ +interface GoStandardLibrary : Library { + open class GoObject(p: Pointer? = Pointer.NULL) : PointerType(p) { + val goType: String + get() { + return INSTANCE.GetType(this.pointer) + } + } + + /** + * This class represents the Go go/token package and contains classes representing structs in + * this package. + */ + class Token {} + + /** + * This class represents the Go go/parser package and contains classes representing structs in + * this package. + */ + object Parser { + fun parseFile(fileSet: Ast.FileSet, path: String, contents: String): Ast.File { + return INSTANCE.goParserParseFile(fileSet, path, contents) + } + } + + object Modfile { + class File(p: Pointer? = Pointer.NULL) : GoObject(p) { + val module: Module + get() { + return INSTANCE.modfileGetFileModule(this) + } + } + + class Module(p: Pointer? = Pointer.NULL) : GoObject(p) { + val mod: GoStandardLibrary.Module.Version + get() { + return INSTANCE.modfileGetModuleMod(this) + } + } + + fun parse(file: String, bytes: String): File { + return INSTANCE.modfileParse(file, bytes) + } + } + + object Module { + + class Version(p: Pointer? = Pointer.NULL) : GoObject(p) { + val path: String + get() { + return INSTANCE.moduleGetVersionPath(this) + } + } + } + + fun modfileParse(file: String, bytes: String): Modfile.File + + fun modfileGetFileModule(file: Modfile.File): Modfile.Module + + fun modfileGetModuleMod(file: Modfile.Module): Module.Version + + fun moduleGetVersionPath(version: Module.Version): String + + /** + * This class represents the Go go/ast package and contains classes representing structs in this + * package. + */ + interface Ast { + open class Node(p: Pointer? = Pointer.NULL) : GoObject(p) { + val pos: Int + get() { + return INSTANCE.GetNodePos(this) + } + + val end: Int + get() { + return INSTANCE.GetNodeEnd(this) + } + } + + class FieldList(p: Pointer? = Pointer.NULL) : Node(p) { + val list: List + get() { + return list(INSTANCE::GetNumFieldListList, INSTANCE::GetFieldListList) + } + } + + class Field(p: Pointer? = Pointer.NULL) : Node(p) { + val names: List by lazy { + list(INSTANCE::GetNumFieldNames, INSTANCE::GetFieldName) + } + + val type: Expr by lazy { INSTANCE.GetFieldType(this) } + } + + open class Decl(p: Pointer? = Pointer.NULL) : Node(p) { + + override fun fromNative(nativeValue: Any?, context: FromNativeContext?): Any { + if (nativeValue !is Pointer) { + return super.fromNative(nativeValue, context) + } + + return when (INSTANCE.GetType(nativeValue)) { + "*ast.GenDecl" -> { + GenDecl(nativeValue) + } + "*ast.FuncDecl" -> { + FuncDecl(nativeValue) + } + else -> { + super.fromNative(nativeValue, context) + } + } + } + } + + class GenDecl(p: Pointer? = Pointer.NULL) : Decl(p) { + val specs: List + get() { + return list(INSTANCE::GetNumGenDeclSpecs, INSTANCE::GetGenDeclSpec) + } + } + + class FuncDecl(p: Pointer? = Pointer.NULL) : Decl(p) { + val recv: FieldList? + get() { + return INSTANCE.GetFuncDeclRecv(this) + } + + val type: FuncType by lazy { INSTANCE.GetFuncDeclType(this) } + + val name: Ident + get() { + return INSTANCE.GetFuncDeclName(this) + } + + val body: BlockStmt + get() { + return INSTANCE.GetFuncDeclBody(this) + } + } + + open class Spec(p: Pointer? = Pointer.NULL) : Node(p) { + override fun fromNative(nativeValue: Any?, context: FromNativeContext?): Any { + if (nativeValue !is Pointer) { + return super.fromNative(nativeValue, context) + } + + return when (INSTANCE.GetType(nativeValue)) { + "*ast.ImportSpec" -> ImportSpec(nativeValue) + "*ast.TypeSpec" -> TypeSpec(nativeValue) + "*ast.ValueSpec" -> ValueSpec(nativeValue) + else -> super.fromNative(nativeValue, context) + } + } + } + + class TypeSpec(p: Pointer? = Pointer.NULL) : Spec(p) { + val name: Ident + get() { + return INSTANCE.GetTypeSpecName(this) + } + + val type: Expr + get() { + return INSTANCE.GetTypeSpecType(this) + } + } + + class ImportSpec(p: Pointer? = Pointer.NULL) : Spec(p) { + val name: Ident? + get() { + return INSTANCE.GetImportSpecName(this) + } + + val path: BasicLit + get() { + return INSTANCE.GetImportSpecPath(this) + } + } + + class ValueSpec(p: Pointer? = Pointer.NULL) : Spec(p) { + val names: List by lazy { + list(INSTANCE::GetNumValueSpecNames, INSTANCE::GetValueSpecName) + } + + val type: Expr? + get() { + return INSTANCE.GetValueSpecType(this) + } + + val values: List by lazy { + list(INSTANCE::GetNumValueSpecValues, INSTANCE::GetValueSpecValue) + } + } + + open class Expr(p: Pointer? = Pointer.NULL) : Node(p) { + override fun fromNative(nativeValue: Any?, context: FromNativeContext?): Any? { + if (nativeValue !is Pointer) { + return super.fromNative(nativeValue, context) + } + + return when (INSTANCE.GetType(nativeValue)) { + "*ast.BasicLit" -> BasicLit(nativeValue) + "*ast.BinaryExpr" -> BinaryExpr(nativeValue) + "*ast.CallExpr" -> CallExpr(nativeValue) + "*ast.CompositeLit" -> CompositeLit(nativeValue) + "*ast.Ellipsis" -> Ellipsis(nativeValue) + "*ast.FuncLit" -> FuncLit(nativeValue) + "*ast.Ident" -> Ident(nativeValue) + "*ast.IndexExpr" -> IndexExpr(nativeValue) + "*ast.KeyValueExpr" -> KeyValueExpr(nativeValue) + "*ast.SelectorExpr" -> SelectorExpr(nativeValue) + "*ast.StarExpr" -> StarExpr(nativeValue) + "*ast.SliceExpr" -> SliceExpr(nativeValue) + "*ast.TypeAssertExpr" -> TypeAssertExpr(nativeValue) + "*ast.UnaryExpr" -> UnaryExpr(nativeValue) + "*ast.ArrayType" -> ArrayType(nativeValue) + "*ast.ChanType" -> ChanType(nativeValue) + "*ast.FuncType" -> FuncType(nativeValue) + "*ast.InterfaceType" -> InterfaceType(nativeValue) + "*ast.MapType" -> MapType(nativeValue) + "*ast.StructType" -> StructType(nativeValue) + else -> super.fromNative(nativeValue, context) + } + } + } + + class BasicLit(p: Pointer? = Pointer.NULL) : Expr(p) { + + enum class Kind(val i: Int) { + IDENT(4), + INT(5), + FLOAT(6), + IMAG(7), + CHAR(8), + STRING(9) + } + + val value: String + get() { + return INSTANCE.GetBasicLitValue(this) + } + + val kind: Kind + get() { + return Kind.entries.first { it.i == INSTANCE.GetBasicLitKind(this) } + } + } + + class BinaryExpr(p: Pointer? = Pointer.NULL) : Expr(p) { + val x: Expr + get() { + return INSTANCE.GetBinaryExprX(this) + } + + val opString: String + get() { + return INSTANCE.GetBinaryExprOpString(this) + } + + val y: Expr + get() { + return INSTANCE.GetBinaryExprY(this) + } + } + + class CallExpr(p: Pointer? = Pointer.NULL) : Expr(p) { + val args: List by lazy { + list(INSTANCE::GetNumCallExprArgs, INSTANCE::GetCallExprArg) + } + + val `fun`: Expr + get() { + return INSTANCE.GetCallExprFun(this) + } + } + + class CompositeLit(p: Pointer? = Pointer.NULL) : Expr(p) { + val type: Expr + get() { + return INSTANCE.GetCompositeLitType(this) + } + + val elts: List + get() { + return list(INSTANCE::GetNumCompositeLitElts, INSTANCE::GetCompositeLitElt) + } + } + + class KeyValueExpr(p: Pointer? = Pointer.NULL) : Expr(p) { + val key: Expr + get() { + return INSTANCE.GetKeyValueExprKey(this) + } + + val value: Expr + get() { + return INSTANCE.GetKeyValueExprValue(this) + } + } + + class FuncLit(p: Pointer? = Pointer.NULL) : Expr(p) { + fun toDecl(): FuncDecl { + return INSTANCE.MakeFuncDeclFromFuncLit(this) + } + } + + class Ellipsis(p: Pointer? = Pointer.NULL) : Expr(p) { + val elt: Expr by lazy { INSTANCE.GetEllipsisElt(this) } + } + + class Ident(p: Pointer? = Pointer.NULL) : Expr(p) { + val name: String + get() { + return INSTANCE.GetIdentName(this) + } + + override fun toString(): String { + return name + } + } + + class IndexExpr(p: Pointer? = Pointer.NULL) : Expr(p) { + val x: Expr + get() { + return INSTANCE.GetIndexExprX(this) + } + + val index: Expr + get() { + return INSTANCE.GetIndexExprIndex(this) + } + } + + class SelectorExpr(p: Pointer? = Pointer.NULL) : Expr(p) { + val x: Expr + get() { + return INSTANCE.GetSelectorExprX(this) + } + + val sel: Ident + get() { + return INSTANCE.GetSelectorExprSel(this) + } + } + + class StarExpr(p: Pointer? = Pointer.NULL) : Expr(p) { + val x: Expr + get() { + return INSTANCE.GetStarExprX(this) + } + } + + class SliceExpr(p: Pointer? = Pointer.NULL) : Expr(p) { + val x: Expr + get() { + return INSTANCE.GetSliceExprX(this) + } + + val low: Expr? + get() { + return INSTANCE.GetSliceExprLow(this) + } + + val high: Expr? + get() { + return INSTANCE.GetSliceExprHigh(this) + } + + val max: Expr? + get() { + return INSTANCE.GetSliceExprMax(this) + } + } + + class TypeAssertExpr(p: Pointer? = Pointer.NULL) : Expr(p) { + val x: Expr + get() { + return INSTANCE.GetTypeAssertExprX(this) + } + + val type: Expr? + get() { + return INSTANCE.GetTypeAssertExprType(this) + } + } + + class UnaryExpr(p: Pointer? = Pointer.NULL) : Expr(p) { + val opString: String + get() { + return INSTANCE.GetUnaryExprOpString(this) + } + + val x: Expr + get() { + return INSTANCE.GetUnaryExprX(this) + } + } + + class ArrayType(p: Pointer? = Pointer.NULL) : Expr(p) { + val elt: Expr + get() { + return INSTANCE.GetArrayTypeElt(this) + } + } + + class ChanType(p: Pointer? = Pointer.NULL) : Expr(p) { + val value: Expr + get() { + return INSTANCE.GetChanTypeValue(this) + } + } + + class InterfaceType(p: Pointer? = Pointer.NULL) : Expr(p) { + val methods: FieldList + get() { + return INSTANCE.GetInterfaceTypeMethods(this) + } + + val incomplete: Boolean + get() { + return INSTANCE.GetInterfaceTypeIncomplete(this) + } + } + + class FuncType(p: Pointer? = Pointer.NULL) : Expr(p) { + val typeParams: FieldList? + get() { + return INSTANCE.GetFuncTypeTypeParams(this) + } + + val params: FieldList + get() { + return INSTANCE.GetFuncTypeParams(this) + } + + val results: FieldList? by lazy { INSTANCE.GetFuncTypeResults(this) } + } + + class MapType(p: Pointer? = Pointer.NULL) : Expr(p) { + val key: Expr + get() { + return INSTANCE.GetMapTypeKey(this) + } + + val value: Expr + get() { + return INSTANCE.GetMapTypeValue(this) + } + } + + class StructType(p: Pointer? = Pointer.NULL) : Expr(p) { + val fields: FieldList + get() { + return INSTANCE.GetStructTypeFields(this) + } + + val incomplete: Boolean + get() { + return INSTANCE.GetStructTypeIncomplete(this) + } + } + + open class Stmt(p: Pointer? = Pointer.NULL) : Node(p) { + override fun fromNative(nativeValue: Any?, context: FromNativeContext?): Any? { + if (nativeValue !is Pointer) { + return super.fromNative(nativeValue, context) + } + + return when (INSTANCE.GetType(nativeValue)) { + "*ast.AssignStmt" -> AssignStmt(nativeValue) + "*ast.BlockStmt" -> BlockStmt(nativeValue) + "*ast.BranchStmt" -> BranchStmt(nativeValue) + "*ast.CaseClause" -> CaseClause(nativeValue) + "*ast.DeferStmt" -> DeferStmt(nativeValue) + "*ast.DeclStmt" -> DeclStmt(nativeValue) + "*ast.ExprStmt" -> ExprStmt(nativeValue) + "*ast.GoStmt" -> GoStmt(nativeValue) + "*ast.ForStmt" -> ForStmt(nativeValue) + "*ast.IfStmt" -> IfStmt(nativeValue) + "*ast.IncDecStmt" -> IncDecStmt(nativeValue) + "*ast.LabeledStmt" -> LabeledStmt(nativeValue) + "*ast.RangeStmt" -> RangeStmt(nativeValue) + "*ast.ReturnStmt" -> ReturnStmt(nativeValue) + "*ast.SwitchStmt" -> SwitchStmt(nativeValue) + else -> super.fromNative(nativeValue, context) + } + } + } + + class AssignStmt(p: Pointer? = Pointer.NULL) : Stmt(p) { + val lhs: List + get() { + return this.list(INSTANCE::GetNumAssignStmtLhs, INSTANCE::GetAssignStmtLhs) + } + + val tok: Int + get() { + return INSTANCE.GetAssignStmtTok(this) + } + + val rhs: List + get() { + return this.list(INSTANCE::GetNumAssignStmtRhs, INSTANCE::GetAssignStmtRhs) + } + } + + class BranchStmt(p: Pointer? = Pointer.NULL) : Stmt(p) { + + val tokString: String + get() { + return INSTANCE.GetBranchStmtTokString(this) + } + + val label: Ident? + get() { + return INSTANCE.GetBranchStmtLabel(this) + } + } + + class BlockStmt(p: Pointer? = Pointer.NULL) : Stmt(p) { + val list: List by lazy { + this.list(INSTANCE::GetNumBlockStmtList, INSTANCE::GetBlockStmtList) + } + } + + class CaseClause(p: Pointer? = Pointer.NULL) : Stmt(p) { + val list: List by lazy { + this.list(INSTANCE::GetNumCaseClauseList, INSTANCE::GetCaseClauseList) + } + + val body: List by lazy { + this.list(INSTANCE::GetNumCaseClauseBody, INSTANCE::GetCaseClauseBody) + } + } + + class DeclStmt(p: Pointer? = Pointer.NULL) : Stmt(p) { + val decl: Decl + get() { + return INSTANCE.GetDeclStmtDecl(this) + } + } + + class DeferStmt(p: Pointer? = Pointer.NULL) : Stmt(p) { + val call: Expr + get() { + return INSTANCE.GetDeferStmtCall(this) + } + } + + class ExprStmt(p: Pointer? = Pointer.NULL) : Stmt(p) { + val x: Expr + get() { + return INSTANCE.GetExprStmtX(this) + } + } + + class IfStmt(p: Pointer? = Pointer.NULL) : Stmt(p) { + val init: Stmt? + get() { + return INSTANCE.GetIfStmtInit(this) + } + + val cond: Expr + get() { + return INSTANCE.GetIfStmtCond(this) + } + + val body: BlockStmt + get() { + return INSTANCE.GetIfStmtBody(this) + } + + val `else`: Stmt? + get() { + return INSTANCE.GetIfStmtElse(this) + } + } + + class ForStmt(p: Pointer? = Pointer.NULL) : Stmt(p) { + val init: Stmt? + get() { + return INSTANCE.GetForStmtInit(this) + } + + val cond: Expr? + get() { + return INSTANCE.GetForStmtCond(this) + } + + val post: Stmt? + get() { + return INSTANCE.GetForStmtPost(this) + } + + val body: BlockStmt? + get() { + return INSTANCE.GetForStmtBody(this) + } + } + + class GoStmt(p: Pointer? = Pointer.NULL) : Stmt(p) { + val call: Expr + get() { + return INSTANCE.GetGoStmtCall(this) + } + } + + class IncDecStmt(p: Pointer? = Pointer.NULL) : Stmt(p) { + val tokString: String + get() { + return INSTANCE.GetIncDecStmtTokString(this) + } + + val x: Expr + get() { + return INSTANCE.GetIncDecStmtX(this) + } + } + + class LabeledStmt(p: Pointer? = Pointer.NULL) : Stmt(p) { + + val label: Ident + get() { + return INSTANCE.GetLabeledStmtLabel(this) + } + + val stmt: Stmt + get() { + return INSTANCE.GetLabeledStmtStmt(this) + } + } + + class RangeStmt(p: Pointer? = Pointer.NULL) : Stmt(p) { + val tokString: String + get() { + return INSTANCE.GetRangeStmtTokString(this) + } + + val key: Expr? + get() { + return INSTANCE.GetRangeStmtKey(this) + } + + val value: Expr? + get() { + return INSTANCE.GetRangeStmtValue(this) + } + + val x: Expr + get() { + return INSTANCE.GetRangeStmtX(this) + } + + val body: BlockStmt + get() { + return INSTANCE.GetRangeStmtBody(this) + } + } + + class ReturnStmt(p: Pointer? = Pointer.NULL) : Stmt(p) { + val results: List + get() { + return list(INSTANCE::GetNumReturnStmtResults, INSTANCE::GetReturnStmtResult) + } + } + + class SwitchStmt(p: Pointer? = Pointer.NULL) : Stmt(p) { + val init: Stmt? + get() { + return INSTANCE.GetSwitchStmtInit(this) + } + + val tag: Expr? + get() { + return INSTANCE.GetSwitchStmtTag(this) + } + + val body: BlockStmt + get() { + return INSTANCE.GetSwitchStmtBody(this) + } + } + + class Position(p: Pointer? = Pointer.NULL) : GoObject(p) { + val line: Int + get() { + return INSTANCE.GetPositionLine(this) + } + + val column: Int + get() { + return INSTANCE.GetPositionColumn(this) + } + } + + class FileSet(p: Pointer? = Pointer.NULL) : GoObject(p) { + fun position(pos: Int): Position { + return INSTANCE.GetFileSetPosition(this, pos) + } + + fun fileName(pos: Int): String? { + return INSTANCE.GetFileSetFileName(this, pos) + } + + fun code(astNode: Node): String? { + return INSTANCE.GetFileSetNodeCode(this, astNode) + } + } + + class CommentMap(p: Pointer? = Pointer.NULL) : GoObject(p) { + fun comment(node: Node): String? { + return INSTANCE.GetCommentMapNodeComment(this, node) + } + } + + class File(p: Pointer? = Pointer.NULL) : Node(p) { + val comments: Pointer + get() { + return INSTANCE.GetFileComments(this) + } + + val imports: List + get() { + return list(INSTANCE::GetNumFileImports, INSTANCE::GetFileImport) + } + + val decls: List + get() { + return this.list(INSTANCE::GetNumFileDecls, INSTANCE::GetFileDecl) + } + + val name: Ident + get() { + return INSTANCE.GetFileName(this) + } + } + } + + // go/parser package + + fun goParserParseFile(fileSet: Ast.FileSet, path: String, src: String): Ast.File + + fun GetType(obj: Pointer): String + + fun GetNodePos(node: Ast.Node): Int + + fun GetNodeEnd(node: Ast.Node): Int + + fun NewFileSet(): Ast.FileSet + + fun NewCommentMap(fset: Ast.FileSet, file: Ast.File, comments: Any): Ast.CommentMap + + fun GetCommentMapNodeComment(commentMap: Ast.CommentMap, node: Ast.Node): String? + + fun GetFileName(file: Ast.File): Ast.Ident + + fun GetFieldType(field: Ast.Field): Ast.Expr + + fun GetNumFieldListList(fieldList: Ast.FieldList): Int + + fun GetFieldListList(fieldList: Ast.FieldList, i: Int): Ast.Field + + fun GetNumFieldNames(field: Ast.Field): Int + + fun GetFieldName(field: Ast.Field, i: Int): Ast.Ident + + fun GetNumFileImports(file: Ast.File): Int + + fun GetPositionLine(position: Ast.Position): Int + + fun GetPositionColumn(position: Ast.Position): Int + + fun GetFileSetPosition(fileSet: Ast.FileSet, pos: Int): Ast.Position + + fun GetFileSetFileName(fileSet: Ast.FileSet, pos: Int): String? + + fun GetFileSetNodeCode(fileSet: Ast.FileSet, node: Ast.Node): String? + + fun GetFileComments(file: Ast.File): Pointer + + fun GetFileImport(file: Ast.File, i: Int): Ast.ImportSpec + + fun GetNumFileDecls(file: Ast.File): Int + + fun GetFileDecl(file: Ast.File, i: Int): Ast.Decl + + fun GetFuncDeclRecv(funcDecl: Ast.FuncDecl): Ast.FieldList? + + fun GetFuncDeclType(funcDecl: Ast.FuncDecl): Ast.FuncType + + fun GetFuncDeclName(funcDecl: Ast.FuncDecl): Ast.Ident + + fun GetFuncDeclBody(funcDecl: Ast.FuncDecl): Ast.BlockStmt + + fun GetCompositeLitType(compositeLit: Ast.CompositeLit): Ast.Expr + + fun GetNumCompositeLitElts(compositeLit: Ast.CompositeLit): Int + + fun GetCompositeLitElt(compositeLit: Ast.CompositeLit, i: Int): Ast.Expr + + fun MakeFuncDeclFromFuncLit(funcLit: Ast.FuncLit): Ast.FuncDecl + + fun GetEllipsisElt(ellipsis: Ast.Ellipsis): Ast.Expr + + fun GetIdentName(ident: Ast.Ident): String + + fun GetKeyValueExprKey(keyValueExpr: Ast.KeyValueExpr): Ast.Expr + + fun GetKeyValueExprValue(keyValueExpr: Ast.KeyValueExpr): Ast.Expr + + fun GetBasicLitValue(basicLit: Ast.BasicLit): String + + fun GetBasicLitKind(basicLit: Ast.BasicLit): Int + + fun GetBinaryExprOpString(binaryExpr: Ast.BinaryExpr): String + + fun GetBinaryExprX(binaryExpr: Ast.BinaryExpr): Ast.Expr + + fun GetBinaryExprY(binaryExpr: Ast.BinaryExpr): Ast.Expr + + fun GetCallExprFun(callExpr: Ast.CallExpr): Ast.Expr + + fun GetNumCallExprArgs(callExpr: Ast.CallExpr): Int + + fun GetCallExprArg(callExpr: Ast.CallExpr, i: Int): Ast.Expr + + fun GetSelectorExprSel(selectorExpr: Ast.SelectorExpr): Ast.Ident + + fun GetSelectorExprX(selectorExpr: Ast.SelectorExpr): Ast.Expr + + fun GetStarExprX(starExpr: Ast.StarExpr): Ast.Expr + + fun GetSliceExprX(sliceExpr: Ast.SliceExpr): Ast.Expr + + fun GetSliceExprLow(sliceExpr: Ast.SliceExpr): Ast.Expr? + + fun GetSliceExprHigh(sliceExpr: Ast.SliceExpr): Ast.Expr? + + fun GetSliceExprMax(sliceExpr: Ast.SliceExpr): Ast.Expr? + + fun GetTypeAssertExprX(typeAssertExpr: Ast.TypeAssertExpr): Ast.Expr + + fun GetTypeAssertExprType(typeAssertExpr: Ast.TypeAssertExpr): Ast.Expr? + + fun GetUnaryExprOpString(unaryExpr: Ast.UnaryExpr): String + + fun GetUnaryExprX(unaryExpr: Ast.UnaryExpr): Ast.Expr + + fun GetArrayTypeElt(arrayType: Ast.ArrayType): Ast.Expr + + fun GetChanTypeValue(chanType: Ast.ChanType): Ast.Expr + + fun GetInterfaceTypeMethods(interfaceType: Ast.InterfaceType): Ast.FieldList + + fun GetInterfaceTypeIncomplete(interfaceType: Ast.InterfaceType): Boolean + + fun GetFuncTypeTypeParams(funcType: Ast.FuncType): Ast.FieldList? + + fun GetFuncTypeParams(funcType: Ast.FuncType): Ast.FieldList + + fun GetFuncTypeResults(funcType: Ast.FuncType): Ast.FieldList? + + fun GetMapTypeKey(mapType: Ast.MapType): Ast.Expr + + fun GetMapTypeValue(mapType: Ast.MapType): Ast.Expr + + fun GetStructTypeFields(structType: Ast.StructType): Ast.FieldList + + fun GetStructTypeIncomplete(structType: Ast.StructType): Boolean + + fun GetAssignStmtTok(assignStmt: Ast.AssignStmt): Int + + fun GetNumAssignStmtLhs(assignStmt: Ast.AssignStmt): Int + + fun GetAssignStmtLhs(assignStmt: Ast.AssignStmt, i: Int): Ast.Expr + + fun GetNumAssignStmtRhs(assignStmt: Ast.AssignStmt): Int + + fun GetAssignStmtRhs(assignStmt: Ast.AssignStmt, i: Int): Ast.Expr + + fun GetNumBlockStmtList(blockStmt: Ast.BlockStmt): Int + + fun GetBlockStmtList(blockStmt: Ast.BlockStmt, i: Int): Ast.Stmt + + fun GetBranchStmtTokString(branchStmt: Ast.BranchStmt): String + + fun GetBranchStmtLabel(branchStmt: Ast.BranchStmt): Ast.Ident? + + fun GetNumCaseClauseList(caseClause: Ast.CaseClause): Int + + fun GetCaseClauseList(caseClause: Ast.CaseClause, i: Int): Ast.Expr + + fun GetNumCaseClauseBody(caseClause: Ast.CaseClause): Int + + fun GetCaseClauseBody(caseClause: Ast.CaseClause, i: Int): Ast.Stmt + + fun GetDeclStmtDecl(declStmt: Ast.DeclStmt): Ast.Decl + + fun GetExprStmtX(exprStmt: Ast.ExprStmt): Ast.Expr + + fun GetDeferStmtCall(deferStmt: Ast.DeferStmt): Ast.Expr + + fun GetForStmtInit(forStmt: Ast.ForStmt): Ast.Stmt? + + fun GetForStmtCond(forStmt: Ast.ForStmt): Ast.Expr? + + fun GetForStmtPost(forStmt: Ast.ForStmt): Ast.Stmt? + + fun GetForStmtBody(forStmt: Ast.ForStmt): Ast.BlockStmt? + + fun GetGoStmtCall(goStmt: Ast.GoStmt): Ast.Expr + + fun GetIncDecStmtTokString(incDecStmt: Ast.IncDecStmt): String + + fun GetIncDecStmtX(incDecStmt: Ast.IncDecStmt): Ast.Expr + + fun GetLabeledStmtLabel(labeledStmt: Ast.LabeledStmt): Ast.Ident + + fun GetLabeledStmtStmt(labeledStmt: Ast.LabeledStmt): Ast.Stmt + + fun GetIndexExprX(IndexExpr: Ast.IndexExpr): Ast.Expr + + fun GetIndexExprIndex(IndexExpr: Ast.IndexExpr): Ast.Expr + + fun GetIfStmtInit(ifStmt: Ast.IfStmt): Ast.Stmt? + + fun GetIfStmtCond(ifStmt: Ast.IfStmt): Ast.Expr + + fun GetIfStmtBody(ifStmt: Ast.IfStmt): Ast.BlockStmt + + fun GetIfStmtElse(ifStmt: Ast.IfStmt): Ast.Stmt? + + fun GetRangeStmtTokString(rangeStmt: Ast.RangeStmt): String + + fun GetRangeStmtKey(rangeStmt: Ast.RangeStmt): Ast.Expr? + + fun GetRangeStmtValue(rangeStmt: Ast.RangeStmt): Ast.Expr? + + fun GetRangeStmtX(rangeStmt: Ast.RangeStmt): Ast.Expr + + fun GetRangeStmtBody(rangeStmt: Ast.RangeStmt): Ast.BlockStmt + + fun GetNumReturnStmtResults(returnStmt: Ast.ReturnStmt): Int + + fun GetReturnStmtResult(returnStmt: Ast.ReturnStmt, i: Int): Ast.Expr + + fun GetSwitchStmtInit(switchStmt: Ast.SwitchStmt): Ast.Stmt? + + fun GetSwitchStmtTag(switchStmt: Ast.SwitchStmt): Ast.Expr? + + fun GetSwitchStmtBody(stmt: Ast.SwitchStmt): Ast.BlockStmt + + fun GetNumGenDeclSpecs(genDecl: Ast.GenDecl): Int + + fun GetGenDeclSpec(genDecl: Ast.GenDecl, i: Int): Ast.Spec + + fun GetImportSpecName(importSpec: Ast.ImportSpec): Ast.Ident? + + fun GetImportSpecPath(importSpec: Ast.ImportSpec): Ast.BasicLit + + fun GetNumValueSpecNames(valueSpec: Ast.ValueSpec): Int + + fun GetValueSpecName(valueSpec: Ast.ValueSpec, i: Int): Ast.Ident + + fun GetValueSpecType(valueSpec: Ast.ValueSpec): Ast.Expr + + fun GetNumValueSpecValues(valueSpec: Ast.ValueSpec): Int + + fun GetValueSpecValue(valueSpec: Ast.ValueSpec, i: Int): Ast.Expr + + fun GetTypeSpecName(typeSpec: Ast.TypeSpec): Ast.Ident + + fun GetTypeSpecType(typeSpec: Ast.TypeSpec): Ast.Expr + + companion object { + val INSTANCE: GoStandardLibrary by lazy { + try { + val arch = + System.getProperty("os.arch") + .replace("aarch64", "arm64") + .replace("x86_64", "amd64") + val ext: String = + if (System.getProperty("os.name").startsWith("Mac")) { + ".dylib" + } else { + ".so" + } + + val stream = + GoLanguageFrontend::class.java.getResourceAsStream("/libcpgo-$arch$ext") + + val tmp = java.io.File.createTempFile("libcpgo", ext) + tmp.deleteOnExit() + val fos = FileOutputStream(tmp) + stream?.copyTo(FileOutputStream(tmp)) + + fos.close() + stream?.close() + + LanguageFrontend.log.info("Loading cpgo library from ${tmp.absoluteFile}") + + // System.load(tmp.absolutePath) + Native.load(tmp.absolutePath, GoStandardLibrary::class.java) + } catch (ex: Exception) { + throw TranslationException( + "Error while loading cpgo library. Go frontend will not work correctly: $ex" + ) + } + } + } +} + +// TODO: optimize to use iterators instead +fun T.list( + numFunc: (T) -> Int, + itemFunc: (T, Int) -> S +): MutableList { + val list = mutableListOf() + for (i in 0 until numFunc(this)) { + list += itemFunc(this, i) + } + + return list +} diff --git a/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/SpecificationHandler.kt b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/SpecificationHandler.kt new file mode 100644 index 0000000000..ebe9297f01 --- /dev/null +++ b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/SpecificationHandler.kt @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2023, 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.frontends.golang + +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.* + +class SpecificationHandler(frontend: GoLanguageFrontend) : + Handler( + ::ProblemDeclaration, + frontend + ) { + + init { + map[GoStandardLibrary.Ast.ImportSpec::class.java] = HandlerInterface { + handleImportSpec(it as GoStandardLibrary.Ast.ImportSpec) + } + map[GoStandardLibrary.Ast.TypeSpec::class.java] = HandlerInterface { + handleTypeSpec(it as GoStandardLibrary.Ast.TypeSpec) + } + map[GoStandardLibrary.Ast.ValueSpec::class.java] = HandlerInterface { + handleValueSpec(it as GoStandardLibrary.Ast.ValueSpec) + } + map.put(GoStandardLibrary.Ast.Spec::class.java, ::handleNode) + } + + private fun handleNode(spec: GoStandardLibrary.Ast.Spec): Declaration { + val message = "Not parsing specification of type ${spec.goType} yet" + log.error(message) + + return newProblemDeclaration(message) + } + + private fun handleImportSpec(importSpec: GoStandardLibrary.Ast.ImportSpec): IncludeDeclaration { + // We set the name of the include declaration to the imported name, i.e., the package name + val name = frontend.getImportName(importSpec) + // We set the filename of the include declaration to the package path, i.e., its full path + // including any module identifiers. This way we can match the include declaration back to + // the namespace's path and name + val filename = importSpec.path.value.removeSurrounding("\"") + val include = newIncludeDeclaration(filename, rawNode = importSpec) + include.name = parseName(name) + + return include + } + + private fun handleTypeSpec(spec: GoStandardLibrary.Ast.TypeSpec): Declaration { + val type = spec.type + val decl = + when (type) { + is GoStandardLibrary.Ast.StructType -> handleStructTypeSpec(spec, type) + is GoStandardLibrary.Ast.InterfaceType -> handleInterfaceTypeSpec(spec, type) + else -> return ProblemDeclaration("not parsing type of type ${spec.goType} yet") + } + + return decl + } + + private fun handleStructTypeSpec( + typeSpec: GoStandardLibrary.Ast.TypeSpec, + structType: GoStandardLibrary.Ast.StructType + ): RecordDeclaration { + val record = newRecordDeclaration(typeSpec.name.name, "struct", rawNode = typeSpec) + + frontend.scopeManager.enterScope(record) + + if (!structType.incomplete) { + for (field in structType.fields.list) { + // a field can also have no name, which means that it is embedded, not quite + // sure yet how to handle this, but since the embedded field can be accessed + // by its type, it could make sense to name the field according to the type + val type = frontend.typeOf(field.type) + + val name = + if (field.names.isEmpty()) { + // Retrieve the root type name + type.root.name.toString() + } else { + field.names[0].name + } + + val decl = newFieldDeclaration(name, type, rawNode = field) + frontend.scopeManager.addDeclaration(decl) + } + } + + frontend.scopeManager.leaveScope(record) + + return record + } + + private fun handleInterfaceTypeSpec( + typeSpec: GoStandardLibrary.Ast.TypeSpec, + interfaceType: GoStandardLibrary.Ast.InterfaceType + ): Declaration { + val record = newRecordDeclaration(typeSpec.name.name, "interface", rawNode = typeSpec) + + frontend.scopeManager.enterScope(record) + + if (!interfaceType.incomplete) { + for (field in interfaceType.methods.list) { + val type = frontend.typeOf(field.type) + + // Even though this list is called "Methods", it contains all kinds + // of things, so we need to proceed with caution. Only if the + // "method" actually has a name, we declare a new method + // declaration. + if (field.names.isNotEmpty()) { + val method = newMethodDeclaration(field.names[0].name, rawNode = field) + method.type = type + + frontend.scopeManager.addDeclaration(method) + } else { + log.debug("Adding {} as super class of interface {}", type.name, record.name) + // Otherwise, it contains either types or interfaces. For now, we + // hope that it only has interfaces. We consider embedded + // interfaces as sort of super types for this interface. + record.addSuperClass(type) + } + } + } + + frontend.scopeManager.leaveScope(record) + + return record + } + + /** + * // handleValueSpec handles parsing of an ast.ValueSpec, which is a variable // declaration. + * Since this can potentially declare multiple variables with one // "spec", this returns a + * [DeclarationSequence]. + */ + private fun handleValueSpec(valueSpec: GoStandardLibrary.Ast.ValueSpec): DeclarationSequence { + val sequence = DeclarationSequence() + + for ((idx, ident) in valueSpec.names.withIndex()) { + val decl = newVariableDeclaration(ident.name, rawNode = valueSpec) + + if (valueSpec.type != null) { + decl.type = frontend.typeOf(valueSpec.type!!) + } + + // There could either be no initializers, otherwise the amount of values + // must match the names + val lenValues = valueSpec.values.size + if (lenValues != 0 && lenValues != valueSpec.names.size) { + log.error( + "Number of initializers does not match number of names. Initializers might be incomplete" + ) + } + + // The initializer is in the "Values" slice with the respective index + if (valueSpec.values.size > idx) { + decl.initializer = frontend.expressionHandler.handle(valueSpec.values[idx]) + } + + sequence += decl + } + + return sequence + } +} diff --git a/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/StatementHandler.kt b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/StatementHandler.kt new file mode 100644 index 0000000000..0159cf2d42 --- /dev/null +++ b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/StatementHandler.kt @@ -0,0 +1,325 @@ +/* + * Copyright (c) 2023, 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.frontends.golang + +import de.fraunhofer.aisec.cpg.graph.* +import de.fraunhofer.aisec.cpg.graph.declarations.DeclarationSequence +import de.fraunhofer.aisec.cpg.graph.statements.* +import de.fraunhofer.aisec.cpg.graph.statements.expressions.* + +class StatementHandler(frontend: GoLanguageFrontend) : + GoHandler(::ProblemExpression, frontend) { + + override fun handleNode(stmt: GoStandardLibrary.Ast.Stmt): Statement { + return when (stmt) { + is GoStandardLibrary.Ast.AssignStmt -> handleAssignStmt(stmt) + is GoStandardLibrary.Ast.BranchStmt -> handleBranchStmt(stmt) + is GoStandardLibrary.Ast.BlockStmt -> handleBlockStmt(stmt) + is GoStandardLibrary.Ast.CaseClause -> handleCaseClause(stmt) + is GoStandardLibrary.Ast.DeclStmt -> handleDeclStmt(stmt) + is GoStandardLibrary.Ast.DeferStmt -> handleDeferStmt(stmt) + is GoStandardLibrary.Ast.ExprStmt -> { + return frontend.expressionHandler.handle(stmt.x) + } + is GoStandardLibrary.Ast.ForStmt -> handleForStmt(stmt) + is GoStandardLibrary.Ast.GoStmt -> handleGoStmt(stmt) + is GoStandardLibrary.Ast.IncDecStmt -> handleIncDecStmt(stmt) + is GoStandardLibrary.Ast.IfStmt -> handleIfStmt(stmt) + is GoStandardLibrary.Ast.LabeledStmt -> handleLabeledStmt(stmt) + is GoStandardLibrary.Ast.RangeStmt -> handleRangeStmt(stmt) + is GoStandardLibrary.Ast.ReturnStmt -> handleReturnStmt(stmt) + is GoStandardLibrary.Ast.SwitchStmt -> handleSwitchStmt(stmt) + else -> handleNotSupported(stmt, stmt.goType) + } + } + + private fun handleAssignStmt(assignStmt: GoStandardLibrary.Ast.AssignStmt): AssignExpression { + val lhs = assignStmt.lhs.map { frontend.expressionHandler.handle(it) } + val rhs = assignStmt.rhs.map { frontend.expressionHandler.handle(it) } + + // We need to explicitly set the operator code on this assignment as + // something which potentially declares a variable, so we can resolve this + // in our extra pass. + val operatorCode = + if (assignStmt.tok == 47) { + ":=" + } else { + "" + } + + return newAssignExpression(operatorCode, lhs, rhs, rawNode = assignStmt) + } + + private fun handleBranchStmt(branchStmt: GoStandardLibrary.Ast.BranchStmt): Statement { + when (branchStmt.tokString) { + "break" -> { + val stmt = newBreakStatement(rawNode = branchStmt) + branchStmt.label?.let { stmt.label = it.name } + return stmt + } + "continue" -> { + val stmt = newContinueStatement(rawNode = branchStmt) + branchStmt.label?.let { stmt.label = it.name } + return stmt + } + "goto" -> { + val stmt = newGotoStatement(rawNode = branchStmt) + branchStmt.label?.let { stmt.labelName = it.name } + return stmt + } + } + + return newProblemExpression("unknown token \"${branchStmt.tokString}\" in branch statement") + } + + private fun handleBlockStmt(blockStmt: GoStandardLibrary.Ast.BlockStmt): Statement { + val compound = newCompoundStatement(rawNode = blockStmt) + + frontend.scopeManager.enterScope(compound) + + for (stmt in blockStmt.list) { + val node = handle(stmt) + // Do not add case statements to the block because the already add themselves in + // handleCaseClause. Otherwise, the order of case's would be wrong + if (node !is CaseStatement) { + compound += node + } + } + + frontend.scopeManager.leaveScope(compound) + + return compound + } + + private fun handleCaseClause(caseClause: GoStandardLibrary.Ast.CaseClause): Statement { + val case = + if (caseClause.list.isEmpty()) { + newDefaultStatement(rawNode = caseClause) + } else { + val case = newCaseStatement(rawNode = caseClause) + case.caseExpression = frontend.expressionHandler.handle(caseClause.list[0]) + case + } + + // We need to find the current block / scope and add the statements to it + val block = frontend.scopeManager.currentBlock + + if (block == null) { + log.error("could not find block to add case clauses") + return newProblemExpression("could not find block to add case clauses") + } + + // Add the case statement + block += case + + for (s in caseClause.body) { + block += handle(s) + } + + // this is a little trick, to not add the case statement in handleStmt because we added it + // already. otherwise, the order is screwed up. + return case + } + + private fun handleDeclStmt(declStmt: GoStandardLibrary.Ast.DeclStmt): DeclarationStatement { + // Let's create a variable declaration (wrapped with a declaration stmt) with + // this, because we define the variable here + val stmt = newDeclarationStatement(rawNode = declStmt) + val sequence = frontend.declarationHandler.handle(declStmt.decl) + if (sequence is DeclarationSequence) { + for (declaration in sequence.declarations) { + frontend.scopeManager.addDeclaration(declaration) + } + stmt.declarations = sequence.asList() + } else { + frontend.scopeManager.addDeclaration(sequence) + stmt.singleDeclaration = sequence + } + + return stmt + } + + /** + * // handleDeferStmt handles the `defer` statement, which is a special keyword in go // that + * the supplied callee is executed once the function it is called in exists. // We cannot model + * this 1:1, so we basically we create a call expression to a built-in call. // We adjust the + * EOG of the call later in an extra pass. + */ + private fun handleDeferStmt(deferStmt: GoStandardLibrary.Ast.DeferStmt): UnaryOperator { + val op = newUnaryOperator("defer", postfix = false, prefix = true, rawNode = deferStmt) + op.input = frontend.expressionHandler.handle(deferStmt.call) + return op + } + + /** + * This function handles the `go` statement, which is a special keyword in go that starts the + * supplied call expression in a separate Go routine. We cannot model this 1:1, so we basically + * we create a call expression to a built-in call. + */ + private fun handleGoStmt(goStmt: GoStandardLibrary.Ast.GoStmt): CallExpression { + val ref = newDeclaredReferenceExpression("go") + val call = newCallExpression(ref, "go", rawNode = goStmt) + call += frontend.expressionHandler.handle(goStmt.call) + + return call + } + + private fun handleForStmt(forStmt: GoStandardLibrary.Ast.ForStmt): ForStatement { + val stmt = newForStatement(rawNode = forStmt) + + frontend.scopeManager.enterScope(stmt) + + forStmt.init?.let { stmt.initializerStatement = handle(it) } + forStmt.cond?.let { stmt.condition = frontend.expressionHandler.handle(it) } + forStmt.post?.let { stmt.iterationStatement = handle(it) } + forStmt.body?.let { stmt.statement = handle(it) } + + frontend.scopeManager.leaveScope(stmt) + + return stmt + } + + private fun handleIncDecStmt(incDecStmt: GoStandardLibrary.Ast.IncDecStmt): UnaryOperator { + val op = + newUnaryOperator( + incDecStmt.tokString, + postfix = true, + prefix = false, + rawNode = incDecStmt + ) + op.input = frontend.expressionHandler.handle(incDecStmt.x) + + return op + } + + private fun handleIfStmt(ifStmt: GoStandardLibrary.Ast.IfStmt): IfStatement { + val stmt = newIfStatement(rawNode = ifStmt) + + frontend.scopeManager.enterScope(stmt) + + ifStmt.init?.let { stmt.initializerStatement = frontend.statementHandler.handle(it) } + + stmt.condition = frontend.expressionHandler.handle(ifStmt.cond) + stmt.thenStatement = frontend.statementHandler.handle(ifStmt.body) + + ifStmt.`else`?.let { stmt.elseStatement = frontend.statementHandler.handle(it) } + + frontend.scopeManager.leaveScope(stmt) + + return stmt + } + + private fun handleLabeledStmt(labeledStmt: GoStandardLibrary.Ast.LabeledStmt): LabelStatement { + val stmt = newLabelStatement(rawNode = labeledStmt) + stmt.subStatement = handle(labeledStmt.stmt) + stmt.label = labeledStmt.label.name + + return stmt + } + + private fun handleRangeStmt(rangeStmt: GoStandardLibrary.Ast.RangeStmt): ForEachStatement { + val forEach = newForEachStatement(rawNode = rangeStmt) + + frontend.scopeManager.enterScope(forEach) + + // TODO: Support other use cases that do not use DEFINE + if (rangeStmt.tokString == ":=") { + val stmt = newDeclarationStatement() + + // TODO: not really the best way to deal with this + // TODO: key type is always int. we could set this + var ref = rangeStmt.key?.let { frontend.expressionHandler.handle(it) } + if (ref is DeclaredReferenceExpression) { + val key = newVariableDeclaration(ref.name, rawNode = rangeStmt.key) + frontend.scopeManager.addDeclaration(key) + stmt.addToPropertyEdgeDeclaration(key) + } + + // TODO: not really the best way to deal with this + ref = rangeStmt.value?.let { frontend.expressionHandler.handle(it) } + if (ref is DeclaredReferenceExpression) { + val key = newVariableDeclaration(ref.name, rawNode = rangeStmt.key) + frontend.scopeManager.addDeclaration(key) + stmt.addToPropertyEdgeDeclaration(key) + } + + forEach.variable = stmt + } + + forEach.iterable = frontend.expressionHandler.handle(rangeStmt.x) + forEach.statement = frontend.statementHandler.handle(rangeStmt.body) + + frontend.scopeManager.leaveScope(forEach) + + return forEach + } + + private fun handleReturnStmt(returnStmt: GoStandardLibrary.Ast.ReturnStmt): ReturnStatement { + val `return` = newReturnStatement(rawNode = returnStmt) + + val results = returnStmt.results + if (results.isNotEmpty()) { + val expr = frontend.expressionHandler.handle(results[0]) + + // TODO: parse more than one result expression + if (expr != null) { + `return`.returnValue = expr + } + } else { + // TODO: connect result statement to result variables + } + + return `return` + } + + private fun handleSwitchStmt(switchStmt: GoStandardLibrary.Ast.SwitchStmt): Statement { + val switch = newSwitchStatement(rawNode = switchStmt) + + frontend.scopeManager.enterScope(switch) + + switchStmt.init?.let { switch.initializerStatement = handle(it) } + switchStmt.tag?.let { switch.selector = frontend.expressionHandler.handle(it) } + + val block = + handle(switchStmt.body) as? CompoundStatement + ?: return newProblemExpression("missing switch body") + + // Because of the way we parse the statements, the case statement turns out to be the last + // statement. However, we need it to be the first statement, so we need to switch first and + // last items + /*val statements = block.statements.toMutableList() + val tmp = statements.first() + statements[0] = block.statements.last() + statements[(statements.size - 1).coerceAtLeast(0)] = tmp + block.statements = statements*/ + + switch.statement = block + + frontend.scopeManager.leaveScope(switch) + + return switch + } +} diff --git a/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/GoEvaluationOrderGraphPass.kt b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/GoEvaluationOrderGraphPass.kt new file mode 100644 index 0000000000..e519e15f30 --- /dev/null +++ b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/GoEvaluationOrderGraphPass.kt @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2023, 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.frontends.golang.GoLanguage +import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration +import de.fraunhofer.aisec.cpg.graph.followNextEOGEdgesUntilHit +import de.fraunhofer.aisec.cpg.graph.statements.ReturnStatement +import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.UnaryOperator + +/** This pass contains fine-grained improvements to the EOG for the [GoLanguage]. */ +class GoEvaluationOrderGraphPass(ctx: TranslationContext) : EvaluationOrderGraphPass(ctx) { + + /** + * Go allows the automatic execution of certain cleanup calls before we exit the function (using + * `defer`). We need to gather the appropriate deferred call expressions and then connect them + * in [handleFunctionDeclaration]. + */ + private var deferredCalls = mutableMapOf>() + + override fun handleUnspecificUnaryOperator(node: UnaryOperator) { + val input = node.input + if (node.operatorCode == "defer" && input is CallExpression) { + handleDeferUnaryOperator(node, input) + } else { + super.handleUnspecificUnaryOperator(node) + } + } + + /** Handles the EOG for a [`defer`](https://go.dev/ref/spec#Defer_statements) statement. */ + private fun handleDeferUnaryOperator(node: UnaryOperator, input: CallExpression) { + val function = scopeManager.currentFunction + if (function != null) { + // We need to disrupt the regular EOG handling here and store this deferred call. We + // will pick it up again in handleFunctionDeclaration. + val calls = deferredCalls.computeIfAbsent(function) { mutableListOf() } + calls += node + + // Push the node itself to the EOG, not its "input" (the deferred call). However, it + // seems that the arguments of the deferred call are evaluated at the point of the + // deferred statement, duh! + pushToEOG(node) + + // Evaluate the callee + input.callee?.let { createEOG(it) } + + // Then the arguments + for (arg in input.arguments) { + createEOG(arg) + } + } else { + log.error( + "Tried to parse a defer statement but could not retrieve current function from scope manager." + ) + } + } + + override fun handleFunctionDeclaration(node: FunctionDeclaration) { + // First, call the regular EOG handler + super.handleFunctionDeclaration(node) + + // Before we exit the function, we need to call the deferred calls for this function + val defers = deferredCalls[node] + + // We need to follow the path from the defer statement to all return statements that are + // reachable from this point. + for (defer in defers ?: listOf()) { + val paths = defer.followNextEOGEdgesUntilHit { it is ReturnStatement } + for (path in paths.fulfilled) { + // It is a bit philosophical whether the deferred call happens before or after the + // return statement in the EOG. For now, it is easier to have it as the last node + // AFTER the return statement + addEOGEdge(path.last(), defer.input) + } + } + } +} diff --git a/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/GoExtraPass.kt b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/GoExtraPass.kt index a0c7bf15de..f806f5d9ed 100644 --- a/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/GoExtraPass.kt +++ b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/GoExtraPass.kt @@ -199,6 +199,12 @@ class GoExtraPass(ctx: TranslationContext) : ComponentPass(ctx), ScopeProvider { */ // TODO: Somehow, this gets called twice?! private fun handleInclude(include: IncludeDeclaration) { + // If the namespace is included as _, we can ignore it, as its only included as a runtime + // dependency + if (include.name.localName == "_") { + return + } + // Try to see if we already know about this namespace somehow val namespace = scopeManager.resolve(scopeManager.globalScope, true) { diff --git a/cpg-language-go/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoLanguageFrontendTest.kt b/cpg-language-go/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoLanguageFrontendTest.kt index dca2e02f44..37776c62a3 100644 --- a/cpg-language-go/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoLanguageFrontendTest.kt +++ b/cpg-language-go/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoLanguageFrontendTest.kt @@ -355,20 +355,18 @@ class GoLanguageFrontendTest : BaseTest() { var methods = myStruct.methods - var myFunc = methods.first() + var myFunc = methods.firstOrNull() + assertNotNull(myFunc) assertLocalName("MyFunc", myFunc) - val myField = fields.first() + val myField = fields.firstOrNull() + assertNotNull(myField) assertLocalName("MyField", myField) assertEquals(tu.primitiveType("int"), myField.type) - val myInterface = - p.getDeclarationsByName("p.MyInterface", RecordDeclaration::class.java) - .iterator() - .next() - + val myInterface = p.records["p.MyInterface"] assertNotNull(myInterface) assertEquals("interface", myInterface.kind) @@ -684,6 +682,10 @@ class GoLanguageFrontendTest : BaseTest() { val tu1 = tus[1] assertNotNull(tu1) + val include = tu1.includes["awesome"] + assertNotNull(include) + assertEquals("example.io/awesome", include.filename) + val main = tu1.functions["main.main"] assertNotNull(main) diff --git a/cpg-language-go/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/StatementTest.kt b/cpg-language-go/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/StatementTest.kt new file mode 100644 index 0000000000..3e698bc905 --- /dev/null +++ b/cpg-language-go/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/StatementTest.kt @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2023, 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.frontends.golang + +import de.fraunhofer.aisec.cpg.TestUtils.analyzeAndGetFirstTU +import de.fraunhofer.aisec.cpg.graph.* +import de.fraunhofer.aisec.cpg.graph.statements.* +import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Literal +import de.fraunhofer.aisec.cpg.graph.statements.expressions.UnaryOperator +import java.nio.file.Path +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertIs +import kotlin.test.assertNotNull + +class StatementTest { + + @Test + fun testBranchStatement() { + val topLevel = Path.of("src", "test", "resources", "golang") + val tu = + analyzeAndGetFirstTU(listOf(topLevel.resolve("branch.go").toFile()), topLevel, true) { + it.registerLanguage() + } + + assertNotNull(tu) + + val p = tu.namespaces["p"] + assertNotNull(p) + + val main = p.functions["main"] + assertNotNull(main) + + val start = main.allChildren().firstOrNull { it.label == "start" } + assertNotNull(start) + + val cases = start.allChildren() + assertEquals(4, cases.size) + + val case0 = cases.firstOrNull { (it.caseExpression as? Literal<*>)?.value == 0 } + assertNotNull(case0) + + var stmt = case0.nextEOG.firstOrNull() + assertIs(stmt) + + val case1 = cases.firstOrNull { (it.caseExpression as? Literal<*>)?.value == 1 } + assertNotNull(case1) + + stmt = case1.nextEOG.firstOrNull() + val breakStatement = assertIs(stmt) + assertEquals("start", breakStatement.label) + + val default = start.allChildren().firstOrNull() + assertNotNull(default) + + val end = main.allChildren().firstOrNull { it.label == "end" } + assertNotNull(end) + } + + @Test + fun testDeferStatement() { + val topLevel = Path.of("src", "test", "resources", "golang") + val tu = + analyzeAndGetFirstTU(listOf(topLevel.resolve("defer.go").toFile()), topLevel, true) { + it.registerLanguage() + } + assertNotNull(tu) + + val p = tu.namespaces["p"] + assertNotNull(p) + + val main = p.functions["main"] + assertNotNull(main) + + val op = main.allChildren { it.name.localName == "defer" }.firstOrNull() + assertNotNull(op) + + // The EOG for the defer statement itself should be in the regular EOG path + op.prevEOG.any { it is CallExpression && it.name.localName == "do" } + op.nextEOG.any { it is DeclaredReferenceExpression && it.name.localName == "that" } + + // It should NOT connect to the call expression + op.nextEOG.none { it is CallExpression } + + // Its call expression should connect to the return statement + op.input.prevEOG.all { it is ReturnStatement } + } +} diff --git a/cpg-language-go/src/test/resources/golang/branch.go b/cpg-language-go/src/test/resources/golang/branch.go new file mode 100644 index 0000000000..e0e9e9c12f --- /dev/null +++ b/cpg-language-go/src/test/resources/golang/branch.go @@ -0,0 +1,26 @@ +package p + +import "fmt" + +func main() { + i := 0 + +start: + for { + switch i { + case 0: + continue + case 1: + break start // will break out of the switch and the for-loop + case 2: + fallthrough // will fall through case 3 + case 3: + fmt.Printf("%d", i) + default: + goto end + } + i++ + } + +end: +} diff --git a/cpg-language-go/src/test/resources/golang/defer.go b/cpg-language-go/src/test/resources/golang/defer.go new file mode 100644 index 0000000000..4493840aab --- /dev/null +++ b/cpg-language-go/src/test/resources/golang/defer.go @@ -0,0 +1,28 @@ +package p + +import ( + "fmt" + "os" +) + +func main() { + i := 1 + do() + defer that(i) + + if len(os.Args) == 2 { + i++ + return + } + + i++ + fmt.Println("Still here, yay!") +} + +func do() { + +} + +func that(i int) { + fmt.Println(i) +} diff --git a/cpg-language-go/src/test/resources/log4j2.xml b/cpg-language-go/src/test/resources/log4j2.xml index 747860628a..5b73082e2c 100644 --- a/cpg-language-go/src/test/resources/log4j2.xml +++ b/cpg-language-go/src/test/resources/log4j2.xml @@ -6,7 +6,7 @@ - + From 38d963f327713887a0ddc0d07b92c7d798d35b22 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 5 Aug 2023 18:17:02 +0000 Subject: [PATCH 115/143] Update dependency org.apache.commons:commons-lang3 to v3.13.0 (#1266) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8cd0897f14..627ee42d3c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -17,7 +17,7 @@ log4j-impl = { module = "org.apache.logging.log4j:log4j-slf4j2-impl", version.re log4j-core = { module= "org.apache.logging.log4j:log4j-core", version.ref = "log4j" } jline = { module = "org.jline:jline", version = "3.23.0" } -apache-commons-lang3 = { module = "org.apache.commons:commons-lang3", version = "3.12.0"} +apache-commons-lang3 = { module = "org.apache.commons:commons-lang3", version = "3.13.0"} neo4j-ogm-core = { module = "org.neo4j:neo4j-ogm-core", version.ref = "neo4j"} neo4j-ogm = { module = "org.neo4j:neo4j-ogm", version.ref = "neo4j"} neo4j-ogm-bolt = { module = "org.neo4j:neo4j-ogm-bolt-driver", version.ref = "neo4j"} From 7d720c8c757ceab7361a2b346797e4c04616d035 Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Tue, 8 Aug 2023 09:35:36 +0200 Subject: [PATCH 116/143] Add Program Dependency Graph (#1227) * Try to use a more formal worklist EOG worklist iteration for the CFSensitiveDFG * Add new SplitsControlFlow interface * New unreachable EOG edges pass * Handle AssignExpressions in ControlFlowSensitiveDFGPass * Mark statements which should be irrelevant because the same edges are set in the normal DFGPass * Remove setting unnecessary (already existing) edges from CFSensitiveDFGPass * ConditionalExpression splits control flow * Fix issue 1141 * Fix issue 1141 * Consistency * Rename class and add fields * Generate CDG * Fix problem with very strange EOG self reference of literals * Test if statements and cdg * Add test for foreach loop * Reduce sonar warnings * Fix failing tests * Use HasShortcircuitOperators to generate ShortcircuitOperator objects in Expression Builder * Little fix * Integrate review feedback * More review feedback * Update cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/EOGWorklist.kt Co-authored-by: Christian Banse * Merge two iterateEOG functions * Fluent DSL for loops * Fix redundant stuff * Revert "Fluent DSL for loops" This reverts commit 4ecc621a22005fd03e54996ff80c2cfa1895db3c. * Get rid of "expectedUpdate" * Change method signatures * Small cleanup * Rename lattice to latticeelement * Remove nullable properties * Nicer code * Documentation * first draft for generating pdg property edges * change delegate constructor parameter to KProperty1 for consistency * change PDGEdges properties from using delegates to custom getters * add EventListener to set pdg edges before neo4j serialization * change to adding the PDG edges through a new Pass * fix typos * add test for the ProgramDependenceGraphPass * add more tests * add documentation * fix format violation * remove unused delegate class * rename pass to ProgramDependenceGraphPass and add documentation * rename pdgSetter to visitor * add new edge property DEPENDENCE * use new DEPENDENCE property for PDGEdges and change addAllPrevPDGEdges function to add the PropertyEdge to both nodes which makes addAllNextPDGEdges unnecessary * change test to not rely on hashCode for comparing the lists * Fix problems of merge * change to use DFG PropertyEdges instead of the nodes * change to use sets for storing PDG and adjust PropertyEdgeSetDelegate to use Collection instead of List * fix types in test --------- Co-authored-by: Alexander Kuechler Co-authored-by: KuechA <31155350+KuechA@users.noreply.github.com> Co-authored-by: Christian Banse Co-authored-by: Christian Banse --- .../de/fraunhofer/aisec/cpg/graph/Node.kt | 40 +++- .../aisec/cpg/graph/edge/Properties.kt | 12 +- .../aisec/cpg/graph/edge/PropertyEdge.kt | 4 +- .../cpg/passes/ProgramDependenceGraphPass.kt | 69 +++++++ .../passes/ProgramDependenceGraphPassTest.kt | 184 ++++++++++++++++++ 5 files changed, 301 insertions(+), 8 deletions(-) create mode 100644 cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ProgramDependenceGraphPass.kt create mode 100644 cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ProgramDependenceGraphPassTest.kt diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Node.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Node.kt index 0e3b01f1b8..7de7a6996f 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Node.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Node.kt @@ -35,6 +35,7 @@ import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.TypedefDeclaration +import de.fraunhofer.aisec.cpg.graph.edge.DependenceType import de.fraunhofer.aisec.cpg.graph.edge.Properties import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.unwrap @@ -46,14 +47,11 @@ import de.fraunhofer.aisec.cpg.graph.scopes.Scope import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker import de.fraunhofer.aisec.cpg.helpers.neo4j.LocationConverter import de.fraunhofer.aisec.cpg.helpers.neo4j.NameConverter -import de.fraunhofer.aisec.cpg.passes.ControlDependenceGraphPass -import de.fraunhofer.aisec.cpg.passes.ControlFlowSensitiveDFGPass -import de.fraunhofer.aisec.cpg.passes.DFGPass -import de.fraunhofer.aisec.cpg.passes.EvaluationOrderGraphPass -import de.fraunhofer.aisec.cpg.passes.FilenameMapper +import de.fraunhofer.aisec.cpg.passes.* import de.fraunhofer.aisec.cpg.processing.IVisitable import de.fraunhofer.aisec.cpg.sarif.PhysicalLocation import java.util.* +import kotlin.collections.ArrayList import org.apache.commons.lang3.builder.ToStringBuilder import org.apache.commons.lang3.builder.ToStringStyle import org.neo4j.ogm.annotation.* @@ -208,6 +206,24 @@ open class Node : IVisitable, Persistable, LanguageProvider, ScopeProvider @PopulatedByPass(DFGPass::class, ControlFlowSensitiveDFGPass::class) var nextDFG: MutableSet by PropertyEdgeSetDelegate(Node::nextDFGEdges, true) + /** Outgoing Program Dependence Edges. */ + @PopulatedByPass(ProgramDependenceGraphPass::class) + @Relationship(value = "PDG", direction = Relationship.Direction.OUTGOING) + var nextPDGEdges: MutableSet> = mutableSetOf() + protected set + + /** Virtual property for accessing the children of the Program Dependence Graph (PDG). */ + var nextPDG: MutableSet by PropertyEdgeSetDelegate(Node::nextPDGEdges, true) + + /** Incoming Program Dependence Edges. */ + @PopulatedByPass(ProgramDependenceGraphPass::class) + @Relationship(value = "PDG", direction = Relationship.Direction.INCOMING) + var prevPDGEdges: MutableSet> = mutableSetOf() + protected set + + /** Virtual property for accessing the parents of the Program Dependence Graph (PDG). */ + var prevPDG: MutableSet by PropertyEdgeSetDelegate(Node::prevPDGEdges, false) + var typedefs: MutableSet = HashSet() /** @@ -300,6 +316,20 @@ open class Node : IVisitable, Persistable, LanguageProvider, ScopeProvider prev.forEach { addPrevDFG(it, properties.toMutableMap()) } } + fun addAllPrevPDG(prev: Collection, dependenceType: DependenceType) { + addAllPrevPDGEdges(prev.map { PropertyEdge(it, this) }, dependenceType) + } + + fun addAllPrevPDGEdges(prev: Collection>, dependenceType: DependenceType) { + + prev.forEach { + val edge = PropertyEdge(it).apply { addProperty(Properties.DEPENDENCE, dependenceType) } + this.prevPDGEdges.add(edge) + val other = if (it.start != this) it.start else it.end + other.nextPDGEdges.add(edge) + } + } + fun removePrevDFG(prev: Node?) { if (prev != null) { val thisRemove = diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edge/Properties.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edge/Properties.kt index 5fbb3510b9..80b1c2e74a 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edge/Properties.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edge/Properties.kt @@ -38,6 +38,9 @@ package de.fraunhofer.aisec.cpg.graph.edge * * [UNREACHABLE]:(boolean) True if the edge flows into unreachable code i.e. a branch condition * which is always false. + * + * [DEPENDENCE]: ([DependenceType] Specifies the type of dependence the property edge might + * represent */ enum class Properties { INDEX, @@ -45,5 +48,12 @@ enum class Properties { NAME, INSTANTIATION, UNREACHABLE, - ACCESS + ACCESS, + DEPENDENCE +} + +/** The types of dependencies that might be represented in the CPG */ +enum class DependenceType { + CONTROL, + DATA } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edge/PropertyEdge.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edge/PropertyEdge.kt index 8e74020131..654d450377 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edge/PropertyEdge.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edge/PropertyEdge.kt @@ -377,11 +377,11 @@ class PropertyEdgeDelegate( /** Similar to a [PropertyEdgeDelegate], but with a [Set] instead of [List]. */ @Transient class PropertyEdgeSetDelegate( - val edge: KProperty1>>, + val edge: KProperty1>>, val outgoing: Boolean = true ) { operator fun getValue(thisRef: S, property: KProperty<*>): MutableSet { - return PropertyEdge.unwrap(edge.get(thisRef), outgoing).toMutableSet() + return PropertyEdge.unwrap(edge.get(thisRef).toList(), outgoing).toMutableSet() } operator fun setValue(thisRef: S, property: KProperty<*>, value: MutableSet) { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ProgramDependenceGraphPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ProgramDependenceGraphPass.kt new file mode 100644 index 0000000000..c31d82aeb1 --- /dev/null +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ProgramDependenceGraphPass.kt @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2023, 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.* +import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration +import de.fraunhofer.aisec.cpg.graph.edge.DependenceType +import de.fraunhofer.aisec.cpg.passes.order.DependsOn +import de.fraunhofer.aisec.cpg.processing.IVisitor +import de.fraunhofer.aisec.cpg.processing.strategy.Strategy + +/** + * This pass collects the dependence information of each node into a Program Dependence Graph (PDG) + * by traversing through the AST. + */ +@DependsOn(ControlDependenceGraphPass::class) +@DependsOn(DFGPass::class) +@DependsOn(ControlFlowSensitiveDFGPass::class, softDependency = true) +class ProgramDependenceGraphPass(ctx: TranslationContext) : TranslationUnitPass(ctx) { + private val visitor = + object : IVisitor() { + /** + * Collects the data and control dependence edges of a node and adds them to the program + * dependence edges + */ + override fun visit(t: Node) { + t.addAllPrevPDGEdges(t.prevDFGEdges, DependenceType.DATA) + t.addAllPrevPDGEdges(t.prevCDGEdges, DependenceType.CONTROL) + } + } + + override fun accept(tu: TranslationUnitDeclaration) { + tu.statements.forEach(::handle) + tu.namespaces.forEach(::handle) + tu.declarations.forEach(::handle) + } + + override fun cleanup() { + // Nothing to do + } + + private fun handle(node: Node) { + node.accept(Strategy::AST_FORWARD, visitor) + } +} diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ProgramDependenceGraphPassTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ProgramDependenceGraphPassTest.kt new file mode 100644 index 0000000000..36581aab5c --- /dev/null +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ProgramDependenceGraphPassTest.kt @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2023, 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.* +import de.fraunhofer.aisec.cpg.frontends.TestLanguage +import de.fraunhofer.aisec.cpg.frontends.TestLanguageFrontend +import de.fraunhofer.aisec.cpg.graph.Node +import de.fraunhofer.aisec.cpg.graph.builder.* +import de.fraunhofer.aisec.cpg.graph.edge.DependenceType +import de.fraunhofer.aisec.cpg.graph.edge.Properties +import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge +import de.fraunhofer.aisec.cpg.graph.functions +import de.fraunhofer.aisec.cpg.graph.get +import de.fraunhofer.aisec.cpg.processing.IVisitor +import de.fraunhofer.aisec.cpg.processing.strategy.Strategy +import java.util.* +import java.util.stream.Stream +import kotlin.test.assertNotNull +import kotlin.test.assertTrue +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.MethodSource + +class ProgramDependenceGraphPassTest { + + @ParameterizedTest(name = "test if pdg of {1} is equal to the union of cdg and dfg") + @MethodSource("provideTranslationResultForPDGTest") + fun `test if pdg is equal to union of cdg and dfg`(result: TranslationResult, name: String) { + assertNotNull(result) + val main = result.functions["main"] + assertNotNull(main) + + main.accept( + Strategy::AST_FORWARD, + object : IVisitor() { + override fun visit(t: Node) { + val expectedPrevEdges = + t.prevCDGEdges.map { + it.apply { addProperty(Properties.DEPENDENCE, DependenceType.CONTROL) } + } + + t.prevDFG.map { + PropertyEdge(it, t).apply { + addProperty(Properties.DEPENDENCE, DependenceType.DATA) + } + } + assertTrue( + "prevPDGEdges did not contain all prevCDGEdges and edges to all prevDFG.\n" + + "expectedPrevEdges: ${expectedPrevEdges.sortedBy { it.hashCode() }}\n" + + "actualPrevEdges: ${t.prevPDGEdges.sortedBy { it.hashCode() }}" + ) { + compareCollectionWithoutOrder(expectedPrevEdges, t.prevPDGEdges) + } + + val expectedNextEdges = + t.nextCDGEdges.map { + it.apply { addProperty(Properties.DEPENDENCE, DependenceType.CONTROL) } + } + + t.nextDFG.map { + PropertyEdge(t, it).apply { + addProperty(Properties.DEPENDENCE, DependenceType.DATA) + } + } + assertTrue( + "nextPDGEdges did not contain all nextCDGEdges and edges to all nextDFG." + + "\nexpectedNextEdges: ${expectedNextEdges.sortedBy { it.hashCode() }}" + + "\nactualNextEdges: ${t.nextPDGEdges.sortedBy { it.hashCode() }}" + ) { + compareCollectionWithoutOrder(expectedNextEdges, t.nextPDGEdges) + } + } + } + ) + } + + private fun compareCollectionWithoutOrder( + expected: Collection, + actual: Collection + ): Boolean { + val expectedWithDuplicatesGrouped = expected.groupingBy { it }.eachCount() + val actualWithDuplicatesGrouped = actual.groupingBy { it }.eachCount() + + return expected.size == actual.size && + expectedWithDuplicatesGrouped == actualWithDuplicatesGrouped + } + + companion object { + fun testFrontend(config: TranslationConfiguration): TestLanguageFrontend { + val ctx = TranslationContext(config, ScopeManager(), TypeManager()) + val language = config.languages.filterIsInstance().first() + return TestLanguageFrontend(language.namespaceDelimiter, language, ctx) + } + + @JvmStatic + fun provideTranslationResultForPDGTest() = + Stream.of( + Arguments.of(getIfTest(), "if statement"), + Arguments.of(getWhileLoopTest(), "while loop") + ) + + private fun getIfTest() = + testFrontend( + TranslationConfiguration.builder() + .registerLanguage(TestLanguage("::")) + .defaultPasses() + .registerPass() + .registerPass() + .build() + ) + .build { + translationResult { + translationUnit("if.cpp") { + // The main method + function("main", t("int")) { + body { + declare { variable("i", t("int")) { call("rand") } } + ifStmt { + condition { ref("i") lt literal(0, t("int")) } + thenStmt { + ref("i") assign (ref("i") * literal(-1, t("int"))) + } + } + returnStmt { ref("i") } + } + } + } + } + } + + private fun getWhileLoopTest() = + testFrontend( + TranslationConfiguration.builder() + .registerLanguage(TestLanguage("::")) + .defaultPasses() + .registerPass() + .registerPass() + .build() + ) + .build { + translationResult { + translationUnit("loop.cpp") { + // The main method + function("main", t("int")) { + body { + declare { variable("i", t("int")) { call("rand") } } + whileStmt { + whileCondition { ref("i") gt literal(0, t("int")) } + loopBody { + call("printf") { literal("#", t("string")) } + ref("i").dec() + } + } + call("printf") { literal("\n", t("string")) } + returnStmt { literal(0, t("int")) } + } + } + } + } + } + } +} From b29b02b31764fbd70c008af592f50854dbc94838 Mon Sep 17 00:00:00 2001 From: KuechA <31155350+KuechA@users.noreply.github.com> Date: Wed, 9 Aug 2023 08:32:08 +0200 Subject: [PATCH 117/143] Readme updates (#1273) * Update readme and contributing md * Warning emoji * Also update docs page * Fix sentence --- CONTRIBUTING.md | 4 ++++ README.md | 7 ++++--- docs/docs/Contributing/index.md | 4 ++++ 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3653cf96d5..13e89b56f7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -116,6 +116,10 @@ override fun hashCode() = Objects.hash(super.hashCode(), constructor, arguments) Before we can accept a pull request from you, you'll need to sign a Contributor License Agreement (CLA). It is an automated process and you only need to do it once. +:warning: +We are currently discussing the implementation of a Contributor License Agreement (CLA). Unfortunately, we cannot merge external pull requests until this issue is resolved. +:warning: + To enable us to quickly review and accept your pull requests, always create one pull request per issue and link the issue in the pull request. Never merge multiple requests in one unless they have the same root cause. Be sure your code is formatted correctly using the respective formatting task. Keep code changes as small as possible. diff --git a/README.md b/README.md index 82e20bfb88..4e1eea4d7e 100644 --- a/README.md +++ b/README.md @@ -114,9 +114,10 @@ val translationConfig = TranslationConfiguration ### Experimental Languages -Some languages, such as Golang are experimental and depend on other native libraries. Therefore, they are not included as gradle submodules by default. -To include them as submodules simply toggle them on in your local `gradle.properties` file by setting the value of the properties to `true` e.g., (`enableGoFrontend=true`). -We provide a sample file [here](./gradle.properties.example). +Some languages, such as Golang are experimental and depend on other native libraries. Therefore, they are not included in the `cpg-core` module but have separate gradle submodules. +C/CPP and Java are currently required by some of the modules (e.g. `cpg-analysis`) and thus, disabling them can lead to compile errors! +To include the desired submodules simply toggle them on in your local `gradle.properties` file by setting the value of the properties to `true` e.g., (`enableGoFrontend=true`). +We provide a sample file with all languages switched on [here](./gradle.properties.example). Instead of manually editing the `gradle.properties` file, you can also use the `configure_frontends.sh` script, which edits the properties for you. #### Golang diff --git a/docs/docs/Contributing/index.md b/docs/docs/Contributing/index.md index b072496082..5a908c5f7d 100644 --- a/docs/docs/Contributing/index.md +++ b/docs/docs/Contributing/index.md @@ -126,6 +126,10 @@ override fun hashCode() = Objects.hash(super.hashCode(), constructor, arguments) Before we can accept a pull request from you, you'll need to sign a Contributor License Agreement (CLA). It is an automated process and you only need to do it once. +:warning: +We are currently discussing the implementation of a Contributor License Agreement (CLA). Unfortunately, we cannot merge external pull requests until this issue is resolved. +:warning: + To enable us to quickly review and accept your pull requests, always create one pull request per issue and link the issue in the pull request. Never merge multiple requests in one unless they have the same root cause. Be sure your code is formatted correctly using the respective formatting task. Keep code changes as small as possible. From 8b9b2973565ef03131eda2f4c05ae50a9c78f116 Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Wed, 9 Aug 2023 10:04:09 +0200 Subject: [PATCH 118/143] Overhaul of type propagation (#1268) Co-authored-by: Alexander Kuechler Co-authored-by: Maximilian Kaul --- .../aisec/cpg/analysis/MultiValueEvaluator.kt | 106 +++-- .../aisec/cpg/analysis/ValueEvaluator.kt | 53 ++- .../cpg/analysis/MultiValueEvaluatorTest.kt | 7 +- .../aisec/cpg/analysis/SizeEvaluatorTest.kt | 8 +- .../aisec/cpg/console/Extensions.kt | 2 +- .../de/fraunhofer/aisec/cpg/ScopeManager.kt | 40 +- .../aisec/cpg/TranslationConfiguration.kt | 34 +- .../aisec/cpg/TranslationManager.kt | 15 - .../de/fraunhofer/aisec/cpg/TypeManager.kt | 28 -- .../cpg/frontends/CompilationDatabase.kt | 11 + .../fraunhofer/aisec/cpg/frontends/Handler.kt | 11 + .../aisec/cpg/frontends/Language.kt | 51 ++- .../fraunhofer/aisec/cpg/graph/Assignment.kt | 1 + .../aisec/cpg/graph/ExpressionBuilder.kt | 7 +- .../fraunhofer/aisec/cpg/graph/Extensions.kt | 5 + .../de/fraunhofer/aisec/cpg/graph/HasBase.kt | 4 +- .../aisec/cpg/graph/HasInitializer.kt | 1 + .../aisec/cpg/graph/HasOperatorCode.kt | 35 ++ .../de/fraunhofer/aisec/cpg/graph/Name.kt | 5 +- .../de/fraunhofer/aisec/cpg/graph/Node.kt | 4 +- .../fraunhofer/aisec/cpg/graph/TypeBuilder.kt | 4 + .../aisec/cpg/graph/builder/Fluent.kt | 146 ++++++- .../graph/declarations/FieldDeclaration.kt | 73 +--- .../graph/declarations/FunctionDeclaration.kt | 1 + .../declarations/TypeParamDeclaration.kt | 6 +- .../graph/declarations/ValueDeclaration.kt | 194 ++------- .../graph/declarations/VariableDeclaration.kt | 94 ++--- .../expressions/ArrayCreationExpression.kt | 27 +- .../ArraySubscriptionExpression.kt | 54 ++- .../expressions/AssignExpression.kt | 131 ++++--- .../statements/expressions/BinaryOperator.kt | 143 ++----- .../statements/expressions/CallExpression.kt | 46 +-- .../statements/expressions/CastExpression.kt | 63 +-- .../expressions/ConditionalExpression.kt | 63 ++- .../expressions/ConstructExpression.kt | 44 +-- .../DeclaredReferenceExpression.kt | 103 ++--- .../statements/expressions/Expression.kt | 199 +--------- .../statements/expressions/ExpressionList.kt | 52 +-- .../expressions/InitializerListExpression.kt | 97 +++-- .../expressions/LambdaExpression.kt | 52 +-- .../expressions/MemberCallExpression.kt | 2 +- .../expressions/MemberExpression.kt | 32 +- .../statements/expressions/UnaryOperator.kt | 132 ++----- .../aisec/cpg/graph/types/AutoType.kt | 50 +++ .../aisec/cpg/graph/types/FunctionType.kt | 1 - .../aisec/cpg/graph/types/HasType.kt | 210 ++++++---- .../aisec/cpg/graph/types/ObjectType.kt | 1 - .../cpg/graph/types/SecondaryTypeEdge.kt | 39 ++ .../fraunhofer/aisec/cpg/graph/types/Type.kt | 2 + .../aisec/cpg/graph/types/UnknownType.kt | 6 +- .../aisec/cpg/helpers/SubgraphWalker.kt | 16 - .../aisec/cpg/passes/CXXCallResolverHelper.kt | 45 +-- .../aisec/cpg/passes/CallResolver.kt | 6 +- .../cpg/passes/ControlFlowSensitiveDFGPass.kt | 48 ++- .../de/fraunhofer/aisec/cpg/passes/DFGPass.kt | 9 +- .../aisec/cpg/passes/TypeHierarchyResolver.kt | 3 - .../aisec/cpg/passes/TypeResolver.kt | 32 +- .../aisec/cpg/passes/VariableUsageResolver.kt | 48 ++- .../aisec/cpg/passes/inference/Inference.kt | 57 ++- .../de/fraunhofer/aisec/cpg/GraphExamples.kt | 4 +- .../aisec/cpg/enhancements/InferenceTest.kt | 51 +-- .../fraunhofer/aisec/cpg/graph/FluentTest.kt | 18 +- .../aisec/cpg/graph/ShortcutsTest.kt | 81 ++-- .../expressions/AssignExpressionTest.kt | 65 ++-- .../cpg/graph/types/TypePropagationTest.kt | 368 ++++++++++++++++-- .../aisec/cpg/helpers/SubgraphWalkerTest.kt | 1 - .../de/fraunhofer/aisec/cpg/passes/DFGTest.kt | 4 +- .../de/fraunhofer/aisec/cpg/TestUtils.kt | 1 - .../aisec/cpg/frontends/cxx/CLanguage.kt | 18 +- .../aisec/cpg/frontends/cxx/CPPLanguage.kt | 19 +- .../aisec/cpg/frontends/cxx/CXXHandler.kt | 3 + .../cpg/frontends/cxx/CXXLanguageFrontend.kt | 45 ++- .../cpg/frontends/cxx/DeclarationHandler.kt | 242 +++++++----- .../cpg/frontends/cxx/DeclaratorHandler.kt | 23 +- .../cpg/frontends/cxx/ExpressionHandler.kt | 112 +++--- .../cpg/frontends/cxx/InitializerHandler.kt | 36 +- .../aisec/cpg/PerformanceRegressionTest.kt | 1 - .../enhancements/calls/FunctionPointerTest.kt | 4 +- .../templates/FunctionTemplateTest.kt | 28 +- .../aisec/cpg/enhancements/types/TypeTests.kt | 6 +- .../frontends/cxx/CXXLanguageFrontendTest.kt | 170 ++++---- .../aisec/cpg/frontends/cxx/LambdaTest.kt | 27 +- .../aisec/cpg/helpers/BenchmarkCXXTest.kt | 4 +- .../{ => cxx}/assignmentexpression.cpp | 0 .../resources/{ => cxx}/binaryoperator.cpp | 0 .../{components => cxx}/castexpr.cpp | 0 .../src/test/resources/cxx/fancy_types.cpp | 3 + .../{components => cxx}/foreachstmt.cpp | 0 .../cpg/frontends/golang/ExpressionHandler.kt | 4 +- .../aisec/cpg/frontends/golang/GoHandler.kt | 2 + .../frontends/golang/SpecificationHandler.kt | 2 + .../aisec/cpg/passes/GoExtraPass.kt | 13 +- .../src/test/resources/golang/literal.go | 5 +- .../cpg/frontends/java/ExpressionHandler.kt | 31 +- .../cpg/frontends/java/StatementHandler.kt | 4 +- .../cpg/passes/JavaCallResolverHelper.kt | 16 +- .../java/JavaLanguageFrontendTest.kt | 10 +- .../aisec/cpg/graph/types/TypeTests.kt | 4 +- .../cpg/frontends/llvm/ExpressionHandler.kt | 3 +- .../cpg/frontends/llvm/StatementHandler.kt | 6 +- .../cpg/frontends/python/PythonLanguage.kt | 13 +- .../src/main/python/CPGPython/_expressions.py | 12 +- .../src/main/python/CPGPython/_statements.py | 47 ++- .../frontends/python/PythonFrontendTest.kt | 42 +- .../frontends/typescript/ExpressionHandler.kt | 2 +- .../src/main/nodejs/package-lock.json | 2 +- .../aisec/cpg_vis_neo4j/ApplicationTest.kt | 4 +- 107 files changed, 2229 insertions(+), 2086 deletions(-) create mode 100644 cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/HasOperatorCode.kt create mode 100644 cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/AutoType.kt create mode 100644 cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/SecondaryTypeEdge.kt rename cpg-language-cxx/src/test/resources/{ => cxx}/assignmentexpression.cpp (100%) rename cpg-language-cxx/src/test/resources/{ => cxx}/binaryoperator.cpp (100%) rename cpg-language-cxx/src/test/resources/{components => cxx}/castexpr.cpp (100%) create mode 100644 cpg-language-cxx/src/test/resources/cxx/fancy_types.cpp rename cpg-language-cxx/src/test/resources/{components => cxx}/foreachstmt.cpp (100%) diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluator.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluator.kt index 8760d1e5b2..c649aeecb7 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluator.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluator.kt @@ -28,6 +28,7 @@ package de.fraunhofer.aisec.cpg.analysis import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.declarations.FieldDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration +import de.fraunhofer.aisec.cpg.graph.invoke import de.fraunhofer.aisec.cpg.graph.statements.DeclarationStatement import de.fraunhofer.aisec.cpg.graph.statements.ForStatement import de.fraunhofer.aisec.cpg.graph.statements.expressions.* @@ -78,6 +79,7 @@ class MultiValueEvaluator : ValueEvaluator() { is Literal<*> -> return node.value is DeclaredReferenceExpression -> return handleDeclaredReferenceExpression(node, depth) is UnaryOperator -> return handleUnaryOp(node, depth) + is AssignExpression -> return handleAssignExpression(node, depth) is BinaryOperator -> return handleBinaryOperator(node, depth) // Casts are just a wrapper in this case, we are interested in the inner expression is CastExpression -> return this.evaluateInternal(node.expression, depth + 1) @@ -92,6 +94,48 @@ class MultiValueEvaluator : ValueEvaluator() { return cannotEvaluate(node, this) } + /** + * We are handling some basic arithmetic compound assignment operations and string operations + * that are more or less language-independent. + */ + override fun handleAssignExpression(node: AssignExpression, depth: Int): Any? { + // This only works for compound assignments + if (!node.isCompoundAssignment) { + return super.handleAssignExpression(node, depth) + } + + // Resolve lhs + val lhsValue = evaluateInternal(node.lhs.singleOrNull(), depth + 1) + // Resolve rhs + val rhsValue = evaluateInternal(node.rhs.singleOrNull(), depth + 1) + + if (lhsValue !is Collection<*> && rhsValue !is Collection<*>) { + return computeBinaryOpEffect(lhsValue, rhsValue, node) + } + + val result = mutableSetOf() + if (lhsValue is Collection<*>) { + // lhsValue is a collection. We compute the result for all lhsValues with all the + // rhsValue(s). + for (lhs in lhsValue) { + if (rhsValue is Collection<*>) { + result.addAll(rhsValue.map { r -> computeBinaryOpEffect(lhs, r, node) }) + } else { + result.add(computeBinaryOpEffect(lhs, rhsValue, node)) + } + } + } else { + // lhsValue is not a collection (so rhsValues is because if both wouldn't be a + // collection, this would be covered by the if-statement some lines above). We compute + // the result for the lhsValue with all the rhsValues. + result.addAll( + (rhsValue as Collection<*>).map { r -> computeBinaryOpEffect(lhsValue, r, node) } + ) + } + + return result + } + /** * We are handling some basic arithmetic binary operations and string operations that are more * or less language-independent. @@ -279,60 +323,64 @@ class MultiValueEvaluator : ValueEvaluator() { val loopOp = loop.iterationStatement loopVar = when (loopOp) { - is BinaryOperator -> { + is AssignExpression -> { if ( loopOp.operatorCode == "=" && - (loopOp.lhs as? DeclaredReferenceExpression)?.refersTo == - expr.refersTo && - loopOp.rhs is BinaryOperator + (loopOp.lhs.singleOrNull() as? DeclaredReferenceExpression) + ?.refersTo == expr.refersTo && + loopOp.rhs.singleOrNull() is BinaryOperator ) { // Assignment to the variable, take the rhs and see if it's also a // binary operator val opLhs = if ( - ((loopOp.rhs as BinaryOperator).lhs + ((loopOp.rhs())?.lhs as? DeclaredReferenceExpression) ?.refersTo == expr.refersTo ) { loopVar } else { - (loopOp.rhs as BinaryOperator).lhs + (loopOp.rhs())?.lhs } val opRhs = if ( - ((loopOp.rhs as BinaryOperator).rhs + ((loopOp.rhs())?.rhs as? DeclaredReferenceExpression) ?.refersTo == expr.refersTo ) { loopVar } else { - evaluateInternal((loopOp.rhs as BinaryOperator).rhs, depth + 1) + evaluateInternal((loopOp.rhs())?.rhs, depth + 1) } - computeBinaryOpEffect(opLhs, opRhs, (loopOp.rhs as BinaryOperator)) + computeBinaryOpEffect(opLhs, opRhs, (loopOp.rhs())) as? Number } else { - // No idea what this is but it's a binary op... - val opLhs = - if ( - (loopOp.lhs as? DeclaredReferenceExpression)?.refersTo == - expr.refersTo - ) { - loopVar - } else { - loopOp.lhs - } - val opRhs = - if ( - (loopOp.rhs as? DeclaredReferenceExpression)?.refersTo == - expr.refersTo - ) { - loopVar - } else { - loopOp.rhs - } - computeBinaryOpEffect(opLhs, opRhs, loopOp) as? Number + cannotEvaluate(loopOp, this) } } + is BinaryOperator -> { + + // No idea what this is but it's a binary op... + val opLhs = + if ( + (loopOp.lhs as? DeclaredReferenceExpression)?.refersTo == + expr.refersTo + ) { + loopVar + } else { + loopOp.lhs + } + val opRhs = + if ( + (loopOp.rhs as? DeclaredReferenceExpression)?.refersTo == + expr.refersTo + ) { + loopVar + } else { + loopOp.rhs + } + computeBinaryOpEffect(opLhs, opRhs, loopOp) as? Number + } is UnaryOperator -> { computeUnaryOpEffect( if ( diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluator.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluator.kt index 5e1d27d582..5fa5f49537 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluator.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluator.kt @@ -26,6 +26,7 @@ package de.fraunhofer.aisec.cpg.analysis import de.fraunhofer.aisec.cpg.graph.AccessValues +import de.fraunhofer.aisec.cpg.graph.HasOperatorCode import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration import de.fraunhofer.aisec.cpg.graph.statements.expressions.* @@ -99,7 +100,7 @@ open class ValueEvaluator( // While we are not handling different paths of variables with If statements, we can // easily be partly path-sensitive in a conditional expression is ConditionalExpression -> return handleConditionalExpression(node, depth) - is AssignExpression -> return handleAssignExpression(node) + is AssignExpression -> return handleAssignExpression(node, depth) } // At this point, we cannot evaluate, and we are calling our [cannotEvaluate] hook, maybe @@ -108,8 +109,19 @@ open class ValueEvaluator( } /** Under certain circumstances, an assignment can also be used as an expression. */ - private fun handleAssignExpression(node: AssignExpression): Any? { - if (node.usedAsExpression) { + protected open fun handleAssignExpression(node: AssignExpression, depth: Int): Any? { + // Handle compound assignments. Only possible with single values + val lhs = node.lhs.singleOrNull() + val rhs = node.rhs.singleOrNull() + if (lhs != null && rhs != null && node.isCompoundAssignment) { + // Resolve rhs + val rhsValue = evaluateInternal(rhs, depth + 1) + + // Resolve lhs + val lhsValue = evaluateInternal(lhs, depth + 1) + + return computeBinaryOpEffect(lhsValue, rhsValue, node) + } else if (node.usedAsExpression) { return node.expressionValue } @@ -130,12 +142,19 @@ open class ValueEvaluator( return computeBinaryOpEffect(lhsValue, rhsValue, expr) } + /** + * Computes the effect of basic "binary" operators. + * + * Note: this is both used by a [BinaryOperator] with basic arithmetic operations as well as + * [AssignExpression], if [AssignExpression.isCompoundAssignment] is true. + */ protected fun computeBinaryOpEffect( lhsValue: Any?, rhsValue: Any?, - expr: BinaryOperator + has: HasOperatorCode?, ): Any? { - return when (expr.operatorCode) { + val expr = has as? Expression + return when (has?.operatorCode) { "+", "+=" -> handlePlus(lhsValue, rhsValue, expr) "-", @@ -149,11 +168,11 @@ open class ValueEvaluator( "<" -> handleLess(lhsValue, rhsValue, expr) "<=" -> handleLEq(lhsValue, rhsValue, expr) "==" -> handleEq(lhsValue, rhsValue, expr) - else -> cannotEvaluate(expr, this) + else -> cannotEvaluate(expr as Node, this) } } - private fun handlePlus(lhsValue: Any?, rhsValue: Any?, expr: BinaryOperator): Any? { + private fun handlePlus(lhsValue: Any?, rhsValue: Any?, expr: Expression?): Any? { return when { lhsValue is String -> lhsValue + rhsValue lhsValue is Int && (rhsValue is Double || rhsValue is Float) -> @@ -174,7 +193,7 @@ open class ValueEvaluator( } } - private fun handleMinus(lhsValue: Any?, rhsValue: Any?, expr: BinaryOperator): Any? { + private fun handleMinus(lhsValue: Any?, rhsValue: Any?, expr: Expression?): Any? { return when { lhsValue is Int && (rhsValue is Double || rhsValue is Float) -> lhsValue - (rhsValue as Number).toDouble() @@ -194,7 +213,7 @@ open class ValueEvaluator( } } - private fun handleDiv(lhsValue: Any?, rhsValue: Any?, expr: BinaryOperator): Any? { + private fun handleDiv(lhsValue: Any?, rhsValue: Any?, expr: Expression?): Any? { return when { rhsValue == 0 -> cannotEvaluate(expr, this) lhsValue is Int && (rhsValue is Double || rhsValue is Float) -> @@ -215,7 +234,7 @@ open class ValueEvaluator( } } - private fun handleTimes(lhsValue: Any?, rhsValue: Any?, expr: BinaryOperator): Any? { + private fun handleTimes(lhsValue: Any?, rhsValue: Any?, expr: Expression?): Any? { return when { lhsValue is Int && (rhsValue is Double || rhsValue is Float) -> lhsValue * (rhsValue as Number).toDouble() @@ -235,7 +254,7 @@ open class ValueEvaluator( } } - private fun handleGreater(lhsValue: Any?, rhsValue: Any?, expr: BinaryOperator): Any? { + private fun handleGreater(lhsValue: Any?, rhsValue: Any?, expr: Expression?): Any? { return if (lhsValue is Number && rhsValue is Number) { lhsValue.compareTo(rhsValue) > 0 } else { @@ -243,7 +262,7 @@ open class ValueEvaluator( } } - private fun handleGEq(lhsValue: Any?, rhsValue: Any?, expr: BinaryOperator): Any? { + private fun handleGEq(lhsValue: Any?, rhsValue: Any?, expr: Expression?): Any? { return if (lhsValue is Number && rhsValue is Number) { lhsValue.compareTo(rhsValue) >= 0 } else { @@ -251,7 +270,7 @@ open class ValueEvaluator( } } - private fun handleLess(lhsValue: Any?, rhsValue: Any?, expr: BinaryOperator): Any? { + private fun handleLess(lhsValue: Any?, rhsValue: Any?, expr: Expression?): Any? { return if (lhsValue is Number && rhsValue is Number) { lhsValue.compareTo(rhsValue) < 0 } else { @@ -259,7 +278,7 @@ open class ValueEvaluator( } } - private fun handleLEq(lhsValue: Any?, rhsValue: Any?, expr: BinaryOperator): Any? { + private fun handleLEq(lhsValue: Any?, rhsValue: Any?, expr: Expression?): Any? { return if (lhsValue is Number && rhsValue is Number) { lhsValue.compareTo(rhsValue) <= 0 } else { @@ -267,7 +286,7 @@ open class ValueEvaluator( } } - private fun handleEq(lhsValue: Any?, rhsValue: Any?, expr: BinaryOperator): Any? { + private fun handleEq(lhsValue: Any?, rhsValue: Any?, expr: Expression?): Any? { return if (lhsValue is Number && rhsValue is Number) { lhsValue.compareTo(rhsValue) == 0 } else { @@ -414,14 +433,14 @@ open class ValueEvaluator( // Remove the self reference list = list.filter { - !((it is BinaryOperator && it.lhs == ref) || + !((it is AssignExpression && it.lhs.singleOrNull() == ref) || (it is UnaryOperator && it.input == ref)) } } else if (ref.access == AccessValues.READWRITE && !isCase2) { // Consider only the self reference list = list.filter { - ((it is BinaryOperator && it.lhs == ref) || + ((it is AssignExpression && it.lhs.singleOrNull() == ref) || (it is UnaryOperator && it.input == ref)) } } diff --git a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluatorTest.kt b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluatorTest.kt index f401058bde..099ca6255c 100644 --- a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluatorTest.kt +++ b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluatorTest.kt @@ -33,7 +33,7 @@ import de.fraunhofer.aisec.cpg.graph.evaluate import de.fraunhofer.aisec.cpg.graph.statements.CompoundStatement import de.fraunhofer.aisec.cpg.graph.statements.DeclarationStatement import de.fraunhofer.aisec.cpg.graph.statements.ForStatement -import de.fraunhofer.aisec.cpg.graph.statements.expressions.BinaryOperator +import de.fraunhofer.aisec.cpg.graph.statements.expressions.AssignExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression import de.fraunhofer.aisec.cpg.passes.EdgeCachePass import java.nio.file.Path @@ -211,7 +211,10 @@ class MultiValueEvaluatorTest { assertNotNull(forLoop) val evaluator = MultiValueEvaluator() - val iVar = ((forLoop.statement as CompoundStatement).statements[0] as BinaryOperator).rhs + val iVarList = + ((forLoop.statement as CompoundStatement).statements[0] as AssignExpression).rhs + assertEquals(1, iVarList.size) + val iVar = iVarList.first() val value = evaluator.evaluate(iVar) as ConcreteNumberSet assertEquals(setOf(0, 1, 2, 3, 4, 5), value.values) } diff --git a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/SizeEvaluatorTest.kt b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/SizeEvaluatorTest.kt index c7ee6bb57e..2f470c3695 100644 --- a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/SizeEvaluatorTest.kt +++ b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/SizeEvaluatorTest.kt @@ -31,10 +31,12 @@ import de.fraunhofer.aisec.cpg.graph.bodyOrNull import de.fraunhofer.aisec.cpg.graph.byNameOrNull import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration +import de.fraunhofer.aisec.cpg.graph.invoke import de.fraunhofer.aisec.cpg.graph.statements.CompoundStatement import de.fraunhofer.aisec.cpg.graph.statements.DeclarationStatement import de.fraunhofer.aisec.cpg.graph.statements.ForStatement -import de.fraunhofer.aisec.cpg.graph.statements.expressions.BinaryOperator +import de.fraunhofer.aisec.cpg.graph.statements.expressions.ArraySubscriptionExpression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.AssignExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression import java.nio.file.Path import kotlin.test.Test @@ -105,7 +107,9 @@ class SizeEvaluatorTest { assertNotNull(forLoop) val subscriptExpr = - ((forLoop.statement as CompoundStatement).statements[0] as BinaryOperator).lhs + ((forLoop.statement as CompoundStatement).statements[0] as AssignExpression).lhs< + ArraySubscriptionExpression + >() value = evaluator.evaluate(subscriptExpr) as Int assertEquals(3, value) diff --git a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/console/Extensions.kt b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/console/Extensions.kt index a1c6177590..274b25f3dc 100644 --- a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/console/Extensions.kt +++ b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/console/Extensions.kt @@ -25,7 +25,6 @@ */ package de.fraunhofer.aisec.cpg.console -import de.fraunhofer.aisec.cpg.graph.HasType import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration @@ -33,6 +32,7 @@ import de.fraunhofer.aisec.cpg.graph.statements.CompoundStatement import de.fraunhofer.aisec.cpg.graph.statements.DeclarationStatement import de.fraunhofer.aisec.cpg.graph.statements.IfStatement import de.fraunhofer.aisec.cpg.graph.statements.expressions.* +import de.fraunhofer.aisec.cpg.graph.types.HasType import de.fraunhofer.aisec.cpg.sarif.PhysicalLocation import de.fraunhofer.aisec.cpg.sarif.Region import java.io.File 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 f1a6fed996..7ab83557f5 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 @@ -36,10 +36,7 @@ import de.fraunhofer.aisec.cpg.graph.types.FunctionPointerType import de.fraunhofer.aisec.cpg.graph.types.IncompleteType import de.fraunhofer.aisec.cpg.graph.types.Type import de.fraunhofer.aisec.cpg.helpers.Util -import de.fraunhofer.aisec.cpg.processing.IVisitor -import de.fraunhofer.aisec.cpg.processing.strategy.Strategy import java.util.* -import java.util.concurrent.atomic.AtomicInteger import java.util.function.Predicate import org.slf4j.LoggerFactory @@ -462,7 +459,9 @@ class ScopeManager : ScopeProvider { if (breakStatement.label == null) { val scope = firstScopeOrNull { scope: Scope? -> scope?.isBreakable() == true } if (scope == null) { - LOGGER.error( + Util.errorWithFileLocation( + breakStatement, + LOGGER, "Break inside of unbreakable scope. The break will be ignored, but may lead " + "to an incorrect graph. The source code is not valid or incomplete." ) @@ -605,10 +604,11 @@ class ScopeManager : ScopeProvider { if ( it.name.lastPartsMatch(ref.name) ) { // TODO: This place is likely to make things fail + var helper = ref.resolutionHelper // If the reference seems to point to a function the entire signature is checked // for equality - if (ref.type is FunctionPointerType && it is FunctionDeclaration) { - val fptrType = (ref as HasType).type as FunctionPointerType + if (helper?.type is FunctionPointerType && it is FunctionDeclaration) { + val fptrType = helper.type as FunctionPointerType // TODO(oxisto): This is the third place where function pointers are // resolved. WHY? // TODO(oxisto): Support multiple return values @@ -793,32 +793,4 @@ class ScopeManager : ScopeProvider { /** Returns the current scope for the [ScopeProvider] interface. */ override val scope: Scope? get() = currentScope - - fun activateTypes(node: Node, typeManager: TypeManager) { - val num = AtomicInteger() - val typeCache = typeManager.typeCache - node.accept( - Strategy::AST_FORWARD, - object : IVisitor() { - override fun visit(t: Node) { - if (t is HasType) { - val typeNode = t as HasType - typeCache.getOrDefault(typeNode, emptyList()).forEach { - (t as HasType).type = - typeManager.resolvePossibleTypedef(it, this@ScopeManager) - } - typeCache.remove(t as HasType) - num.getAndIncrement() - } - } - } - ) - LOGGER.debug("Activated {} nodes for {}", num, node.name) - - // For some nodes it may happen that they are not reachable via AST, but we still need to - // set their type to the requested value - typeCache.forEach { (n, types) -> - types.forEach { t -> n.type = typeManager.resolvePossibleTypedef(t, this) } - } - } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationConfiguration.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationConfiguration.kt index f421af0141..2f051ba193 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationConfiguration.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationConfiguration.kt @@ -105,7 +105,6 @@ private constructor( disableCleanup: Boolean, useUnityBuild: Boolean, useParallelFrontends: Boolean, - typeSystemActiveInFrontend: Boolean, inferenceConfiguration: InferenceConfiguration, compilationDatabase: CompilationDatabase?, matchCommentsToNodes: Boolean, @@ -141,13 +140,6 @@ private constructor( */ val useParallelFrontends: Boolean - /** - * If false, the type listener system is only activated once the frontends are done building the - * initial AST structure. This avoids errors where the type of a node may depend on the order in - * which the source files have been parsed. - */ - val typeSystemActiveInFrontend: Boolean - /** * This is the data structure for storing the compilation database. It stores a mapping from the * File to the list of files that have to be included to their path, specified by the parameter @@ -182,7 +174,6 @@ private constructor( this.disableCleanup = disableCleanup this.useUnityBuild = useUnityBuild this.useParallelFrontends = useParallelFrontends - this.typeSystemActiveInFrontend = typeSystemActiveInFrontend this.inferenceConfiguration = inferenceConfiguration this.compilationDatabase = compilationDatabase this.matchCommentsToNodes = matchCommentsToNodes @@ -232,7 +223,6 @@ private constructor( private var disableCleanup = false private var useUnityBuild = false private var useParallelFrontends = false - private var typeSystemActiveInFrontend = true private var inferenceConfiguration = InferenceConfiguration.Builder().build() private var compilationDatabase: CompilationDatabase? = null private var matchCommentsToNodes = false @@ -579,9 +569,7 @@ private constructor( /** * If true, the ASTs for the source files are parsed in parallel, but the passes afterwards * will still run in a single thread. This speeds up initial parsing but makes sure that - * further graph enrichment algorithms remain correct. Please make sure to also set - * [ ][.typeSystemActiveInFrontend] to false to avoid probabilistic errors that appear - * depending on the parsing order. + * further graph enrichment algorithms remain correct. * * @param b the new value */ @@ -590,18 +578,6 @@ private constructor( return this } - /** - * If false, the type system is only activated once the frontends are done building the - * initial AST structure. This avoids errors where the type of a node may depend on the - * order in which the source files have been parsed. - * - * @param b the new value - */ - fun typeSystemActiveInFrontend(b: Boolean): Builder { - typeSystemActiveInFrontend = b - return this - } - fun inferenceConfiguration(configuration: InferenceConfiguration): Builder { inferenceConfiguration = configuration return this @@ -609,13 +585,6 @@ private constructor( @Throws(ConfigurationException::class) fun build(): TranslationConfiguration { - if (useParallelFrontends && typeSystemActiveInFrontend) { - log.warn( - "Not disabling the type system during the frontend " + - "phase is not recommended when using the parallel frontends feature! " + - "This may result in erroneous results." - ) - } registerExtraFrontendPasses() registerReplacedPasses() return TranslationConfiguration( @@ -636,7 +605,6 @@ private constructor( disableCleanup, useUnityBuild, useParallelFrontends, - typeSystemActiveInFrontend, inferenceConfiguration, compilationDatabase, matchCommentsToNodes, diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationManager.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationManager.kt index 9e92b11164..dee1e5ac01 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationManager.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationManager.kt @@ -219,8 +219,6 @@ private constructor( sourceLocations = list } - TypeManager.isTypeSystemActive = ctx.config.typeSystemActiveInFrontend - usedFrontends.addAll( if (useParallelFrontends) { parseParallel(component, result, ctx, sourceLocations) @@ -228,19 +226,6 @@ private constructor( parseSequentially(component, result, ctx, sourceLocations) } ) - - if (!config.typeSystemActiveInFrontend) { - TypeManager.isTypeSystemActive = true - - result.components.forEach { s -> - s.translationUnits.forEach { - val bench = - Benchmark(this.javaClass, "Activating types for ${it.name}", true) - ctx.scopeManager.activateTypes(it, ctx.typeManager) - bench.stop() - } - } - } } return usedFrontends diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TypeManager.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TypeManager.kt index ffed50cca9..0081741eb8 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TypeManager.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TypeManager.kt @@ -45,9 +45,6 @@ import org.apache.commons.lang3.builder.ToStringBuilder import org.slf4j.LoggerFactory class TypeManager { - val typeCache: MutableMap> = - Collections.synchronizedMap(IdentityHashMap()) - private val typeToRecord = Collections.synchronizedMap(HashMap()) /** @@ -213,16 +210,6 @@ class TypeManager { return firstOrderTypes.stream().anyMatch { type: Type -> type.root.name.toString() == name } } - @Synchronized - fun cacheType(node: HasType, type: Type) { - if (!isUnknown(type)) { - val types = typeCache.computeIfAbsent(node) { mutableListOf() } - if (!types.contains(type)) { - types.add(type) - } - } - } - fun isUnknown(type: Type?): Boolean { return type is UnknownType } @@ -240,19 +227,6 @@ class TypeManager { return false } - /** - * @param type oldType that we want to replace - * @param newType newType - * @return true if an objectType with instantiated generics is replaced by the same objectType - * with parameterizedTypes as generics false otherwise - */ - fun stopPropagation(type: Type, newType: Type): Boolean { - return if (type is ObjectType && newType is ObjectType && type.name == newType.name) { - (containsParameterizedType(newType.generics) && - !containsParameterizedType(type.generics)) - } else false - } - private fun rewrapType( type: Type, depth: Int, @@ -637,7 +611,5 @@ class TypeManager { companion object { private val log = LoggerFactory.getLogger(TypeManager::class.java) - - var isTypeSystemActive = true } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/CompilationDatabase.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/CompilationDatabase.kt index 12125cd394..3f67e857a3 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/CompilationDatabase.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/CompilationDatabase.kt @@ -286,10 +286,21 @@ class CompilationDatabase : ArrayList, HandlerInterface>() private val typeOfT: Class<*>? + /** + * This property contains the last node this handler has successfully processed. It is safe to + * call, even when parsing multiple TUs in parallel, since for each TU, a dedicated + * [LanguageFrontend] is spawned, and for each frontend, a dedicated set of [Handler]s is + * created. Within one TU, the processing is sequential in the AST order. + */ + var lastNode: ResultNode? = null + /** * Searches for a handler matching the most specific superclass of [HandlerNode]. The created * map should thus contain a handler for every semantically different AST node and can reuse @@ -131,7 +139,10 @@ abstract class Handler> : Node() { return when (operation.operatorCode) { "+" -> - if (operation.lhs.propagationType is StringType) { + if (operation.lhs.type is StringType) { // string + anything => string - operation.lhs.propagationType - } else if (operation.rhs.propagationType is StringType) { + operation.lhs.type + } else if (operation.rhs.type is StringType) { // anything + string => string - operation.rhs.propagationType + operation.rhs.type } else { - arithmeticOpTypePropagation( - operation.lhs.propagationType, - operation.rhs.propagationType - ) + arithmeticOpTypePropagation(operation.lhs.type, operation.rhs.type) } "-", "*", - "/" -> - arithmeticOpTypePropagation( - operation.lhs.propagationType, - operation.rhs.propagationType - ) + "/" -> arithmeticOpTypePropagation(operation.lhs.type, operation.rhs.type) "<<", ">>" -> - if ( - operation.lhs.propagationType.isPrimitive && - operation.rhs.propagationType.isPrimitive - ) { + if (operation.lhs.type.isPrimitive && operation.rhs.type.isPrimitive) { // primitive type 1 OP primitive type 2 => primitive type 1 - operation.lhs.propagationType + operation.lhs.type } else { unknownType() } else -> unknownType() // We don't know what is this thing } } + + /** + * When propagating [HasType.assignedTypes] from one node to another, we might want to propagate + * only certain types. A common example is to truncate [NumericType]s, when they are not "big" + * enough. + */ + open fun shouldPropagateType(hasType: HasType, srcType: Type): Boolean { + val node = hasType as Node + var nodeType = hasType.type + + // We only want to add certain types, in case we have a numeric type + if (nodeType is NumericType) { + // We do not allow to propagate non-numeric types into numeric types + return if (srcType !is NumericType) { + false + } else { + val srcWidth = srcType.bitWidth + val lhsWidth = nodeType.bitWidth + // Do not propagate anything if the new type is too big for the current type. + return !(lhsWidth != null && srcWidth != null && lhsWidth < srcWidth) + } + } + + return true + } } /** diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Assignment.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Assignment.kt index 038149efd9..2ef8f01c47 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Assignment.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Assignment.kt @@ -28,6 +28,7 @@ package de.fraunhofer.aisec.cpg.graph import com.fasterxml.jackson.annotation.JsonIgnore import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression +import de.fraunhofer.aisec.cpg.graph.types.HasType /** An assignment holder is a node that intentionally contains assignment edges. */ interface AssignmentHolder { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/ExpressionBuilder.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/ExpressionBuilder.kt index 6b401d0521..94eb6f4691 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/ExpressionBuilder.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/ExpressionBuilder.kt @@ -201,10 +201,10 @@ fun MetadataProvider.newConditionalExpression( val node = ConditionalExpression() node.applyMetadata(this, EMPTY_NAME, rawNode, code, true) + node.type = type node.condition = condition node.thenExpr = thenExpr node.elseExpr = elseExpr - node.type = type log(node) return node @@ -531,12 +531,15 @@ fun MetadataProvider.newExpressionList(code: String? = null, rawNode: Any? = nul */ @JvmOverloads fun MetadataProvider.newInitializerListExpression( + targetType: Type = unknownType(), code: String? = null, rawNode: Any? = null ): InitializerListExpression { val node = InitializerListExpression() node.applyMetadata(this, EMPTY_NAME, rawNode, code, true) + node.type = targetType + log(node) return node } @@ -614,10 +617,10 @@ fun Literal.duplicate(implicit: Boolean): Literal { duplicate.language = this.language duplicate.value = this.value duplicate.type = this.type + duplicate.assignedTypes = this.assignedTypes duplicate.code = this.code duplicate.location = this.location duplicate.locals = this.locals - duplicate.possibleSubTypes = this.possibleSubTypes duplicate.argumentIndex = this.argumentIndex duplicate.annotations = this.annotations duplicate.comment = this.comment diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt index a304dbff6e..6c7dbc9f82 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt @@ -572,6 +572,11 @@ val VariableDeclaration.firstAssignment: Expression? ?.value } +/** Returns the [i]-th item in this list (or null) and casts it to [T]. */ +inline operator fun List.invoke(i: Int = 0): T? { + return this.getOrNull(i) as? T +} + operator fun Expression.invoke(): N? { return this as? N } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/HasBase.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/HasBase.kt index 185bc3f22e..d200b24825 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/HasBase.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/HasBase.kt @@ -28,7 +28,7 @@ package de.fraunhofer.aisec.cpg.graph import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression /** Specifies that a certain node has a base on which it executes an operation. */ -interface HasBase { +interface HasBase : HasOperatorCode { /** The base. */ val base: Expression? @@ -37,5 +37,5 @@ interface HasBase { * The operator that is used to access the base. Usually either `.` or `->`, but some languages * offer additional operator codes. */ - val operatorCode: String? + override val operatorCode: String? } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/HasInitializer.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/HasInitializer.kt index 1bcd1ef01b..ff1c5963cf 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/HasInitializer.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/HasInitializer.kt @@ -26,6 +26,7 @@ package de.fraunhofer.aisec.cpg.graph import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression +import de.fraunhofer.aisec.cpg.graph.types.HasType /** * Specifies that a certain node has an initializer. It is a special case of [ArgumentHolder], in diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/HasOperatorCode.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/HasOperatorCode.kt new file mode 100644 index 0000000000..68005bbbc9 --- /dev/null +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/HasOperatorCode.kt @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2023, 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.graph + +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression + +/** A simple interface to denote that the implementing class has some kind of [operatorCode]. */ +interface HasOperatorCode { + + /** The operator code, identifying an operation executed on one or more [Expression]s */ + val operatorCode: String? +} diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Name.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Name.kt index 2e2d52b496..4cb0a1f75c 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Name.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Name.kt @@ -135,10 +135,7 @@ internal fun parseName(fqn: CharSequence, delimiter: String, vararg splitDelimit var name: Name? = null for (part in parts) { - val localName = part.replace(")", "").replace("*", "") - if (localName.isNotEmpty()) { - name = Name(localName, name, delimiter) - } + name = Name(part, name, delimiter) } // Actually this should not occur, but otherwise the compiler won't let us return a diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Node.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Node.kt index 7de7a6996f..62bab149b2 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Node.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Node.kt @@ -294,7 +294,7 @@ open class Node : IVisitable, Persistable, LanguageProvider, ScopeProvider } } - fun addPrevDFG( + open fun addPrevDFG( prev: Node, properties: MutableMap = EnumMap(Properties::class.java) ) { @@ -451,7 +451,7 @@ open class Node : IVisitable, Persistable, LanguageProvider, ScopeProvider companion object { @JvmField var TO_STRING_STYLE: ToStringStyle = ToStringStyle.SHORT_PREFIX_STYLE - protected val log: Logger = LoggerFactory.getLogger(Node::class.java) + @JvmStatic protected val log: Logger = LoggerFactory.getLogger(Node::class.java) const val EMPTY_NAME = "" } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/TypeBuilder.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/TypeBuilder.kt index ca7e9b312c..2e3a70e36a 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/TypeBuilder.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/TypeBuilder.kt @@ -42,6 +42,10 @@ fun MetadataProvider?.unknownType(): Type { } } +fun LanguageProvider.autoType(): Type { + return AutoType(this.language) +} + fun MetadataProvider?.incompleteType(): Type { return IncompleteType() } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt index 24af1fcb35..05ce234380 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt @@ -32,7 +32,9 @@ import de.fraunhofer.aisec.cpg.graph.declarations.* import de.fraunhofer.aisec.cpg.graph.scopes.RecordScope import de.fraunhofer.aisec.cpg.graph.statements.* import de.fraunhofer.aisec.cpg.graph.statements.expressions.* +import de.fraunhofer.aisec.cpg.graph.types.FunctionType import de.fraunhofer.aisec.cpg.graph.types.Type +import de.fraunhofer.aisec.cpg.graph.types.UnknownType import de.fraunhofer.aisec.cpg.passes.executePassSequential fun LanguageFrontend<*, *>.translationResult( @@ -157,6 +159,9 @@ fun LanguageFrontend<*, *>.function( node.returnTypes = listOf(returnType) } + // Make sure that our function has the correct type + node.type = FunctionType.computeType(node) + scopeManager.enterScope(node) init?.let { it(node) } scopeManager.leaveScope(node) @@ -176,13 +181,16 @@ context(RecordDeclaration) fun LanguageFrontend<*, *>.method( name: CharSequence, returnType: Type = unknownType(), - init: MethodDeclaration.() -> Unit + init: (MethodDeclaration.() -> Unit)? = null ): MethodDeclaration { val node = newMethodDeclaration(name) node.returnTypes = listOf(returnType) + node.type = FunctionType.computeType(node) scopeManager.enterScope(node) - init(node) + if (init != null) { + init(node) + } scopeManager.leaveScope(node) scopeManager.addDeclaration(node) @@ -273,6 +281,26 @@ fun LanguageFrontend<*, *>.returnStmt(init: ReturnStatement.() -> Unit): ReturnS return node } +context(Holder) + +fun LanguageFrontend<*, *>.ase( + init: (ArraySubscriptionExpression.() -> Unit)? = null +): ArraySubscriptionExpression { + val node = newArraySubscriptionExpression() + + if (init != null) { + init(node) + } + + // Only add this to an argument holder if the nearest holder is an argument holder + val holder = this@Holder + if (holder is ArgumentHolder) { + holder += node + } + + return node +} + /** * Creates a new [DeclarationStatement] in the Fluent Node DSL and adds it to the * [StatementHolder.statements] of the nearest enclosing [StatementHolder]. The [init] block can be @@ -331,7 +359,7 @@ fun LanguageFrontend<*, *>.call( init: (CallExpression.() -> Unit)? = null ): CallExpression { // Try to parse the name - val parsedName = parseName(name) + val parsedName = parseName(name, ".") val node = if (parsedName.parent != null) { newMemberCallExpression( @@ -422,6 +450,25 @@ fun LanguageFrontend<*, *>.construct( context(Holder) +fun LanguageFrontend<*, *>.cast( + castType: Type, + init: (CastExpression.() -> Unit)? = null +): CastExpression { + val node = newCastExpression() + node.castType = castType + if (init != null) init(node) + + val holder = this@Holder + if (holder is StatementHolder) { + holder += node + } else if (holder is ArgumentHolder) { + holder += node + } + return node +} + +context(Holder) + fun LanguageFrontend<*, *>.new(init: (NewExpression.() -> Unit)? = null): NewExpression { val node = newNewExpression() if (init != null) init(node) @@ -440,9 +487,11 @@ fun LanguageFrontend<*, *>.memberOrRef(name: Name, type: Type = unknownType()): if (name.parent != null) { newMemberExpression(name.localName, memberOrRef(name.parent)) } else { - newDeclaredReferenceExpression(name.localName, objectType(name.localName)) + newDeclaredReferenceExpression(name.localName) } - node.type = type + if (type !is UnknownType) { + node.type = type + } return node } @@ -760,6 +809,32 @@ fun LanguageFrontend<*, *>.literal(value: N, type: Type = unknownType()): Li return node } +/** + * Creates a new [InitializerListExpression] in the Fluent Node DSL and invokes + * [ArgumentHolder.addArgument] of the nearest enclosing [Holder], but only if it is an + * [ArgumentHolder]. + */ +context(Holder) + +fun LanguageFrontend<*, *>.ile( + targetType: Type = unknownType(), + init: (InitializerListExpression.() -> Unit)? = null +): InitializerListExpression { + val node = newInitializerListExpression(targetType) + + if (init != null) { + init(node) + } + + // Only add this to an argument holder if the nearest holder is an argument holder + val holder = this@Holder + if (holder is ArgumentHolder) { + holder += node + } + + return node +} + /** * Creates a new [DeclaredReferenceExpression] in the Fluent Node DSL and invokes * [ArgumentHolder.addArgument] of the nearest enclosing [Holder], but only if it is an @@ -875,10 +950,8 @@ operator fun Expression.plus(rhs: Expression): BinaryOperator { */ context(LanguageFrontend<*, *>, StatementHolder) -operator fun Expression.plusAssign(rhs: Expression): Unit { - val node = (this@LanguageFrontend).newBinaryOperator("+=") - node.lhs = this - node.rhs = rhs +operator fun Expression.plusAssign(rhs: Expression) { + val node = (this@LanguageFrontend).newAssignExpression("+=", listOf(this), listOf(rhs)) (this@StatementHolder) += node } @@ -1022,7 +1095,7 @@ infix fun Expression.lt(rhs: Expression): BinaryOperator { * Creates a new [ConditionalExpression] with a `=` [BinaryOperator.operatorCode] in the Fluent Node * DSL and invokes [StatementHolder.addStatement] of the nearest enclosing [StatementHolder]. */ -context(LanguageFrontend<*, *>, StatementHolder) +context(LanguageFrontend<*, *>, Holder) fun Expression.conditional( condition: Expression, @@ -1031,7 +1104,11 @@ fun Expression.conditional( ): ConditionalExpression { val node = (this@LanguageFrontend).newConditionalExpression(condition, thenExpr, elseExpr) - (this@StatementHolder) += node + if (this@Holder is StatementHolder) { + (this@Holder) += node + } else if (this@Holder is ArgumentHolder) { + this@Holder += node + } return node } @@ -1042,10 +1119,11 @@ fun Expression.conditional( */ context(LanguageFrontend<*, *>, StatementHolder) -infix fun Expression.assign(init: BinaryOperator.() -> Expression): BinaryOperator { - val node = (this@LanguageFrontend).newBinaryOperator("=") - node.lhs = this - node.rhs = init(node) +infix fun Expression.assign(init: AssignExpression.() -> Expression): AssignExpression { + val node = (this@LanguageFrontend).newAssignExpression("=") + node.lhs = listOf(this) + init(node) + // node.rhs = listOf(init(node)) (this@StatementHolder) += node @@ -1053,15 +1131,13 @@ infix fun Expression.assign(init: BinaryOperator.() -> Expression): BinaryOperat } /** - * Creates a new [BinaryOperator] with a `=` [BinaryOperator.operatorCode] in the Fluent Node DSL - * and invokes [StatementHolder.addStatement] of the nearest enclosing [StatementHolder]. + * Creates a new [AssignExpression] with a `=` [AssignExpression.operatorCode] in the Fluent Node + * DSL and invokes [StatementHolder.addStatement] of the nearest enclosing [StatementHolder]. */ context(LanguageFrontend<*, *>, Holder) -infix fun Expression.assign(rhs: Expression): BinaryOperator { - val node = (this@LanguageFrontend).newBinaryOperator("=") - node.lhs = this - node.rhs = rhs +infix fun Expression.assign(rhs: Expression): AssignExpression { + val node = (this@LanguageFrontend).newAssignExpression("=", listOf(this), listOf(rhs)) if (this@Holder is StatementHolder) { this@Holder += node @@ -1070,6 +1146,34 @@ infix fun Expression.assign(rhs: Expression): BinaryOperator { return node } +/** + * Creates a new [AssignExpression] with a `=` [AssignExpression.operatorCode] in the Fluent Node + * DSL and invokes [StatementHolder.addStatement] of the nearest enclosing [StatementHolder]. + */ +context(LanguageFrontend<*, *>, Holder) + +infix fun Expression.assignAsExpr(rhs: Expression): AssignExpression { + val node = (this@LanguageFrontend).newAssignExpression("=", listOf(this), listOf(rhs)) + + node.usedAsExpression = true + + return node +} +/** + * Creates a new [AssignExpression] with a `=` [AssignExpression.operatorCode] in the Fluent Node + * DSL and invokes [StatementHolder.addStatement] of the nearest enclosing [StatementHolder]. + */ +context(LanguageFrontend<*, *>, Holder) + +infix fun Expression.assignAsExpr(rhs: AssignExpression.() -> Unit): AssignExpression { + val node = (this@LanguageFrontend).newAssignExpression("=", listOf(this)) + rhs(node) + + node.usedAsExpression = true + + return node +} + /** Creates a new [Type] with the given [name] in the Fluent Node DSL. */ fun LanguageFrontend<*, *>.t(name: CharSequence, init: (Type.() -> Unit)? = null): Type { val type = objectType(name) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/FieldDeclaration.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/FieldDeclaration.kt index b2f3c985f2..e76b68b601 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/FieldDeclaration.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/FieldDeclaration.kt @@ -27,9 +27,6 @@ package de.fraunhofer.aisec.cpg.graph.declarations import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression -import de.fraunhofer.aisec.cpg.graph.statements.expressions.InitializerListExpression -import de.fraunhofer.aisec.cpg.graph.types.Type -import de.fraunhofer.aisec.cpg.graph.types.UnknownType import java.util.* import org.apache.commons.lang3.builder.ToStringBuilder import org.neo4j.ogm.annotation.Relationship @@ -38,26 +35,7 @@ import org.neo4j.ogm.annotation.Relationship * Declaration of a field within a [RecordDeclaration]. It contains the modifiers associated with * the field as well as an initializer [Expression] which provides an initial value for the field. */ -class FieldDeclaration : ValueDeclaration(), HasType.TypeListener, HasInitializer { - @AST - override var initializer: Expression? = null - set(value) { - if (field != null) { - isDefinition = true - field?.unregisterTypeListener(this) - if (field is HasType.TypeListener) { - unregisterTypeListener(field as HasType.TypeListener) - } - } - field = value - if (value != null) { - value.registerTypeListener(this) - if (value is HasType.TypeListener) { - registerTypeListener(value as HasType.TypeListener) - } - } - } - +class FieldDeclaration : VariableDeclaration() { /** Specifies, whether this field declaration is also a definition, i.e. has an initializer. */ private var isDefinition = false @@ -72,53 +50,8 @@ class FieldDeclaration : ValueDeclaration(), HasType.TypeListener, HasInitialize } } - /** @see VariableDeclaration.implicitInitializerAllowed */ - var isImplicitInitializerAllowed = false - - var isArray = false var modifiers: List = mutableListOf() - override fun typeChanged(src: HasType, root: MutableList, oldType: Type) { - if (!isTypeSystemActive) { - return - } - if (type !is UnknownType && src.propagationType == oldType) { - return - } - val previous = type - val newType = - if (src === initializer && initializer is InitializerListExpression) { - // Init list is seen as having an array type, but can be used ambiguously. It can be - // either used to initialize an array, or to initialize some objects. If it is used - // as an - // array initializer, we need to remove the array/pointer layer from the type, - // otherwise it - // can be ignored once we have a type - if (isArray) { - src.type - } else if (type !is UnknownType) { - return - } else { - src.type.dereference() - } - } else { - src.propagationType - } - setType(newType, root) - if (previous != type) { - type.typeOrigin = Type.Origin.DATAFLOW - } - } - - override fun possibleSubTypesChanged(src: HasType, root: MutableList) { - if (!isTypeSystemActive) { - return - } - val subTypes: MutableList = ArrayList(possibleSubTypes) - subTypes.addAll(src.possibleSubTypes) - setPossibleSubTypes(subTypes, root) - } - override fun toString(): String { return ToStringBuilder(this, TO_STRING_STYLE) .appendSuper(super.toString()) @@ -134,9 +67,7 @@ class FieldDeclaration : ValueDeclaration(), HasType.TypeListener, HasInitialize if (other !is FieldDeclaration) { return false } - return (super.equals(other) && - initializer == other.initializer && - modifiers == other.modifiers) + return (super.equals(other) && modifiers == other.modifiers) } override fun hashCode() = Objects.hash(super.hashCode(), initializer, modifiers) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/FunctionDeclaration.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/FunctionDeclaration.kt index b2adfcd3c4..043737c0a4 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/FunctionDeclaration.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/FunctionDeclaration.kt @@ -34,6 +34,7 @@ import de.fraunhofer.aisec.cpg.graph.statements.CompoundStatement import de.fraunhofer.aisec.cpg.graph.statements.Statement import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression import de.fraunhofer.aisec.cpg.graph.types.Type +import de.fraunhofer.aisec.cpg.graph.types.isSupertypeOf import java.util.* import java.util.stream.Collectors import org.apache.commons.lang3.builder.ToStringBuilder diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/TypeParamDeclaration.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/TypeParamDeclaration.kt index a184b6d1a6..22176fd81e 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/TypeParamDeclaration.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/TypeParamDeclaration.kt @@ -27,7 +27,7 @@ package de.fraunhofer.aisec.cpg.graph.declarations import de.fraunhofer.aisec.cpg.graph.AST import de.fraunhofer.aisec.cpg.graph.HasDefault -import de.fraunhofer.aisec.cpg.graph.HasType.SecondaryTypeEdge +import de.fraunhofer.aisec.cpg.graph.types.SecondaryTypeEdge import de.fraunhofer.aisec.cpg.graph.types.Type import java.util.* import org.neo4j.ogm.annotation.Relationship @@ -37,8 +37,8 @@ class TypeParamDeclaration : ValueDeclaration(), SecondaryTypeEdge, HasDefault() - /** - * The type of this declaration. In order to maximize compatibility with Java legacy code - * (primarily the type listeners), this is a virtual property which wraps around a dedicated - * backing field [_type]. - */ - override var type: Type - get() { - val result: Type = - if (isTypeSystemActive) { - _type - } else { - ctx?.typeManager - ?.typeCache - ?.computeIfAbsent(this) { mutableListOf() } - ?.firstOrNull() - ?: unknownType() - } - return result - } + /** The type of this declaration. */ + override var type: Type = unknownType() set(value) { - // Trigger the type listener foo - setType(value, null) - } + val old = field + field = value + + // Only inform our observer if the type has changed. This should not trigger if we + // "squash" types into one, because they should still be regarded as "equal", but not + // the "same". + if (old != value) { + informObservers(HasType.TypeObserver.ChangeType.TYPE) + } - @Relationship("POSSIBLE_SUB_TYPES") protected var _possibleSubTypes = mutableListOf() - override var possibleSubTypes: List - get() { - return if (!isTypeSystemActive) { - ctx?.typeManager?.typeCache?.getOrDefault(this, emptyList()) ?: listOf() - } else _possibleSubTypes + // We also want to add the definitive type (if known) to our assigned types + if (value !is UnknownType && value !is AutoType) { + addAssignedType(value) + } } + + override var assignedTypes: Set = mutableSetOf() set(value) { - setPossibleSubTypes(value, ArrayList()) - } + if (field == value) { + return + } - @Transient override val typeListeners: MutableSet = HashSet() + field = value + informObservers(HasType.TypeObserver.ChangeType.ASSIGNED_TYPE) + } /** * Links to all the [DeclaredReferenceExpression]s accessing the variable and the respective @@ -118,130 +102,6 @@ abstract class ValueDeclaration : Declaration(), HasType { usageEdges.add(usageEdge) } - /** - * There is no case in which we would want to propagate a referenceType as in this case always - * the underlying ObjectType should be propagated - * - * @return Type that should be propagated - */ - override val propagationType: Type - get() { - return if (type is ReferenceType) { - (type as ReferenceType?)?.elementType ?: unknownType() - } else type - } - - override fun setType(type: Type, root: MutableList?) { - var t: Type = type - var r: MutableList? = root - if (!isTypeSystemActive) { - cacheType(t) - return - } - if (r == null) { - r = ArrayList() - } - if ( - r.contains(this) || - t is UnknownType || - this._type is FunctionPointerType && t !is FunctionPointerType - ) { - return - } - val oldType = this.type - t = t.duplicate() - val subTypes = mutableSetOf() - for (t in possibleSubTypes) { - if (!t.isSimilar(t)) { - subTypes.add(t) - } - } - subTypes.add(t) - this._type = registerType(getCommonType(subTypes).orElse(t)) - val newSubtypes: MutableList = ArrayList() - for (s in subTypes) { - if (isSupertypeOf(this.type, s)) { - newSubtypes.add(registerType(s)) - } - } - possibleSubTypes = newSubtypes - if (oldType == t) { - // Nothing changed, so we do not have to notify the listeners. - return - } - r.add(this) // Add current node to the set of "triggers" to detect potential loops. - // Notify all listeners about the changed type - for (l in typeListeners) { - if (l != this) { - l.typeChanged(this, r, oldType) - } - } - } - - override fun setPossibleSubTypes(possibleSubTypes: List, root: MutableList) { - var list = possibleSubTypes - list = list.filterNot { type -> type is UnknownType }.distinct().toMutableList() - if (!isTypeSystemActive) { - list.forEach { t -> cacheType(t) } - - return - } - if (root.contains(this)) { - return - } - val oldSubTypes = this.possibleSubTypes - this._possibleSubTypes = list - - if (HashSet(oldSubTypes).containsAll(list)) { - // Nothing changed, so we do not have to notify the listeners. - return - } - // Add current node to the set of "triggers" to detect potential loops. - root.add(this) - - // Notify all listeners about the changed type - for (listener in typeListeners) { - if (listener != this) { - listener.possibleSubTypesChanged(this, root) - } - } - } - - override fun resetTypes(type: Type) { - val oldSubTypes = possibleSubTypes - val oldType = this._type - this._type = type - possibleSubTypes = listOf(type) - val root = mutableListOf(this) - if (oldType != type) { - typeListeners - .stream() - .filter { l: HasType.TypeListener -> l != this } - .forEach { l: HasType.TypeListener -> l.typeChanged(this, root, oldType) } - } - if (oldSubTypes.size != 1 || !oldSubTypes.contains(type)) - typeListeners - .stream() - .filter { l: HasType.TypeListener -> l != this } - .forEach { l: HasType.TypeListener -> l.possibleSubTypesChanged(this, root) } - } - - override fun refreshType() { - val root = mutableListOf(this) - for (l in typeListeners) { - l.typeChanged(this, root, type) - l.possibleSubTypesChanged(this, root) - } - } - - override fun updateType(type: Type) { - this._type = type - } - - override fun updatePossibleSubtypes(types: List) { - this._possibleSubTypes = types.toMutableList() - } - override fun toString(): String { return ToStringBuilder(this, TO_STRING_STYLE).appendSuper(super.toString()).toString() } @@ -253,9 +113,7 @@ abstract class ValueDeclaration : Declaration(), HasType { if (other !is ValueDeclaration) { return false } - return (super.equals(other) && - type == other.type && - possibleSubTypes == other.possibleSubTypes) + return (super.equals(other) && type == other.type) } override fun hashCode(): Int { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/VariableDeclaration.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/VariableDeclaration.kt index 3ca71622bb..825a10dab9 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/VariableDeclaration.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/VariableDeclaration.kt @@ -26,19 +26,21 @@ package de.fraunhofer.aisec.cpg.graph.declarations import de.fraunhofer.aisec.cpg.graph.* +import de.fraunhofer.aisec.cpg.graph.statements.expressions.ConstructExpression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression -import de.fraunhofer.aisec.cpg.graph.statements.expressions.InitializerListExpression +import de.fraunhofer.aisec.cpg.graph.types.AutoType +import de.fraunhofer.aisec.cpg.graph.types.HasType import de.fraunhofer.aisec.cpg.graph.types.Type -import de.fraunhofer.aisec.cpg.graph.types.UnknownType import org.apache.commons.lang3.builder.ToStringBuilder import org.neo4j.ogm.annotation.Relationship -/** Represents the declaration of a variable. */ -class VariableDeclaration : ValueDeclaration(), HasType.TypeListener, HasInitializer { +/** Represents the declaration of a local variable. */ +open class VariableDeclaration : ValueDeclaration(), HasInitializer, HasType.TypeObserver { /** - * We need a way to store the templateParameters that a VariableDeclaration might have before - * the ConstructExpression is created. + * We need a way to store the templateParameters that a [VariableDeclaration] might have before + * the [ConstructExpression] is created. * * Because templates are only used by a small subset of languages and variable declarations are * used often, we intentionally make this a nullable list instead of an empty list. @@ -62,65 +64,18 @@ class VariableDeclaration : ValueDeclaration(), HasType.TypeListener, HasInitial @AST override var initializer: Expression? = null set(value) { - field?.unregisterTypeListener(this) - if (field is HasType.TypeListener) { - unregisterTypeListener(field as HasType.TypeListener) - } + field?.unregisterTypeObserver(this) field = value - value?.registerTypeListener(this) - - // if the initializer implements a type listener, inform it about our type changes - // since the type is tied to the declaration, but it is convenient to have the type - // information in the initializer, i.e. in a ConstructExpression. - if (value is HasType.TypeListener) { - registerTypeListener(value as HasType.TypeListener) + if (value is DeclaredReferenceExpression) { + value.resolutionHelper = this } + value?.registerTypeObserver(this) } fun getInitializerAs(clazz: Class): T? { return clazz.cast(initializer) } - override fun typeChanged(src: HasType, root: MutableList, oldType: Type) { - if (!isTypeSystemActive) { - return - } - if (type !is UnknownType && src.propagationType == oldType) { - return - } - val previous = type - val newType = - if (src === initializer && initializer is InitializerListExpression) { - // Init list is seen as having an array type, but can be used ambiguously. It can be - // either used to initialize an array, or to initialize some objects. If it is used - // as an - // array initializer, we need to remove the array/pointer layer from the type, - // otherwise it can be ignored once we have a type - if (isArray) { - src.type - } else if (type !is UnknownType) { - return - } else { - src.type.dereference() - } - } else { - src.propagationType - } - setType(newType, root) - if (previous != type) { - type.typeOrigin = Type.Origin.DATAFLOW - } - } - - override fun possibleSubTypesChanged(src: HasType, root: MutableList) { - if (!isTypeSystemActive) { - return - } - val subTypes: MutableList = ArrayList(possibleSubTypes) - subTypes.addAll(src.possibleSubTypes) - setPossibleSubTypes(subTypes, root) - } - override fun toString(): String { return ToStringBuilder(this, TO_STRING_STYLE) .append("name", name) @@ -129,10 +84,29 @@ class VariableDeclaration : ValueDeclaration(), HasType.TypeListener, HasInitial .toString() } - override val assignments: List - get() { - return initializer?.let { listOf(Assignment(it, this, this)) } ?: listOf() + override fun typeChanged(newType: Type, src: HasType) { + // Only accept type changes from our initializer, if any + if (src != initializer) { + return + } + + // In the auto-inference case, we want to set the type of our declaration to the + // declared type of the initializer + if (this.type is AutoType) { + type = newType + } else { + // Otherwise, we are at least interested in what the initializer's type is, to see + // whether we can fill our assigned types with that + addAssignedType(newType) + } + } + + override fun assignedTypeChanged(assignedTypes: Set, src: HasType) { + // Propagate the assigned types from our initializer into the declaration + if (src == initializer) { + addAssignedTypes(assignedTypes) } + } override fun equals(other: Any?): Boolean { if (this === other) { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ArrayCreationExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ArrayCreationExpression.kt index 258a9e9251..dcf37a3c17 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ArrayCreationExpression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ArrayCreationExpression.kt @@ -26,13 +26,10 @@ package de.fraunhofer.aisec.cpg.graph.statements.expressions import de.fraunhofer.aisec.cpg.graph.AST -import de.fraunhofer.aisec.cpg.graph.HasType import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.propertyEqualsList import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdgeDelegate -import de.fraunhofer.aisec.cpg.graph.isTypeSystemActive -import de.fraunhofer.aisec.cpg.graph.types.Type import java.util.* import org.neo4j.ogm.annotation.Relationship @@ -40,7 +37,7 @@ import org.neo4j.ogm.annotation.Relationship * Expressions of the form `new Type[]` that represents the creation of an array, mostly used in * combination with a [VariableDeclaration]. */ -class ArrayCreationExpression : Expression(), HasType.TypeListener { +class ArrayCreationExpression : Expression() { /** * The initializer of the expression, if present. Many languages, such as Java, either specify * [dimensions] or an initializer. @@ -48,9 +45,7 @@ class ArrayCreationExpression : Expression(), HasType.TypeListener { @AST var initializer: Expression? = null set(value) { - field?.unregisterTypeListener(this) field = value - value?.registerTypeListener(this) } /** @@ -80,24 +75,4 @@ class ArrayCreationExpression : Expression(), HasType.TypeListener { } override fun hashCode() = Objects.hash(super.hashCode(), initializer, dimensions) - - override fun typeChanged(src: HasType, root: MutableList, oldType: Type) { - if (!isTypeSystemActive) { - return - } - val previous = type - setType(src.propagationType, root) - if (previous != type) { - type.typeOrigin = Type.Origin.DATAFLOW - } - } - - override fun possibleSubTypesChanged(src: HasType, root: MutableList) { - if (!isTypeSystemActive) { - return - } - val subTypes: MutableList = ArrayList(possibleSubTypes) - subTypes.addAll(src.possibleSubTypes) - setPossibleSubTypes(subTypes, root) - } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ArraySubscriptionExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ArraySubscriptionExpression.kt index 7cc3d05d6c..7206864f2c 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ArraySubscriptionExpression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ArraySubscriptionExpression.kt @@ -26,16 +26,16 @@ package de.fraunhofer.aisec.cpg.graph.statements.expressions import de.fraunhofer.aisec.cpg.graph.* +import de.fraunhofer.aisec.cpg.graph.types.HasType import de.fraunhofer.aisec.cpg.graph.types.Type import java.util.* -import java.util.stream.Collectors /** * Represents the subscription or access of an array of the form `array[index]`, where both `array` * ([arrayExpression]) and `index` ([subscriptExpression]) are of type [Expression]. CPP can * overload operators thus changing semantics of array access. */ -class ArraySubscriptionExpression : Expression(), HasType.TypeListener, HasBase { +class ArraySubscriptionExpression : Expression(), HasBase, HasType.TypeObserver, ArgumentHolder { /** * The array on which the access is happening. This is most likely a * [DeclaredReferenceExpression]. @@ -43,9 +43,10 @@ class ArraySubscriptionExpression : Expression(), HasType.TypeListener, HasBase @AST var arrayExpression: Expression = ProblemExpression("could not parse array expression") set(value) { + field.unregisterTypeObserver(this) field = value type = getSubscriptType(value.type) - value.registerTypeListener(this) + value.registerTypeObserver(this) } /** @@ -75,29 +76,42 @@ class ArraySubscriptionExpression : Expression(), HasType.TypeListener, HasBase } } - override fun typeChanged(src: HasType, root: MutableList, oldType: Type) { - if (!isTypeSystemActive) { + override fun typeChanged(newType: Type, src: HasType) { + // Make sure the source is really our array + if (src != arrayExpression) { return } - val previous = type - setType(getSubscriptType(src.propagationType), root) - if (previous != type) { - type.typeOrigin = Type.Origin.DATAFLOW - } + + this.type = getSubscriptType(newType) } - override fun possibleSubTypesChanged(src: HasType, root: MutableList) { - if (!isTypeSystemActive) { + override fun assignedTypeChanged(assignedTypes: Set, src: HasType) { + // Make sure the source is really our array + if (src != arrayExpression) { return } - val subTypes: MutableList = ArrayList(possibleSubTypes) - subTypes.addAll( - src.possibleSubTypes - .stream() - .map { arrayType: Type -> getSubscriptType(arrayType) } - .collect(Collectors.toList()) - ) - setPossibleSubTypes(subTypes, root) + + addAssignedTypes(assignedTypes.map { getSubscriptType(it) }.toSet()) + } + + override fun addArgument(expression: Expression) { + if (arrayExpression is ProblemExpression) { + arrayExpression = expression + } else if (subscriptExpression is ProblemExpression) { + subscriptExpression = expression + } + } + + override fun replaceArgument(old: Expression, new: Expression): Boolean { + return if (arrayExpression == old) { + arrayExpression = new + true + } else if (subscriptExpression == old) { + subscriptExpression = new + true + } else { + false + } } override fun equals(other: Any?): Boolean { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/AssignExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/AssignExpression.kt index e240f0e67e..d48ebec724 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/AssignExpression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/AssignExpression.kt @@ -27,6 +27,7 @@ package de.fraunhofer.aisec.cpg.graph.statements.expressions import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration +import de.fraunhofer.aisec.cpg.graph.types.HasType import de.fraunhofer.aisec.cpg.graph.types.TupleType import de.fraunhofer.aisec.cpg.graph.types.Type import org.slf4j.Logger @@ -49,34 +50,38 @@ import org.slf4j.LoggerFactory * [usedAsExpression]. When this property is set to true (it defaults to false), we model a dataflow * from the (first) rhs to the [AssignExpression] itself. */ -class AssignExpression : Expression(), AssignmentHolder, HasType.TypeListener { +class AssignExpression : + Expression(), AssignmentHolder, ArgumentHolder, HasType.TypeObserver, HasOperatorCode { - var operatorCode: String = "=" - - @AST var lhs: List = listOf() + override var operatorCode: String = "=" @AST - var rhs: List = listOf() + var lhs: List = listOf() set(value) { - // Unregister any old type listeners - field.forEach { it.unregisterTypeListener(this) } field = value - // Register this statement as a type listener for each expression - value.forEach { - it.registerTypeListener(this) - - if (it is DeclaredReferenceExpression) { - it.access = AccessValues.WRITE + if (operatorCode == "=") { + field.forEach { (it as? DeclaredReferenceExpression)?.access = AccessValues.WRITE } + } else { + field.forEach { + (it as? DeclaredReferenceExpression)?.access = AccessValues.READWRITE } } } + @AST + var rhs: List = listOf() + set(value) { + field.forEach { it.unregisterTypeObserver(this) } + field = value + value.forEach { it.registerTypeObserver(this) } + } + /** * This property specifies, that this is actually used as an expression. Not many languages * support that. In the regular case, an assignment is a simple statement and does not hold any * value itself. */ - val usedAsExpression = false + var usedAsExpression = false /** * If this node is used an expression, this property contains a reference of the [Expression] @@ -94,7 +99,7 @@ class AssignExpression : Expression(), AssignmentHolder, HasType.TypeListener { /** * We also support compound assignments in this class, but only if the appropriate compound - * operator is set and only if there is a single-value expression on both side. + * operator is set and only if there is a single-value expression on both sides. */ val isCompoundAssignment: Boolean get() { @@ -112,47 +117,6 @@ class AssignExpression : Expression(), AssignmentHolder, HasType.TypeListener { */ override var declarations = mutableListOf() - override fun typeChanged(src: HasType, root: MutableList, oldType: Type) { - if (!isTypeSystemActive) { - return - } - - val type = src.type - - // There are now two possibilities: Either, we have a tuple type, that we need to - // deconstruct, or we have a singular type - if (type is TupleType) { - val targets = findTargets(src) - if (targets.size == type.types.size) { - // Set the corresponding type on the left-side - type.types.forEachIndexed { idx, t -> lhs.getOrNull(idx)?.type = t } - } - } else { - findTargets(src).forEach { it.type = src.propagationType } - } - - // If this is used as an expression, we also set the type accordingly - if (usedAsExpression) { - expressionValue?.propagationType?.let { setType(it, root) } - } - } - - override fun possibleSubTypesChanged(src: HasType, root: MutableList) { - if (!isTypeSystemActive) { - return - } - - // Basically, we need to find out which index on the rhs this variable belongs to and set - // the corresponding subtypes on the lhs. - val idx = rhs.indexOf(src) - if (idx == -1) { - return - } - - // Set the subtypes - lhs.getOrNull(idx)?.setPossibleSubTypes(src.possibleSubTypes, root) - } - /** Finds the value (of [rhs]) that is assigned to the particular [lhs] expression. */ fun findValue(lhsExpr: HasType): Expression? { return if (lhs.size > 1) { @@ -211,4 +175,59 @@ class AssignExpression : Expression(), AssignmentHolder, HasType.TypeListener { companion object { private val log: Logger = LoggerFactory.getLogger(Node::class.java) } + + override fun typeChanged(newType: Type, src: HasType) { + // Double-check, if the src is really from the rhs + if (!rhs.contains(src)) { + return + } + + // There are now two possibilities: Either, we have a tuple type, that we need to + // deconstruct, or we have a singular type. Now, its getting tricky. We do NOT want + // to propagate the type to the declared type, but only to the "assigned" type + if (newType is TupleType) { + val targets = findTargets(src) + if (targets.size == newType.types.size) { + // Set the corresponding type on the left-side + newType.types.forEachIndexed { idx, t -> lhs.getOrNull(idx)?.addAssignedType(t) } + } + } else { + findTargets(src).forEach { it.addAssignedType(newType) } + } + + // If this is used as an expression, we also set the type accordingly + if (usedAsExpression) { + expressionValue?.type?.let { type = it } + } + } + + override fun assignedTypeChanged(assignedTypes: Set, src: HasType) { + // Double-check, if the src is really from the rhs + if (!rhs.contains(src)) { + return + } + + // Propagate any assigned types from the source to the target + findTargets(src).forEach { it.addAssignedTypes(assignedTypes) } + } + + override fun addArgument(expression: Expression) { + if (lhs.isEmpty()) { + lhs = listOf(expression) + } else { + rhs = listOf(expression) + } + } + + override fun replaceArgument(old: Expression, new: Expression): Boolean { + return if (lhs == listOf(old)) { + lhs = listOf(new) + true + } else if (rhs == listOf(old)) { + rhs = listOf(new) + true + } else { + false + } + } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/BinaryOperator.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/BinaryOperator.kt index 7c81f0b426..dad65f97ee 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/BinaryOperator.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/BinaryOperator.kt @@ -25,20 +25,21 @@ */ package de.fraunhofer.aisec.cpg.graph.statements.expressions +import de.fraunhofer.aisec.cpg.frontends.TranslationException import de.fraunhofer.aisec.cpg.graph.* -import de.fraunhofer.aisec.cpg.graph.types.NumericType -import de.fraunhofer.aisec.cpg.graph.types.StringType +import de.fraunhofer.aisec.cpg.graph.types.HasType import de.fraunhofer.aisec.cpg.graph.types.Type -import de.fraunhofer.aisec.cpg.graph.types.UnknownType import java.util.* import org.apache.commons.lang3.builder.ToStringBuilder /** * A binary operation expression, such as "a + b". It consists of a left hand expression (lhs), a * right hand expression (rhs) and an operatorCode. + * + * Note: For assignments, i.e., using an `=` or `+=`, etc. the [AssignExpression] MUST be used. */ open class BinaryOperator : - Expression(), HasType.TypeListener, AssignmentHolder, HasBase, ArgumentHolder { + Expression(), HasBase, HasOperatorCode, ArgumentHolder, HasType.TypeObserver { /** The left-hand expression. */ @AST var lhs: Expression = ProblemExpression("could not parse lhs") @@ -56,6 +57,7 @@ open class BinaryOperator : field = value connectNewRhs(value) } + /** The operator code. */ override var operatorCode: String? = null set(value) { @@ -64,8 +66,8 @@ open class BinaryOperator : (operatorCode in (language?.compoundAssignmentOperators ?: setOf())) || (operatorCode == "=") ) { - NodeBuilder.LOGGER.warn( - "Creating a BinaryOperator with an assignment operator code is deprecated. The class AssignExpression should be used instead." + throw TranslationException( + "Creating a BinaryOperator with an assignment operator code is not allowed. The class AssignExpression should be used instead." ) } } @@ -75,35 +77,21 @@ open class BinaryOperator : } private fun connectNewLhs(lhs: Expression) { - lhs.registerTypeListener(this) - if ("=" == operatorCode) { - if (lhs is DeclaredReferenceExpression) { - // declared reference expr is the left-hand side of an assignment -> writing to the - // var - lhs.access = AccessValues.WRITE - } - if (lhs is HasType.TypeListener) { - registerTypeListener(lhs as HasType.TypeListener) - registerTypeListener(this.lhs as HasType.TypeListener) - } - } else if (operatorCode in (language?.compoundAssignmentOperators ?: setOf())) { - if (lhs is DeclaredReferenceExpression) { - // declared reference expr is the left-hand side of an assignment -> writing to the - // var - lhs.access = AccessValues.READWRITE - } - if (lhs is HasType.TypeListener) { - registerTypeListener(lhs as HasType.TypeListener) - registerTypeListener(this.lhs as HasType.TypeListener) - } + lhs.registerTypeObserver(this) + if (lhs is DeclaredReferenceExpression && "=" == operatorCode) { + // declared reference expr is the left-hand side of an assignment -> writing to the var + lhs.access = AccessValues.WRITE + } else if ( + lhs is DeclaredReferenceExpression && + operatorCode in (language?.compoundAssignmentOperators ?: setOf()) + ) { + // declared reference expr is the left-hand side of an assignment -> writing to the var + lhs.access = AccessValues.READWRITE } } private fun disconnectOldLhs() { - lhs.unregisterTypeListener(this) - if ("=" == operatorCode && lhs is HasType.TypeListener) { - unregisterTypeListener(lhs as HasType.TypeListener) - } + lhs.unregisterTypeObserver(this) } fun getRhsAs(clazz: Class): T? { @@ -111,65 +99,11 @@ open class BinaryOperator : } private fun connectNewRhs(rhs: Expression) { - rhs.registerTypeListener(this) - if ("=" == operatorCode && rhs is HasType.TypeListener) { - registerTypeListener(rhs as HasType.TypeListener) - } + rhs.registerTypeObserver(this) } private fun disconnectOldRhs() { - rhs.unregisterTypeListener(this) - if ("=" == operatorCode && rhs is HasType.TypeListener) { - unregisterTypeListener(rhs as HasType.TypeListener) - } - } - - override fun typeChanged(src: HasType, root: MutableList, oldType: Type) { - if (!isTypeSystemActive) { - return - } - val previous = type - if (operatorCode == "=") { - val srcWidth = (src.type as? NumericType)?.bitWidth - val lhsWidth = (lhs.type as? NumericType)?.bitWidth - if (src == rhs && lhsWidth != null && srcWidth != null && lhsWidth < srcWidth) { - // Do not propagate anything if the new type is too big for the current type. - return - } - setType(src.propagationType, root) - } else if ( - operatorCode == "+" && - (lhs.propagationType is StringType || rhs.propagationType is StringType) - ) { - // String + any other type results in a String - _possibleSubTypes.clear() // TODO: Why do we clear the list here? - val stringType = - if (lhs.propagationType is StringType) lhs.propagationType else rhs.propagationType - setType(stringType, root) - } else if (operatorCode == ".*" || operatorCode == "->*" && src === rhs) { - // Propagate the function pointer type to the expression itself. This helps us later in - // the call resolver, when trying to determine, whether this is a regular call or a - // function pointer call. - setType(src.propagationType, root) - } else { - val resultingType = language?.propagateTypeOfBinaryOperation(this) ?: unknownType() - if (resultingType !is UnknownType) { - setType(resultingType, root) - } - } - - if (previous != type) { - type.typeOrigin = Type.Origin.DATAFLOW - } - } - - override fun possibleSubTypesChanged(src: HasType, root: MutableList) { - if (!isTypeSystemActive) { - return - } - val subTypes: MutableList = ArrayList(possibleSubTypes) - subTypes.addAll(src.possibleSubTypes) - setPossibleSubTypes(subTypes, root) + rhs.unregisterTypeObserver(this) } override fun toString(): String { @@ -180,15 +114,29 @@ open class BinaryOperator : .toString() } - @Deprecated("BinaryOperator should not be used for assignments anymore") - override val assignments: List - get() { - return if (isAssignment) { - listOf(Assignment(rhs, lhs, this)) + override fun typeChanged(newType: Type, src: HasType) { + // We need to do some special dealings for function pointer calls + if (operatorCode == ".*" || operatorCode == "->*" && src === rhs) { + // Propagate the function pointer type to the expression itself. This helps us later in + // the call resolver, when trying to determine, whether this is a regular call or a + // function pointer call. + this.type = newType + } else { + // Otherwise, we have a special language-specific function to deal with type propagation + val type = language?.propagateTypeOfBinaryOperation(this) + if (type != null) { + this.type = type } else { - listOf() + // If we don't know how to propagate the types of this particular binary operation, + // we just leave the type alone. We cannot take newType because it is just "half" of + // the operation (either from lhs or rhs) and would lead to very incorrect results. } } + } + + override fun assignedTypeChanged(assignedTypes: Set, src: HasType) { + // TODO: replicate something similar like propagateTypeOfBinaryOperation for assigned types + } override fun equals(other: Any?): Boolean { if (this === other) { @@ -205,15 +153,6 @@ open class BinaryOperator : override fun hashCode() = Objects.hash(super.hashCode(), lhs, rhs, operatorCode) - private val isAssignment: Boolean - get() { - // TODO(oxisto): We need to discuss, if the other operators are also assignments and if - // we really want them - return this.operatorCode.equals("=") - /*||this.operatorCode.equals("+=") ||this.operatorCode.equals("-=") - ||this.operatorCode.equals("/=") ||this.operatorCode.equals("*=")*/ - } - override fun addArgument(expression: Expression) { if (lhs is ProblemExpression) { lhs = expression diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/CallExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/CallExpression.kt index 83d77fab03..93d0e0fc5a 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/CallExpression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/CallExpression.kt @@ -27,7 +27,6 @@ package de.fraunhofer.aisec.cpg.graph.statements.expressions import de.fraunhofer.aisec.cpg.PopulatedByPass import de.fraunhofer.aisec.cpg.graph.* -import de.fraunhofer.aisec.cpg.graph.HasType.SecondaryTypeEdge import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.TemplateDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.TemplateDeclaration.TemplateInitialization @@ -36,9 +35,8 @@ import de.fraunhofer.aisec.cpg.graph.edge.Properties import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.propertyEqualsList import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.transformIntoOutgoingPropertyEdgeList import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.unwrap -import de.fraunhofer.aisec.cpg.graph.types.FunctionPointerType -import de.fraunhofer.aisec.cpg.graph.types.TupleType -import de.fraunhofer.aisec.cpg.graph.types.Type +import de.fraunhofer.aisec.cpg.graph.types.* +import de.fraunhofer.aisec.cpg.graph.types.SecondaryTypeEdge import de.fraunhofer.aisec.cpg.passes.CallResolver import de.fraunhofer.aisec.cpg.passes.VariableUsageResolver import java.util.* @@ -49,8 +47,11 @@ import org.neo4j.ogm.annotation.Relationship * An expression, which calls another function. It has a list of arguments (list of [Expression]s) * and is connected via the INVOKES edge to its [FunctionDeclaration]. */ -open class CallExpression : Expression(), HasType.TypeListener, SecondaryTypeEdge, ArgumentHolder { - /** Connection to its [FunctionDeclaration]. This will be populated by the [CallResolver]. */ +open class CallExpression : Expression(), HasType.TypeObserver, SecondaryTypeEdge, ArgumentHolder { + /** + * Connection to its [FunctionDeclaration]. This will be populated by the [CallResolver]. This + * will have an effect on the [type] + */ @PopulatedByPass(CallResolver::class) @Relationship(value = "INVOKES", direction = Relationship.Direction.OUTGOING) var invokeEdges = mutableListOf>() @@ -70,9 +71,9 @@ open class CallExpression : Expression(), HasType.TypeListener, SecondaryTypeEdg return Collections.unmodifiableList(targets) } set(value) { - unwrap(invokeEdges).forEach { it.unregisterTypeListener(this) } + unwrap(invokeEdges).forEach { it.unregisterTypeObserver(this) } invokeEdges = transformIntoOutgoingPropertyEdgeList(value, this) - value.forEach { it.registerTypeListener(this) } + value.forEach { it.registerTypeObserver(this) } } /** @@ -265,18 +266,14 @@ open class CallExpression : Expression(), HasType.TypeListener, SecondaryTypeEdg return templateInstantiation != null || templateParameterEdges != null || template } - override fun typeChanged(src: HasType, root: MutableList, oldType: Type) { - if (!isTypeSystemActive) { - return - } - + override fun typeChanged(newType: Type, src: HasType) { // If this is a template, we need to ignore incoming type changes, because our template // system will explicitly set the type if (this.template) { return } - val previous = type + // TODO(oxisto): We could actually use the newType (which is a FunctionType now) val types = invokeEdges.map(PropertyEdge::end).mapNotNull { if (it.returnTypes.size == 1) { @@ -288,25 +285,12 @@ open class CallExpression : Expression(), HasType.TypeListener, SecondaryTypeEdg } val alternative = if (types.isNotEmpty()) types[0] else unknownType() val commonType = getCommonType(types).orElse(alternative) - val subTypes: MutableList = ArrayList(possibleSubTypes) - - subTypes.remove(oldType) - subTypes.addAll(types) - setType(commonType, root) - setPossibleSubTypes(subTypes, root) - if (previous != type) { - type.typeOrigin = Type.Origin.DATAFLOW - } - } - override fun possibleSubTypesChanged(src: HasType, root: MutableList) { - if (!isTypeSystemActive) { - return - } + this.type = commonType + } - val subTypes: MutableList = ArrayList(possibleSubTypes) - subTypes.addAll(src.possibleSubTypes) - setPossibleSubTypes(subTypes, root) + override fun assignedTypeChanged(assignedTypes: Set, src: HasType) { + // Nothing to do } override fun toString(): String { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/CastExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/CastExpression.kt index e2073c202d..ca8b592b8f 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/CastExpression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/CastExpression.kt @@ -26,13 +26,19 @@ package de.fraunhofer.aisec.cpg.graph.statements.expressions import de.fraunhofer.aisec.cpg.graph.* +import de.fraunhofer.aisec.cpg.graph.types.HasType import de.fraunhofer.aisec.cpg.graph.types.Type import java.util.* -import kotlin.collections.ArrayList import org.slf4j.LoggerFactory -class CastExpression : Expression(), HasType.TypeListener { - @AST var expression: Expression = ProblemExpression("could not parse inner expression") +class CastExpression : Expression(), ArgumentHolder, HasType.TypeObserver { + @AST + var expression: Expression = ProblemExpression("could not parse inner expression") + set(value) { + field.unregisterTypeObserver(this) + field = value + value.registerTypeObserver(this) + } var castType: Type = unknownType() set(value) { @@ -40,33 +46,6 @@ class CastExpression : Expression(), HasType.TypeListener { type = value } - override fun updateType(type: Type) { - super.updateType(type) - castType = type - } - - override fun typeChanged(src: HasType, root: MutableList, oldType: Type) { - if (!isTypeSystemActive) { - return - } - val previous = type - if (isSupertypeOf(castType, src.propagationType)) { - setType(src.propagationType, root) - } else { - resetTypes(castType) - } - if (previous != type) { - type.typeOrigin = Type.Origin.DATAFLOW - } - } - - override fun possibleSubTypesChanged(src: HasType, root: MutableList) { - if (!isTypeSystemActive) { - return - } - setPossibleSubTypes(ArrayList(src.possibleSubTypes), root) - } - fun setCastOperator(operatorCode: Int) { var localName: String? = null when (operatorCode) { @@ -82,6 +61,30 @@ class CastExpression : Expression(), HasType.TypeListener { } } + override fun addArgument(expression: Expression) { + this.expression = expression + } + + override fun replaceArgument(old: Expression, new: Expression): Boolean { + if (this.expression == old) { + this.expression = new + return true + } + + return false + } + + override fun typeChanged(newType: Type, src: HasType) { + // Nothing to do, the cast type always stays the same + } + + override fun assignedTypeChanged(assignedTypes: Set, src: HasType) { + // We want to propagate the assigned types, if they come from our expression + if (src == expression) { + addAssignedTypes(assignedTypes) + } + } + override fun equals(other: Any?): Boolean { if (this === other) { return true diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ConditionalExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ConditionalExpression.kt index 6fc9049a9f..0374329164 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ConditionalExpression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ConditionalExpression.kt @@ -26,8 +26,9 @@ package de.fraunhofer.aisec.cpg.graph.statements.expressions import de.fraunhofer.aisec.cpg.graph.* +import de.fraunhofer.aisec.cpg.graph.types.HasType import de.fraunhofer.aisec.cpg.graph.types.Type -import java.util.ArrayList +import de.fraunhofer.aisec.cpg.graph.types.getCommonType import java.util.Objects import org.apache.commons.lang3.builder.ToStringBuilder @@ -35,55 +36,25 @@ import org.apache.commons.lang3.builder.ToStringBuilder * Represents an expression containing a ternary operator: `var x = condition ? valueIfTrue : * valueIfFalse`; */ -class ConditionalExpression : Expression(), HasType.TypeListener, ArgumentHolder, BranchingNode { +class ConditionalExpression : Expression(), ArgumentHolder, BranchingNode, HasType.TypeObserver { @AST var condition: Expression = ProblemExpression("could not parse condition expression") @AST var thenExpr: Expression? = null set(value) { - field?.unregisterTypeListener(this) + field?.unregisterTypeObserver(this) field = value - value?.registerTypeListener(this) + value?.registerTypeObserver(this) } @AST var elseExpr: Expression? = null set(value) { - field?.unregisterTypeListener(this) + field?.unregisterTypeObserver(this) field = value - value?.registerTypeListener(this) + value?.registerTypeObserver(this) } - override fun typeChanged(src: HasType, root: MutableList, oldType: Type) { - if (!isTypeSystemActive) { - return - } - val previous = type - val types: MutableList = ArrayList() - - thenExpr?.propagationType?.let { types.add(it) } - elseExpr?.propagationType?.let { types.add(it) } - - val subTypes: MutableList = ArrayList(possibleSubTypes) - subTypes.remove(oldType) - subTypes.addAll(types) - val alternative = if (types.isNotEmpty()) types[0] else unknownType() - setType(getCommonType(types).orElse(alternative), root) - setPossibleSubTypes(subTypes, root) - if (previous != type) { - type.typeOrigin = Type.Origin.DATAFLOW - } - } - - override fun possibleSubTypesChanged(src: HasType, root: MutableList) { - if (!isTypeSystemActive) { - return - } - val subTypes: MutableList = ArrayList(possibleSubTypes) - subTypes.addAll(src.possibleSubTypes) - possibleSubTypes = subTypes - } - override fun toString(): String { return ToStringBuilder(this, TO_STRING_STYLE) .appendSuper(super.toString()) @@ -105,6 +76,26 @@ class ConditionalExpression : Expression(), HasType.TypeListener, ArgumentHolder return false } + override fun typeChanged(newType: Type, src: HasType) { + val types = mutableListOf() + + thenExpr?.type?.let { types.add(it) } + elseExpr?.type?.let { types.add(it) } + + val alternative = if (types.isNotEmpty()) types[0] else unknownType() + this.type = getCommonType(types).orElse(alternative) + } + + override fun assignedTypeChanged(assignedTypes: Set, src: HasType) { + // Merge and propagate the assigned types of our branches + if (src == thenExpr || src == elseExpr) { + val types = mutableSetOf() + thenExpr?.assignedTypes?.let { types.addAll(it) } + elseExpr?.assignedTypes?.let { types.addAll(it) } + addAssignedTypes(types) + } + } + override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is ConditionalExpression) return false diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ConstructExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ConstructExpression.kt index 47c9884711..6c3e413591 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ConstructExpression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ConstructExpression.kt @@ -28,8 +28,6 @@ package de.fraunhofer.aisec.cpg.graph.statements.expressions import de.fraunhofer.aisec.cpg.PopulatedByPass import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.declarations.* -import de.fraunhofer.aisec.cpg.graph.types.FunctionType -import de.fraunhofer.aisec.cpg.graph.types.Type import de.fraunhofer.aisec.cpg.graph.types.UnknownType import de.fraunhofer.aisec.cpg.passes.CallResolver import java.util.* @@ -41,7 +39,7 @@ import org.apache.commons.lang3.builder.ToStringBuilder * as part of a [NewExpression]. * * In Java, it is the initializer of a [NewExpression]. */ -class ConstructExpression : CallExpression(), HasType.TypeListener { +class ConstructExpression : CallExpression() { /** * The link to the [ConstructorDeclaration]. This is populated by the * [de.fraunhofer.aisec.cpg.passes.CallResolver] later. @@ -81,46 +79,6 @@ class ConstructExpression : CallExpression(), HasType.TypeListener { } } - /** - * This function implements the [HasType.TypeListener] interface. We need to be really careful - * about type changes in the [ConstructExpression]. The problem is, that usually, a - * [VariableDeclaration] is registered as a type listener for its initializer, e.g, to infer the - * type of the variable declaration based on its literal initializer. BUT, if the initializer - * also implements [HasType.TypeListener], as does [ConstructExpression], the initializer is - * also registered as a type listener for the declaration. The reason for that is primary - * stemming from the way the C++ AST works where we need to get information about `Integer - * i(4)`, in which the `Integer` type is only available to the declaration AST element and `(4)` - * which is the [ConstructExpression] does not have the type information. - * - * Furthermore, there is a second source of type listening events coming from the [CallResolver] - * , more specifically, if [CallExpression.invokes] is set. In this case, the call target, i.e., - * the [ConstructorDeclaration] invokes this function here. We have to differentiate between - * those two, because in the second case we are not interested in the full - * [FunctionDeclaration.type] that propagates this change (which is a [FunctionType], but only - * its [FunctionDeclaration.returnTypes]. This is already handled by - * [CallExpression.typeChanged], so we can just delegate to that. - * - * In fact, we could get rid of this particular implementation altogether, if we would somehow - * work around the first case in a different way. - */ - override fun typeChanged(src: HasType, root: MutableList, oldType: Type) { - if (!isTypeSystemActive) { - return - } - - // In the second case (see above), the src is always a function declaration, so we can - // delegate this to our parent. - if (src is FunctionDeclaration) { - return super.typeChanged(src, root, oldType) - } - - val previous: Type = this.type - setType(src.propagationType, root) - if (previous != this.type) { - this.type.typeOrigin = Type.Origin.DATAFLOW - } - } - override fun toString(): String { return ToStringBuilder(this, TO_STRING_STYLE) .appendSuper(super.toString()) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/DeclaredReferenceExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/DeclaredReferenceExpression.kt index 10859e88de..0a7b589939 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/DeclaredReferenceExpression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/DeclaredReferenceExpression.kt @@ -27,39 +27,38 @@ package de.fraunhofer.aisec.cpg.graph.statements.expressions import de.fraunhofer.aisec.cpg.PopulatedByPass import de.fraunhofer.aisec.cpg.graph.AccessValues -import de.fraunhofer.aisec.cpg.graph.HasType +import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.declarations.Declaration import de.fraunhofer.aisec.cpg.graph.declarations.ValueDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration -import de.fraunhofer.aisec.cpg.graph.isTypeSystemActive +import de.fraunhofer.aisec.cpg.graph.edge.Properties +import de.fraunhofer.aisec.cpg.graph.types.HasType import de.fraunhofer.aisec.cpg.graph.types.Type import de.fraunhofer.aisec.cpg.passes.VariableUsageResolver import java.util.* -import kotlin.collections.ArrayList import org.apache.commons.lang3.builder.ToStringBuilder import org.neo4j.ogm.annotation.Relationship /** * An expression, which refers to something which is declared, e.g. a variable. For example, the - * expression `a = b`, which itself is a [BinaryOperator], contains two [ ]s, one for the variable - * `a` and one for variable `b ` * , which have been previously been declared. + * expression `a = b`, which itself is an [AssignExpression], contains two + * [DeclaredReferenceExpression]s, one for the variable `a` and one for variable `b`, which have + * been previously been declared. */ -open class DeclaredReferenceExpression : Expression(), HasType.TypeListener { - /** The [Declaration]s this expression might refer to. */ +open class DeclaredReferenceExpression : Expression(), HasType.TypeObserver { + /** + * The [Declaration]s this expression might refer to. This will influence the [declaredType] of + * this expression. + */ @PopulatedByPass(VariableUsageResolver::class) @Relationship(value = "REFERS_TO") var refersTo: Declaration? = null set(value) { val current = field - // unregister type listeners for current declaration - if (current != null) { - if (current is ValueDeclaration) { - current.unregisterTypeListener(this) - } - if (current is HasType.TypeListener) { - unregisterTypeListener(current as HasType.TypeListener) - } + // unregister type observers for current declaration + if (current != null && current is HasType) { + current.unregisterTypeObserver(this) } // set it @@ -68,12 +67,9 @@ open class DeclaredReferenceExpression : Expression(), HasType.TypeListener { value.addUsage(this) } - // update type listeners - if (field is ValueDeclaration) { - (field as ValueDeclaration).registerTypeListener(this) - } - if (field is HasType.TypeListener) { - registerTypeListener(field as HasType.TypeListener) + // Register ourselves to get type updates from the declaration + if (value is HasType) { + value.registerTypeObserver(this) } } // set the access @@ -85,7 +81,14 @@ open class DeclaredReferenceExpression : Expression(), HasType.TypeListener { var isStaticAccess = false /** - * Returns the contents of [.refersTo] as the specified class, if the class is assignable. + * This is a MAJOR workaround needed to resolve function pointers, until we properly re-design + * the call resolver. When this [DeclaredReferenceExpression] contains a function pointer + * reference that is assigned to a variable (or to another reference), we need to set + */ + var resolutionHelper: HasType? = null + + /** + * Returns the contents of [refersTo] as the specified class, if the class is assignable. * Otherwise, it will return null. * * @param clazz the expected class @@ -99,30 +102,6 @@ open class DeclaredReferenceExpression : Expression(), HasType.TypeListener { else null } - override fun typeChanged(src: HasType, root: MutableList, oldType: Type) { - if (!isTypeSystemActive) { - return - } - val previous = type - setType(src.propagationType, root) - if (previous != type) { - type.typeOrigin = Type.Origin.DATAFLOW - } - } - - override fun possibleSubTypesChanged(src: HasType, root: MutableList) { - if (!isTypeSystemActive) { - return - } - - // since we want to update the sub types, we need to exclude ourselves from the root, - // otherwise it won't work. What a weird and broken system! - root.remove(this) - val subTypes: MutableList = ArrayList(possibleSubTypes) - subTypes.addAll(src.possibleSubTypes) - setPossibleSubTypes(subTypes, root) - } - override fun toString(): String { return ToStringBuilder(this, TO_STRING_STYLE) .append(super.toString()) @@ -130,6 +109,27 @@ open class DeclaredReferenceExpression : Expression(), HasType.TypeListener { .toString() } + override fun typeChanged(newType: Type, src: HasType) { + // Make sure that the update comes from our declaration, if we change our declared type + if (src == refersTo) { + // Set our type + this.type = newType + } + } + + override fun assignedTypeChanged(assignedTypes: Set, src: HasType) { + // Make sure that the update comes from our declaration, if we change our assigned types + if (src == refersTo) { + // Set our type + this.addAssignedTypes(assignedTypes) + } + + // We also allow updates from our previous DFG nodes + if (prevDFG.contains(src as Node)) { + this.addAssignedTypes(assignedTypes) + } + } + override fun equals(other: Any?): Boolean { if (this === other) { return true @@ -140,5 +140,16 @@ open class DeclaredReferenceExpression : Expression(), HasType.TypeListener { return super.equals(other) && refersTo == other.refersTo } + override fun addPrevDFG(prev: Node, properties: MutableMap) { + super.addPrevDFG(prev, properties) + + // We want to propagate assigned types all through the previous DFG nodes. Therefore, we + // override the DFG adding function here and add a type observer to the previous node (if it + // is not ourselves) + if (prev != this && prev is HasType) { + prev.registerTypeObserver(this) + } + } + override fun hashCode(): Int = Objects.hash(super.hashCode(), refersTo) } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/Expression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/Expression.kt index a690bf68fc..db1e6563ab 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/Expression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/Expression.kt @@ -27,13 +27,8 @@ package de.fraunhofer.aisec.cpg.graph.statements.expressions import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.statements.Statement -import de.fraunhofer.aisec.cpg.graph.types.FunctionPointerType -import de.fraunhofer.aisec.cpg.graph.types.ReferenceType -import de.fraunhofer.aisec.cpg.graph.types.Type -import de.fraunhofer.aisec.cpg.graph.types.UnknownType -import java.util.* +import de.fraunhofer.aisec.cpg.graph.types.* import org.apache.commons.lang3.builder.ToStringBuilder -import org.neo4j.ogm.annotation.Relationship import org.neo4j.ogm.annotation.Transient /** @@ -48,185 +43,35 @@ import org.neo4j.ogm.annotation.Transient *

This is not possible in Java, the aforementioned code example would prompt a compile error. */ abstract class Expression : Statement(), HasType { + @Transient override val typeObservers = mutableListOf() - @Relationship("TYPE") private var _type: Type = unknownType() - - /** The type of the value after evaluation. */ - override var type: Type - get() { - val result: Type = - if (isTypeSystemActive) { - _type - } else { - ctx?.typeManager - ?.typeCache - ?.computeIfAbsent(this) { mutableListOf() } - ?.firstOrNull() - ?: unknownType() - } - return result - } - set(value) { - // Trigger the type listener foo - setType(value, null) - } - - @Relationship("POSSIBLE_SUB_TYPES") protected var _possibleSubTypes = mutableListOf() - - override var possibleSubTypes: List - get() { - return if (!isTypeSystemActive) { - ctx?.typeManager?.typeCache?.getOrDefault(this, emptyList()) ?: listOf() - } else _possibleSubTypes - } + override var type: Type = unknownType() set(value) { - setPossibleSubTypes(value, ArrayList()) - } - - @Transient override val typeListeners: MutableSet = HashSet() - - override val propagationType: Type - get() { - return if (type is ReferenceType) { - (type as ReferenceType?)?.elementType ?: unknownType() - } else type - } - - @Override - override fun setType(type: Type, root: MutableList?) { - var t: Type = type - var r: MutableList? = root - - // TODO Document this method. It is called very often (potentially for each AST node) and - // performs less than optimal. - if (!isTypeSystemActive) { - this._type = t - cacheType(t) - return - } - - if (r == null) { - r = mutableListOf() - } - - // No (or only unknown) type given, loop detected? Stop early because there's nothing we can - // do. - if ( - r.contains(this) || - t is UnknownType || - stopPropagation(this.type, t) || - (this.type is FunctionPointerType && t !is FunctionPointerType) - ) { - return - } - - val oldType = this.type - // Backup to check if something changed - - t = t.duplicate() - val subTypes = mutableSetOf() - - // Check all current subtypes and consider only those which are "different enough" to type. - for (s in possibleSubTypes) { - if (!s.isSimilar(s)) { - subTypes.add(s) - } - } - - subTypes.add(t) - - // Probably tries to get something like the best supertype of all possible subtypes. - this._type = registerType(getCommonType(subTypes).orElse(t)) - - // TODO: Why do we need this loop? Shouldn't the condition be ensured by the previous line - // getting the common type?? - val newSubtypes = mutableListOf() - for (s in subTypes) { - if (isSupertypeOf(this.type, s)) { - newSubtypes.add(registerType(s)) + val old = field + field = value + + // Only inform our observer if the type has changed. This should not trigger if we + // "squash" types into one, because they should still be regarded as "equal", but not + // the "same". + if (old != value) { + informObservers(HasType.TypeObserver.ChangeType.TYPE) } - } - - possibleSubTypes = newSubtypes - - if (oldType == t) { - // Nothing changed, so we do not have to notify the listeners. - return - } - // Add current node to the set of "triggers" to detect potential loops. - r.add(this) - - // Notify all listeners about the changed type - for (l in typeListeners) { - if (l != this) { - l.typeChanged(this, r, oldType) + // We also want to add the definitive type (if known) to our assigned types + if (value !is UnknownType && value !is AutoType) { + addAssignedType(value) } } - } - - override fun setPossibleSubTypes(possibleSubTypes: List, root: MutableList) { - var list = possibleSubTypes - list = list.filterNot { type -> type is UnknownType }.distinct().toMutableList() - if (!isTypeSystemActive) { - list.forEach { t -> cacheType(t) } - return - } - if (root.contains(this)) { - return - } - val oldSubTypes = this.possibleSubTypes - this._possibleSubTypes = list - - if (HashSet(oldSubTypes).containsAll(list)) { - // Nothing changed, so we do not have to notify the listeners. - return - } - // Add current node to the set of "triggers" to detect potential loops. - root.add(this) - // Notify all listeners about the changed type - for (listener in typeListeners) { - if (listener != this) { - listener.possibleSubTypesChanged(this, root) + override var assignedTypes: Set = mutableSetOf() + set(value) { + if (field == value) { + return } - } - } - - override fun resetTypes(type: Type) { - val oldSubTypes = possibleSubTypes - val oldType = this._type - this._type = type - possibleSubTypes = listOf(type) - val root = mutableListOf(this) - if (oldType != type) { - typeListeners - .stream() - .filter { l: HasType.TypeListener -> l != this } - .forEach { l: HasType.TypeListener -> l.typeChanged(this, root, oldType) } - } - if (oldSubTypes.size != 1 || !oldSubTypes.contains(type)) - typeListeners - .stream() - .filter { l: HasType.TypeListener -> l != this } - .forEach { l: HasType.TypeListener -> l.possibleSubTypesChanged(this, root) } - } - override fun refreshType() { - val root = mutableListOf(this) - for (l in typeListeners) { - l.typeChanged(this, root, type) - l.possibleSubTypesChanged(this, root) + field = value + informObservers(HasType.TypeObserver.ChangeType.ASSIGNED_TYPE) } - } - - override fun updateType(type: Type) { - this._type = type - } - - override fun updatePossibleSubtypes(types: List) { - this._possibleSubTypes = types.toMutableList() - } override fun toString(): String { return ToStringBuilder(this, TO_STRING_STYLE) @@ -242,9 +87,7 @@ abstract class Expression : Statement(), HasType { if (other !is Expression) { return false } - return (super.equals(other) && - type == other.type && - possibleSubTypes == other.possibleSubTypes) + return (super.equals(other) && type == other.type) } override fun hashCode(): Int { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ExpressionList.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ExpressionList.kt index a82869e10d..28de83ce55 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ExpressionList.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ExpressionList.kt @@ -26,72 +26,26 @@ package de.fraunhofer.aisec.cpg.graph.statements.expressions import de.fraunhofer.aisec.cpg.graph.AST -import de.fraunhofer.aisec.cpg.graph.HasType import de.fraunhofer.aisec.cpg.graph.edge.Properties import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.propertyEqualsList -import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.transformIntoOutgoingPropertyEdgeList -import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.unwrap -import de.fraunhofer.aisec.cpg.graph.isTypeSystemActive +import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdgeDelegate import de.fraunhofer.aisec.cpg.graph.statements.Statement -import de.fraunhofer.aisec.cpg.graph.types.Type import java.util.* import kotlin.collections.ArrayList import org.neo4j.ogm.annotation.Relationship -class ExpressionList : Expression(), HasType.TypeListener { +class ExpressionList : Expression() { @Relationship(value = "SUBEXPR", direction = Relationship.Direction.OUTGOING) @AST var expressionEdges: MutableList> = ArrayList() - var expressions: List - get() { - return unwrap(expressionEdges) - } - set(value) { - if (this.expressionEdges.isNotEmpty()) { - val lastExpression = this.expressionEdges[this.expressionEdges.size - 1].end - if (lastExpression is HasType) - (lastExpression as HasType).unregisterTypeListener(this) - } - this.expressionEdges = transformIntoOutgoingPropertyEdgeList(value, this) - if (this.expressionEdges.isNotEmpty()) { - val lastExpression = this.expressionEdges[this.expressionEdges.size - 1].end - if (lastExpression is HasType) - (lastExpression as HasType).registerTypeListener(this) - } - } + var expressions: List by PropertyEdgeDelegate(ExpressionList::expressionEdges, true) fun addExpression(expression: Statement) { - if (expressionEdges.isNotEmpty()) { - val lastExpression = expressionEdges[expressionEdges.size - 1].end - if (lastExpression is HasType) (lastExpression as HasType).unregisterTypeListener(this) - } val propertyEdge = PropertyEdge(this, expression) propertyEdge.addProperty(Properties.INDEX, expressionEdges.size) expressionEdges.add(propertyEdge) - if (expression is HasType) { - (expression as HasType).registerTypeListener(this) - } - } - - override fun typeChanged(src: HasType, root: MutableList, oldType: Type) { - if (!isTypeSystemActive) { - return - } - val previous = type - setType(src.propagationType, root) - setPossibleSubTypes(ArrayList(src.possibleSubTypes), root) - if (previous != type) { - type.typeOrigin = Type.Origin.DATAFLOW - } - } - - override fun possibleSubTypesChanged(src: HasType, root: MutableList) { - if (!isTypeSystemActive) { - return - } - setPossibleSubTypes(ArrayList(src.possibleSubTypes), root) } override fun equals(other: Any?): Boolean { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/InitializerListExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/InitializerListExpression.kt index 69f7858c1c..c937cc65bb 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/InitializerListExpression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/InitializerListExpression.kt @@ -29,74 +29,33 @@ import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.propertyEqualsList import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdgeDelegate -import de.fraunhofer.aisec.cpg.graph.types.PointerType.PointerOrigin +import de.fraunhofer.aisec.cpg.graph.types.HasType +import de.fraunhofer.aisec.cpg.graph.types.PointerType import de.fraunhofer.aisec.cpg.graph.types.Type -import de.fraunhofer.aisec.cpg.graph.types.UnknownType import java.util.* import org.apache.commons.lang3.builder.ToStringBuilder import org.neo4j.ogm.annotation.Relationship -/** A list of initializer expressions. */ -class InitializerListExpression : Expression(), HasType.TypeListener { +/** + * This node represents the initialization of an "aggregate" object, such as an array or a struct or + * object. The actual use can greatly differ by the individual language frontends. In order to be as + * accurate as possible when propagating types, the [InitializerListExpression.type] property MUST + * be set before adding any values to [InitializerListExpression.initializers]. + */ +class InitializerListExpression : Expression(), ArgumentHolder, HasType.TypeObserver { /** The list of initializers. */ @Relationship(value = "INITIALIZERS", direction = Relationship.Direction.OUTGOING) @AST var initializerEdges = mutableListOf>() set(value) { - field.forEach { - it.end.unregisterTypeListener(this) - removePrevDFG(it.end) - } + field.forEach { it.end.unregisterTypeObserver(this) } field = value - value.forEach { - it.end.registerTypeListener(this) - addPrevDFG(it.end) - } + value.forEach { it.end.registerTypeObserver(this) } } /** Virtual property to access [initializerEdges] without property edges. */ var initializers by PropertyEdgeDelegate(InitializerListExpression::initializerEdges) - override fun typeChanged(src: HasType, root: MutableList, oldType: Type) { - if (!isTypeSystemActive) { - return - } - if (type !is UnknownType && src.propagationType == oldType) { - return - } - val previous = type - val newType: Type - val subTypes: MutableList - if (initializers.contains(src)) { - val types = - initializers.map { registerType(it.type.reference(PointerOrigin.ARRAY)) }.toSet() - val alternative = if (types.isNotEmpty()) types.iterator().next() else unknownType() - newType = getCommonType(types).orElse(alternative) - subTypes = ArrayList(possibleSubTypes) - subTypes.remove(oldType) - subTypes.addAll(types) - } else { - newType = src.type - subTypes = ArrayList(possibleSubTypes) - subTypes.remove(oldType) - subTypes.add(newType) - } - setType(newType, root) - setPossibleSubTypes(subTypes, root) - if (previous != type) { - type.typeOrigin = Type.Origin.DATAFLOW - } - } - - override fun possibleSubTypesChanged(src: HasType, root: MutableList) { - if (!isTypeSystemActive) { - return - } - val subTypes: MutableList = ArrayList(possibleSubTypes) - subTypes.addAll(src.possibleSubTypes) - setPossibleSubTypes(subTypes, root) - } - override fun toString(): String { return ToStringBuilder(this, TO_STRING_STYLE) .appendSuper(super.toString()) @@ -104,6 +63,40 @@ class InitializerListExpression : Expression(), HasType.TypeListener { .toString() } + override fun addArgument(expression: Expression) { + this.initializers += expression + } + + override fun replaceArgument(old: Expression, new: Expression): Boolean { + // Not supported, too complex + return false + } + + override fun typeChanged(newType: Type, src: HasType) { + // Normally, we would check, if the source comes from our initializers, but we want to limit + // the iteration of the initializer list (which can potentially contain tens of thousands of + // entries in generated code), we skip it here. + // + // So we just have to look what kind of object we are initializing (its type is stored in + // our "type), to see whether we need to propagate something at all. If it has an array + // type, we need to propagate an array version of the incoming type. If our "target" is a + // regular object type, we do NOT propagate anything at all, because in this case we get the + // types of individual fields, and we are not interested in those (yet). + val type = type + if (type is PointerType && type.pointerOrigin == PointerType.PointerOrigin.ARRAY) { + addAssignedType(newType.array()) + } + } + + override fun assignedTypeChanged(assignedTypes: Set, src: HasType) { + // Same as above, we can just propagate the incoming assigned types to us (in array form), + // if we are initializing an array + val type = type + if (type is PointerType && type.pointerOrigin == PointerType.PointerOrigin.ARRAY) { + addAssignedTypes(assignedTypes.map { it.array() }.toSet()) + } + } + override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is InitializerListExpression) return false diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/LambdaExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/LambdaExpression.kt index 08a1fd0bbd..a737cf20f1 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/LambdaExpression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/LambdaExpression.kt @@ -26,19 +26,18 @@ package de.fraunhofer.aisec.cpg.graph.statements.expressions import de.fraunhofer.aisec.cpg.graph.AST -import de.fraunhofer.aisec.cpg.graph.HasType import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.ValueDeclaration -import de.fraunhofer.aisec.cpg.graph.isTypeSystemActive -import de.fraunhofer.aisec.cpg.graph.types.FunctionPointerType +import de.fraunhofer.aisec.cpg.graph.pointer +import de.fraunhofer.aisec.cpg.graph.types.FunctionType +import de.fraunhofer.aisec.cpg.graph.types.HasType import de.fraunhofer.aisec.cpg.graph.types.Type -import de.fraunhofer.aisec.cpg.graph.types.UnknownType /** * This expression denotes the usage of an anonymous / lambda function. It connects the inner * anonymous function to the user of a lambda function with an expression. */ -class LambdaExpression : Expression(), HasType.TypeListener { +class LambdaExpression : Expression(), HasType.TypeObserver { /** * If [areVariablesMutable] is false, only the (outer) variables in this list can be modified @@ -52,46 +51,25 @@ class LambdaExpression : Expression(), HasType.TypeListener { @AST var function: FunctionDeclaration? = null set(value) { - if (value != null) { - value.unregisterTypeListener(this) - if (value is HasType.TypeListener) { - unregisterTypeListener(value) - } - } + value?.unregisterTypeObserver(this) field = value - value?.registerTypeListener(this) + value?.registerTypeObserver(this) } - override fun typeChanged(src: HasType, root: MutableList, oldType: Type) { - if (!isTypeSystemActive) { + override fun typeChanged(newType: Type, src: HasType) { + // Make sure our src is the function + if (src != function) { return } - if (type !is UnknownType && src.propagationType == oldType) { - return - } - - if (src !is FunctionDeclaration) { - return - } - - val previous = type - - val parameterTypes = src.parameters.map { it.type } - val returnType = src.propagationType - - // the incoming "type" is associated to the function and it is only its return type (if it - // is known). what we really want is to construct a function type, or rather a function - // pointer type, since this is the closest to what we have - val functionType = FunctionPointerType(parameterTypes, this.language, returnType) - - setType(functionType, root) - if (previous != type) { - type.typeOrigin = Type.Origin.DATAFLOW + // We should only propagate a function type, coming from our declared function + if (newType is FunctionType) { + // Propagate a pointer reference to the function + this.type = newType.pointer() } } - override fun possibleSubTypesChanged(src: HasType, root: MutableList) { - // do not take sub types from the listener + override fun assignedTypeChanged(assignedTypes: Set, src: HasType) { + // Nothing to do } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/MemberCallExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/MemberCallExpression.kt index 239d152845..588be27338 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/MemberCallExpression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/MemberCallExpression.kt @@ -38,7 +38,7 @@ import java.util.* * While this node implements [HasBase], this is basically just a shortcut to access the base of the * underlying [callee] property, if appropriate. */ -class MemberCallExpression : CallExpression(), HasBase { +class MemberCallExpression : CallExpression(), HasBase, HasOperatorCode { /** * The base object. This is basically a shortcut to accessing the base of the [callee], if it * has one (i.e., if it implements [HasBase]). This is the case for example, if it is a diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/MemberExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/MemberExpression.kt index 5753976c99..59c9588064 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/MemberExpression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/MemberExpression.kt @@ -27,9 +27,9 @@ package de.fraunhofer.aisec.cpg.graph.statements.expressions import de.fraunhofer.aisec.cpg.graph.AST import de.fraunhofer.aisec.cpg.graph.HasBase -import de.fraunhofer.aisec.cpg.graph.HasType import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration import de.fraunhofer.aisec.cpg.graph.fqn +import de.fraunhofer.aisec.cpg.graph.types.HasType import de.fraunhofer.aisec.cpg.graph.types.Type import java.util.Objects import org.apache.commons.lang3.builder.ToStringBuilder @@ -43,10 +43,10 @@ class MemberExpression : DeclaredReferenceExpression(), HasBase { @AST override var base: Expression = ProblemExpression("could not parse base expression") set(value) { - field.unregisterTypeListener(this) + field.unregisterTypeObserver(this) field = value updateName() - value.registerTypeListener(this) + value.registerTypeObserver(this) } override var operatorCode: String? = null @@ -58,22 +58,6 @@ class MemberExpression : DeclaredReferenceExpression(), HasBase { .toString() } - override fun typeChanged(src: HasType, root: MutableList, oldType: Type) { - // We are basically only interested in type changes from our base to update the naming. We - // need to ignore actual changes to the type because otherwise things go horribly wrong - if (src == base) { - updateName() - } else { - super.typeChanged(src, root, oldType) - } - } - - override fun possibleSubTypesChanged(src: HasType, root: MutableList) { - if (src != base) { - super.possibleSubTypesChanged(src, root) - } - } - override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is MemberExpression) return false @@ -82,6 +66,16 @@ class MemberExpression : DeclaredReferenceExpression(), HasBase { override fun hashCode() = Objects.hash(super.hashCode(), base) + override fun typeChanged(newType: Type, src: HasType) { + // We are basically only interested in type changes from our base to update the naming. We + // need to ignore actual changes to the type because otherwise things go horribly wrong + if (src == base) { + updateName() + } else { + super.typeChanged(newType, src) + } + } + private fun updateName() { this.name = base.type.root.name.fqn(name.localName) } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/UnaryOperator.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/UnaryOperator.kt index be71e58f86..fca3d2deda 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/UnaryOperator.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/UnaryOperator.kt @@ -27,24 +27,20 @@ package de.fraunhofer.aisec.cpg.graph.statements.expressions import de.fraunhofer.aisec.cpg.graph.AST import de.fraunhofer.aisec.cpg.graph.AccessValues -import de.fraunhofer.aisec.cpg.graph.HasType -import de.fraunhofer.aisec.cpg.graph.isTypeSystemActive -import de.fraunhofer.aisec.cpg.graph.types.PointerType +import de.fraunhofer.aisec.cpg.graph.pointer +import de.fraunhofer.aisec.cpg.graph.types.HasType import de.fraunhofer.aisec.cpg.graph.types.Type -import de.fraunhofer.aisec.cpg.helpers.Util.distinctBy -import java.util.stream.Collectors import org.apache.commons.lang3.builder.ToStringBuilder -import org.neo4j.ogm.annotation.Transient /** A unary operator expression, involving one expression and an operator, such as `a++`. */ -class UnaryOperator : Expression(), HasType.TypeListener { +class UnaryOperator : Expression(), HasType.TypeObserver { /** The expression on which the operation is applied. */ @AST var input: Expression = ProblemExpression("could not parse input") set(value) { - field.unregisterTypeListener(this) + field.unregisterTypeObserver(this) field = value - input.registerTypeListener(this) + input.registerTypeObserver(this) changeExpressionAccess() } @@ -61,8 +57,6 @@ class UnaryOperator : Expression(), HasType.TypeListener { /** Specifies, whether this a pre fix operation. */ var isPrefix = false - @Transient private val checked: MutableList = ArrayList() - private fun changeExpressionAccess() { var access = AccessValues.READ if (operatorCode == "++" || operatorCode == "--") { @@ -73,103 +67,49 @@ class UnaryOperator : Expression(), HasType.TypeListener { } } - private fun getsDataFromInput( - curr: HasType.TypeListener, - target: HasType.TypeListener - ): Boolean { - val worklist: MutableList = ArrayList() - worklist.add(curr) - while (worklist.isNotEmpty()) { - val tl = worklist.removeAt(0) - if (!checked.contains(tl)) { - checked.add(tl) - if (tl === target) { - return true - } - if (curr is HasType) { - worklist.addAll((curr as HasType).typeListeners) - } - } - } - return false - } - - private fun getsDataFromInput(listener: HasType.TypeListener): Boolean { - checked.clear() - for (l in input.typeListeners) { - if (getsDataFromInput(l, listener)) return true - } - return false + override fun toString(): String { + return ToStringBuilder(this, TO_STRING_STYLE) + .appendSuper(super.toString()) + .append("operatorCode", operatorCode) + .append("postfix", isPostfix) + .append("prefix", isPrefix) + .toString() } - override fun typeChanged(src: HasType, root: MutableList, oldType: Type) { - if (!isTypeSystemActive) { + override fun typeChanged(newType: Type, src: HasType) { + // Only accept type changes from out input + if (src != input) { return } - val previous = type - if (src === input) { - var newType = src.propagationType - if (operatorCode == "*") { - newType = newType.dereference() - } else if (operatorCode == "&") { - newType = newType.reference(PointerType.PointerOrigin.POINTER) - } - setType(newType, root) - } else { - // Our input didn't change, so we don't need to (de)reference the type - setType(src.propagationType, root) - // Pass the type on to the input in an inversely (de)referenced way - var newType: Type? = src.propagationType - if (operatorCode == "*") { - newType = src.propagationType.reference(PointerType.PointerOrigin.POINTER) - } else if (operatorCode == "&") { - newType = src.propagationType.dereference() + val type = + when (operatorCode) { + "*" -> newType.dereference() + "&" -> newType.pointer() + else -> newType } - newType?.let { input.setType(it, mutableListOf(this)) } - } - if (previous != type) { - type.typeOrigin = Type.Origin.DATAFLOW - } + this.type = type } - override fun possibleSubTypesChanged(src: HasType, root: MutableList) { - if (!isTypeSystemActive) { - return - } - if (src is HasType.TypeListener && getsDataFromInput(src as HasType.TypeListener)) { + override fun assignedTypeChanged(assignedTypes: Set, src: HasType) { + // Only accept type changes from out input + if (src != input) { return } - var currSubTypes: MutableList = ArrayList(possibleSubTypes) - val newSubTypes = src.possibleSubTypes - currSubTypes.addAll(newSubTypes) - if (operatorCode == "*") { - currSubTypes = - currSubTypes - .stream() - .filter(distinctBy { obj: Type -> obj.typeName }) - .map { obj: Type -> obj.dereference() } - .collect(Collectors.toList()) - } else if (operatorCode == "&") { - currSubTypes = - currSubTypes - .stream() - .filter(distinctBy { obj: Type -> obj.typeName }) - .map { t: Type -> t.reference(PointerType.PointerOrigin.POINTER) } - .collect(Collectors.toList()) - } - _possibleSubTypes.clear() - setPossibleSubTypes(currSubTypes, root) // notify about the new type - } - override fun toString(): String { - return ToStringBuilder(this, TO_STRING_STYLE) - .appendSuper(super.toString()) - .append("operatorCode", operatorCode) - .append("postfix", isPostfix) - .append("prefix", isPrefix) - .toString() + // Apply our operator to all assigned types and forward them to us + this.addAssignedTypes( + assignedTypes + .map { + when (operatorCode) { + "*" -> it.dereference() + "&" -> it.pointer() + else -> it + } + } + .toSet() + ) } override fun equals(other: Any?): Boolean { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/AutoType.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/AutoType.kt new file mode 100644 index 0000000000..5d98db1138 --- /dev/null +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/AutoType.kt @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2023, 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.graph.types + +import de.fraunhofer.aisec.cpg.frontends.Language +import de.fraunhofer.aisec.cpg.graph.unknownType + +/** + * This type represents a [Type] that uses auto-inference (usually from an initializer) to determine + * it's actual type. It is commonly used in dynamically typed languages or in languages that have a + * special keyword, such as `auto` in C++. + * + * Note: This is intentionally a distinct type and not the [UnknownType]. + */ +class AutoType(override var language: Language<*>?) : Type("auto", language) { + override fun reference(pointer: PointerType.PointerOrigin?): Type { + return PointerType(this, pointer) + } + + override fun dereference(): Type { + return unknownType() + } + + override fun duplicate(): Type { + return AutoType(this.language) + } +} diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/FunctionType.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/FunctionType.kt index 0dda94a841..d228edc6c6 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/FunctionType.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/FunctionType.kt @@ -27,7 +27,6 @@ package de.fraunhofer.aisec.cpg.graph.types import de.fraunhofer.aisec.cpg.frontends.Language import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration -import de.fraunhofer.aisec.cpg.graph.registerType import de.fraunhofer.aisec.cpg.graph.unknownType /** 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 edf680fc7c..8df00cad74 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, Fraunhofer AISEC. All rights reserved. + * Copyright (c) 2023, 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. @@ -23,109 +23,174 @@ * \______/ \__| \______/ * */ -package de.fraunhofer.aisec.cpg.graph +package de.fraunhofer.aisec.cpg.graph.types -import de.fraunhofer.aisec.cpg.TypeManager import de.fraunhofer.aisec.cpg.frontends.TranslationException -import de.fraunhofer.aisec.cpg.graph.types.Type -import java.util.Optional - -interface HasType : ContextProvider { - var type: Type - - /** - * @return The returned Type is always the same as getType() with the exception of ReferenceType - * since there is no case in which we want to propagate a reference when using typeChanged() - */ - val propagationType: Type +import de.fraunhofer.aisec.cpg.graph.ContextProvider +import de.fraunhofer.aisec.cpg.graph.LanguageProvider +import de.fraunhofer.aisec.cpg.graph.Node +import de.fraunhofer.aisec.cpg.graph.declarations.ValueDeclaration +import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Literal +import de.fraunhofer.aisec.cpg.graph.statements.expressions.UnaryOperator +import java.util.* + +/** + * 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 + * types should derive from these two base classes. + */ +interface HasType : ContextProvider, LanguageProvider { /** - * Side-effect free type modification WARNING: This should only be used by the TypeSystem Pass + * This property refers to the *definite* [Type] that the [Node] has. If you are unsure about + * what it's type is, you should prefer to set it to the [UnknownType]. It is usually one of the + * following: + * - the type declared by the [Node], e.g., by a [ValueDeclaration] + * - intrinsically tied to the node, e.g. an [IntegerType] in an integer [Literal] + * - the [Type] of a declaration a node is referring to, e.g., in a + * [DeclaredReferenceExpression] * - * @param type new type + * An implementation of this must be sure to invoke [informObservers]. */ - fun updateType(type: Type) - - fun updatePossibleSubtypes(types: List) + var type: Type /** - * Set the node's type. This may start a chain of type listener notifications + * This property refers to a list of [Type] nodes which are assigned to that [Node]. This could + * be different from the [HasType.type]. A common example is that a node could contain an + * interface as a [HasType.type], but the actual implementation of the type as one of the + * [assignedTypes]. This could potentially also be empty, if we don't see any assignments to + * this expression. * - * @param type new type - * @param root The nodes which we have seen in the type change chain. When a node receives a - * type setting command where root.contains(this), we know that we have a type listener circle - * and can abort. If root is an empty list, the type change is seen as an externally triggered - * event and subsequent type listeners receive the current node as their root. + * Note: in order to properly inform observers, one should NOT use the regular [MutableSet.add] + * or [MutableSet.addAll] but rather use [addAssignedType] and [addAssignedTypes]. Otherwise, we + * cannot watch for changes within the set. We therefore only expose this as a [Set], but an + * implementing class MUST implement this as a [MutableSet] so that we can modify it internally. */ - fun setType(type: Type, root: MutableList?) - - var possibleSubTypes: List + var assignedTypes: Set /** - * Set the node's possible subtypes. Listener circle detection works the same way as with - * [ ][.setType] - * - * @param possibleSubTypes the set of possible sub types - * @param root A list of already seen nodes which is used for detecting loops. + * Adds [type] to the list of [HasType.assignedTypes] and informs all observers about the + * change. */ - fun setPossibleSubTypes(possibleSubTypes: List, root: MutableList) - - val typeListeners: MutableSet - - fun registerTypeListener(listener: TypeListener) { - val root = mutableListOf(this) - typeListeners.add(listener) - listener.typeChanged(this, root, type) - listener.possibleSubTypesChanged(this, root) + fun addAssignedType(type: Type) { + if (language?.shouldPropagateType(this, type) == false) { + return + } + + val changed = (this.assignedTypes as MutableSet).add(type) + if (changed) { + informObservers(TypeObserver.ChangeType.ASSIGNED_TYPE) + } } - fun unregisterTypeListener(listener: TypeListener) { - typeListeners.remove(listener) + /** + * Adds all [types] to the list of [HasType.assignedTypes] and informs all observers about the + * change. + */ + fun addAssignedTypes(types: Set) { + val changed = + (this.assignedTypes as MutableSet).addAll( + types.filter { language?.shouldPropagateType(this, it) == true } + ) + if (changed) { + informObservers(TypeObserver.ChangeType.ASSIGNED_TYPE) + } } - fun refreshType() - /** - * Used to set the type and clear the possible subtypes list for when a type is more precise - * than the current. - * - * @param type the more precise type + * A list of [TypeObserver] objects that will be informed about type changes, usually by + * [informObservers]. */ - fun resetTypes(type: Type) + val typeObservers: MutableList - interface TypeListener { - fun typeChanged(src: HasType, root: MutableList, oldType: Type) + /** + * A [TypeObserver] can be used by its implementing class to observe changes to the + * [HasType.type] and/or [HasType.assignedTypes] of a [Node] (that implements [HasType]). The + * implementing node can then decide if and how to propagate this type information to itself + * (and possibly to others). Examples include modifying the incoming type depending on an + * operator, e.g., in a [UnaryOperator] expression. Changes to [HasType.type] will invoke + * [typeChanged], changes to [HasType.assignedTypes] will invoke [assignedTypes]. + */ + interface TypeObserver { + enum class ChangeType { + TYPE, + ASSIGNED_TYPE + } + + /** + * This callback function will be invoked, if the observed node changes its [HasType.type]. + */ + fun typeChanged(newType: Type, src: HasType) + + /** + * This callback function will be invoked, if the observed node changes its + * [HasType.assignedTypes]. + */ + fun assignedTypeChanged(assignedTypes: Set, src: HasType) + } - fun possibleSubTypesChanged(src: HasType, root: MutableList) + /** + * This function SHOULD be used be an implementing class to inform observers about type changes. + * While the implementing class can technically do this on its own, it is strongly recommended + * to use this function to harmonize the behaviour of propagating types. + */ + fun informObservers(changeType: TypeObserver.ChangeType) { + 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) + } + } } /** - * The Typeresolver needs to be aware of all outgoing edges to types in order to merge equal - * types to the same node. For the primary type edge, this is achieved through the hasType - * interface. If a node has additional type edges (e.g. default type in [ ]) the node must - * implement the updateType method, so that the current type is always replaced with the merged - * one + * Registers the given [typeObservers] to be informed about type updates. This also immediately + * invokes both [TypeObserver.typeChanged] and [TypeObserver.assignedTypeChanged]. */ - interface SecondaryTypeEdge { - fun updateType(typeState: Collection) + fun registerTypeObserver(typeObserver: TypeObserver) { + typeObservers += typeObserver + + // If we would only propagate the unknown type, we can also skip it + val newType = this.type + if (newType !is UnknownType) { + // Immediately inform about changes + typeObserver.typeChanged(newType, this) + } + + // If we would propagate an empty list, we can also skip it + val assignedTypes = this.assignedTypes + if (assignedTypes.isNotEmpty()) { + // Immediately inform about changes + typeObserver.assignedTypeChanged(assignedTypes, this) + } } -} -val Node.isTypeSystemActive: Boolean - get() { - return TypeManager.isTypeSystemActive + /** Unregisters the given [typeObservers] from the list of observers. */ + fun unregisterTypeObserver(typeObserver: TypeObserver) { + typeObservers -= typeObserver } +} fun Node.isSupertypeOf(superType: Type, subType: Type): Boolean { val c = ctx ?: throw TranslationException("context not available") return c.typeManager.isSupertypeOf(superType, subType, this) } -fun HasType.cacheType(type: Type) { - val c = ctx ?: throw TranslationException("context not available") - c.typeManager.cacheType(this, type) -} - fun Node.registerType(type: T): T { val c = ctx ?: throw TranslationException("context not available") return c.typeManager.registerType(type) @@ -135,8 +200,3 @@ fun Node.getCommonType(types: Collection): Optional { val c = ctx ?: throw TranslationException("context not available") return c.typeManager.getCommonType(types, this.ctx) } - -fun Node.stopPropagation(type: Type, newType: Type): Boolean { - val c = ctx ?: throw TranslationException("context not available") - return c.typeManager.stopPropagation(type, newType) -} diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/ObjectType.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/ObjectType.kt index d81ecc441e..bbe1d32282 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/ObjectType.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/ObjectType.kt @@ -26,7 +26,6 @@ package de.fraunhofer.aisec.cpg.graph.types import de.fraunhofer.aisec.cpg.frontends.Language -import de.fraunhofer.aisec.cpg.graph.HasType.SecondaryTypeEdge import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration import de.fraunhofer.aisec.cpg.graph.edge.Properties import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/SecondaryTypeEdge.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/SecondaryTypeEdge.kt new file mode 100644 index 0000000000..c0519df0d6 --- /dev/null +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/SecondaryTypeEdge.kt @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2023, 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.graph.types + +import de.fraunhofer.aisec.cpg.graph.declarations.TypeParamDeclaration +import de.fraunhofer.aisec.cpg.passes.TypeResolver + +/** + * The [TypeResolver] needs to be aware of all outgoing edges to types in order to merge equal types + * to the same node. For the primary type edge, this is achieved through the hasType interface. If a + * node has additional type edges (e.g. [TypeParamDeclaration.default]) the node must implement the + * [updateType] method, so that the current type is always replaced with the merged one + */ +interface SecondaryTypeEdge { + fun updateType(typeState: Collection) +} diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/Type.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/Type.kt index a43599c030..4ebd21cef4 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/Type.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/Type.kt @@ -159,8 +159,10 @@ abstract class Type : Node { */ get() = (this is ObjectType || + this is AutoType || this is UnknownType || this is FunctionType || + this is ProblemType || this is TupleType // TODO(oxisto): convert FunctionPointerType to second order type || this is FunctionPointerType || diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/UnknownType.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/UnknownType.kt index ea8925e75f..57b2c678fc 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/UnknownType.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/UnknownType.kt @@ -32,9 +32,9 @@ import java.util.* import java.util.concurrent.ConcurrentHashMap /** - * UnknownType describe the case in which it is not possible for the CPG to determine which Type is - * used. E.g.: This occurs when the type is inferred by the compiler automatically when using - * keywords such as auto in cpp + * UnknownType describe the case in which it is not possible for the CPG to determine which [Type] + * is used. Ideally, this type is only assigned temporary and then later replaced with an actual + * known type. But, because we sometimes do fuzzy parsing, this might not be the case all the time. */ class UnknownType private constructor() : Type() { init { 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 a86ca0329a..38d3a66fa1 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 @@ -28,7 +28,6 @@ package de.fraunhofer.aisec.cpg.helpers import de.fraunhofer.aisec.cpg.ScopeManager import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend import de.fraunhofer.aisec.cpg.graph.AST -import de.fraunhofer.aisec.cpg.graph.HasType import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration @@ -38,7 +37,6 @@ import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.checkForPropertyEdge import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.unwrap import de.fraunhofer.aisec.cpg.graph.statements.CompoundStatement -import de.fraunhofer.aisec.cpg.processing.IVisitor import de.fraunhofer.aisec.cpg.processing.strategy.Strategy import java.lang.annotation.AnnotationFormatError import java.lang.reflect.Field @@ -250,20 +248,6 @@ object SubgraphWalker { return border } - fun refreshType(node: Node) { - // Using a visitor to avoid loops in the AST - node.accept( - Strategy::AST_FORWARD, - object : IVisitor() { - override fun visit(t: Node) { - if (t is HasType) { - (t as HasType).refreshType() - } - } - } - ) - } - /** * For better readability: `result.entries` instead of `result.get(0)` when working with * getEOGPathEdges. Can be used for all subgraphs in subgraphs, e.g. AST entries and exits in a diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CXXCallResolverHelper.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CXXCallResolverHelper.kt index 1030524b39..ed9a77e044 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CXXCallResolverHelper.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CXXCallResolverHelper.kt @@ -30,10 +30,7 @@ import de.fraunhofer.aisec.cpg.TypeManager import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.declarations.* import de.fraunhofer.aisec.cpg.graph.statements.expressions.* -import de.fraunhofer.aisec.cpg.graph.types.ObjectType -import de.fraunhofer.aisec.cpg.graph.types.ParameterizedType -import de.fraunhofer.aisec.cpg.graph.types.Type -import de.fraunhofer.aisec.cpg.graph.types.UnknownType +import de.fraunhofer.aisec.cpg.graph.types.* import java.util.HashMap import java.util.regex.Pattern @@ -44,19 +41,15 @@ import java.util.regex.Pattern * signature by means of casting */ fun compatibleSignatures( + call: CallExpression, callSignature: List, - functionSignature: List, - ctx: TranslationContext + functionSignature: List ): Boolean { return if (callSignature.size == functionSignature.size) { for (i in callSignature.indices) { if ( callSignature[i].isPrimitive != functionSignature[i].isPrimitive && - !ctx.typeManager.isSupertypeOf( - functionSignature[i], - callSignature[i], - ctx.scopeManager - ) + !call.isSupertypeOf(functionSignature[i], callSignature[i]) ) { return false } @@ -97,8 +90,7 @@ fun getCallSignatureWithDefaults( */ fun resolveWithImplicitCast( call: CallExpression, - initialInvocationCandidates: List, - ctx: TranslationContext + initialInvocationCandidates: List ): List { // Output list for invocationTargets obtaining a valid signature by performing implicit @@ -111,13 +103,13 @@ fun resolveWithImplicitCast( for (functionDeclaration in initialInvocationCandidates) { val callSignature = getCallSignatureWithDefaults(call, functionDeclaration) // Check if the signatures match by implicit casts - if (compatibleSignatures(callSignature, functionDeclaration.signatureTypes, ctx)) { + if (compatibleSignatures(call, callSignature, functionDeclaration.signatureTypes)) { val implicitCastTargets = signatureWithImplicitCastTransformation( + call, getCallSignatureWithDefaults(call, functionDeclaration), call.arguments, functionDeclaration.signatureTypes, - ctx ) if (implicitCasts == null) { implicitCasts = implicitCastTargets @@ -126,7 +118,7 @@ fun resolveWithImplicitCast( // to the same target type checkMostCommonImplicitCast(implicitCasts, implicitCastTargets) } - if (compatibleSignatures(call.signature, functionDeclaration.signatureTypes, ctx)) { + if (compatibleSignatures(call, call.signature, functionDeclaration.signatureTypes)) { invocationTargetsWithImplicitCast.add(functionDeclaration) } else { invocationTargetsWithImplicitCastAndDefaults.add(functionDeclaration) @@ -262,8 +254,7 @@ fun shouldContinueSearchInParent(recordDeclaration: RecordDeclaration?, name: St */ fun resolveConstructorWithImplicitCast( constructExpression: ConstructExpression, - recordDeclaration: RecordDeclaration, - ctx: TranslationContext + recordDeclaration: RecordDeclaration ): ConstructorDeclaration? { for (constructorDeclaration in recordDeclaration.constructors) { val workingSignature = mutableListOf(*constructExpression.signature.toTypedArray()) @@ -278,29 +269,33 @@ fun resolveConstructorWithImplicitCast( } if ( compatibleSignatures( + constructExpression, constructExpression.signature, constructorDeclaration.signatureTypes, - ctx ) ) { val implicitCasts = signatureWithImplicitCastTransformation( + constructExpression, constructExpression.signature, constructExpression.arguments, constructorDeclaration.signatureTypes, - ctx ) applyImplicitCastToArguments(constructExpression, implicitCasts) return constructorDeclaration } else if ( - compatibleSignatures(workingSignature, constructorDeclaration.signatureTypes, ctx) + compatibleSignatures( + constructExpression, + workingSignature, + constructorDeclaration.signatureTypes, + ) ) { val implicitCasts = signatureWithImplicitCastTransformation( + constructExpression, getCallSignatureWithDefaults(constructExpression, constructorDeclaration), constructExpression.arguments, constructorDeclaration.signatureTypes, - ctx ) applyImplicitCastToArguments(constructExpression, implicitCasts) return constructorDeclaration @@ -362,10 +357,10 @@ fun applyTemplateInstantiation( val templateCallSignature = templateCall.signature val callSignatureImplicit = signatureWithImplicitCastTransformation( + templateCall, templateCallSignature, templateCall.arguments, templateFunctionSignature, - ctx ) for (i in callSignatureImplicit.indices) { val cast = callSignatureImplicit[i] @@ -399,10 +394,10 @@ fun applyTemplateInstantiation( * FunctionDeclaration cannot be reached through implicit casts */ fun signatureWithImplicitCastTransformation( + call: CallExpression, callSignature: List, arguments: List, functionSignature: List, - ctx: TranslationContext ): MutableList { val implicitCasts = mutableListOf() if (callSignature.size != functionSignature.size) return implicitCasts @@ -412,7 +407,7 @@ fun signatureWithImplicitCastTransformation( val funcType = functionSignature[i] if (callType?.isPrimitive == true && funcType.isPrimitive && callType != funcType) { val implicitCast = CastExpression() - implicitCast.ctx = ctx + implicitCast.ctx = call.ctx implicitCast.isImplicit = true implicitCast.castType = funcType implicitCast.language = funcType.language diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolver.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolver.kt index 70b17a789b..5bd0a46fa2 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolver.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolver.kt @@ -110,6 +110,7 @@ open class CallResolver(ctx: TranslationContext) : SymbolResolverPass(ctx) { val currInitializer = node.initializer if (currInitializer == null && node.isImplicitInitializerAllowed) { val initializer = node.newConstructExpression(typeString, "$typeString()") + initializer.type = node.type initializer.isImplicit = true node.initializer = initializer node.templateParameters?.let { @@ -125,6 +126,7 @@ open class CallResolver(ctx: TranslationContext) : SymbolResolverPass(ctx) { val signature = arguments.map(Node::code).joinToString(", ") val initializer = node.newConstructExpression(typeString, "$typeString($signature)") + initializer.type = node.type initializer.arguments = mutableListOf(*arguments.toTypedArray()) initializer.isImplicit = true node.initializer = initializer @@ -483,7 +485,7 @@ open class CallResolver(ctx: TranslationContext) : SymbolResolverPass(ctx) { if (node is MemberCallExpression) { node.base?.let { base -> possibleTypes.add(base.type) - possibleTypes.addAll(base.possibleSubTypes) + possibleTypes.addAll(base.assignedTypes) } } else { // This could be a C++ member call with an implicit this (which we do not create), so @@ -607,7 +609,7 @@ open class CallResolver(ctx: TranslationContext) : SymbolResolverPass(ctx) { // If we don't find any candidate and our current language is c/c++ we check if there is // a candidate with an implicit cast constructorCandidate = - resolveConstructorWithImplicitCast(constructExpression, recordDeclaration, ctx) + resolveConstructorWithImplicitCast(constructExpression, recordDeclaration) } return constructorCandidate diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt index 9182f2c19e..d39bfc207e 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt @@ -34,6 +34,8 @@ import de.fraunhofer.aisec.cpg.graph.statements.* import de.fraunhofer.aisec.cpg.graph.statements.expressions.* import de.fraunhofer.aisec.cpg.helpers.* import de.fraunhofer.aisec.cpg.passes.order.DependsOn +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.contract /** * This pass determines the data flows of DeclaredReferenceExpressions which refer to a @@ -41,6 +43,7 @@ import de.fraunhofer.aisec.cpg.passes.order.DependsOn * path, only such data flows are left which can occur when following the control flow (in terms of * the EOG) of the program. */ +@OptIn(ExperimentalContracts::class) @DependsOn(EvaluationOrderGraphPass::class) @DependsOn(DFGPass::class) open class ControlFlowSensitiveDFGPass(ctx: TranslationContext) : TranslationUnitPass(ctx) { @@ -86,7 +89,7 @@ open class ControlFlowSensitiveDFGPass(ctx: TranslationContext) : TranslationUni * code [node]. */ protected fun clearFlowsOfVariableDeclarations(node: Node) { - for (varDecl in node.variables) { + for (varDecl in node.variables.filter { it !is FieldDeclaration }) { varDecl.clearPrevDFG() varDecl.clearNextDFG() } @@ -107,7 +110,7 @@ open class ControlFlowSensitiveDFGPass(ctx: TranslationContext) : TranslationUni worklist: Worklist, Node, Set> ): State> { // We will set this if we write to a variable - val writtenDecl: Declaration? + var writtenDecl: Declaration? val currentNode = currentEdge.end val doubleState = state as DFGPassState @@ -120,7 +123,7 @@ open class ControlFlowSensitiveDFGPass(ctx: TranslationContext) : TranslationUni state.push(currentNode, PowersetLattice(setOf(initializer))) doubleState.pushToDeclarationsState(currentNode, PowersetLattice(setOf(currentNode))) - } else if (currentNode is AssignExpression) { + } else if (isSimpleAssignment(currentNode)) { // It's an assignment which can have one or multiple things on the lhs and on the // rhs. The lhs could be a declaration or a reference (or multiple of these things). // The rhs can be anything. The rhs flows to the respective lhs. To identify the @@ -145,33 +148,25 @@ open class ControlFlowSensitiveDFGPass(ctx: TranslationContext) : TranslationUni state.push(input, doubleState.declarationsState[writtenDecl]) doubleState.declarationsState[writtenDecl] = PowersetLattice(setOf(input)) } - } else if (isSimpleAssignment(currentNode)) { - // Only the lhs is the last write statement here and the variable which is written - // to. - writtenDecl = - ((currentNode as BinaryOperator).lhs as DeclaredReferenceExpression).refersTo - - if (writtenDecl != null) { - doubleState.declarationsState[writtenDecl] = PowersetLattice(setOf(currentNode.lhs)) - } } else if (isCompoundAssignment(currentNode)) { // We write to the lhs, but it also serves as an input => We first get all previous // writes to the lhs and then add the flow from lhs and rhs to the current node. // The write operation goes to the variable in the lhs - writtenDecl = - ((currentNode as BinaryOperator).lhs as? DeclaredReferenceExpression)?.refersTo + val lhs = currentNode.lhs.singleOrNull() + writtenDecl = (lhs as? DeclaredReferenceExpression)?.refersTo - if (writtenDecl != null) { + if (writtenDecl != null && lhs != null) { // Data flows from the last writes to the lhs variable to this node - state.push(currentNode.lhs, doubleState.declarationsState[writtenDecl]) + state.push(lhs, doubleState.declarationsState[writtenDecl]) // The whole current node is the place of the last update, not (only) the lhs! - doubleState.declarationsState[writtenDecl] = PowersetLattice(setOf(currentNode.lhs)) + doubleState.declarationsState[writtenDecl] = PowersetLattice(setOf(lhs)) } } else if ( (currentNode as? DeclaredReferenceExpression)?.access == AccessValues.READ && - currentNode.refersTo is VariableDeclaration + currentNode.refersTo is VariableDeclaration && + currentNode.refersTo !is FieldDeclaration ) { // We can only find a change if there's a state for the variable doubleState.declarationsState[currentNode.refersTo]?.let { @@ -245,17 +240,18 @@ open class ControlFlowSensitiveDFGPass(ctx: TranslationContext) : TranslationUni * Checks if the node performs an operation and an assignment at the same time e.g. with the * operators +=, -=, *=, ... */ - protected fun isCompoundAssignment(currentNode: Node) = - currentNode is BinaryOperator && + protected fun isCompoundAssignment(currentNode: Node): Boolean { + contract { returns(true) implies (currentNode is AssignExpression) } + return currentNode is AssignExpression && currentNode.operatorCode in (currentNode.language?.compoundAssignmentOperators ?: setOf()) && - (currentNode.lhs as? DeclaredReferenceExpression)?.refersTo != null + (currentNode.lhs.singleOrNull() as? DeclaredReferenceExpression)?.refersTo != null + } - /** Checks if the node is a simple assignment of the form `var = ...` */ - protected fun isSimpleAssignment(currentNode: Node) = - currentNode is BinaryOperator && - currentNode.operatorCode == "=" && - (currentNode.lhs as? DeclaredReferenceExpression)?.refersTo != null + protected fun isSimpleAssignment(currentNode: Node): Boolean { + contract { returns(true) implies (currentNode is AssignExpression) } + return currentNode is AssignExpression && currentNode.operatorCode == "=" + } /** Checks if the node is an increment or decrement operator (e.g. i++, i--, ++i, --i) */ protected fun isIncOrDec(currentNode: Node) = diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/DFGPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/DFGPass.kt index 2ca7531daa..580ebe69ed 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/DFGPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/DFGPass.kt @@ -71,7 +71,7 @@ class DFGPass(ctx: TranslationContext) : ComponentPass(ctx) { is NewExpression -> handleNewExpression(node) // We keep the logic for the InitializerListExpression in that class because the // performance would decrease too much. - // is InitializerListExpression -> handleInitializerListExpression(node) + is InitializerListExpression -> handleInitializerListExpression(node) is KeyValueExpression -> handleKeyValueExpression(node) is LambdaExpression -> handleLambdaExpression(node) is UnaryOperator -> handleUnaryOperator(node) @@ -272,14 +272,9 @@ class DFGPass(ctx: TranslationContext) : ComponentPass(ctx) { /** * Adds the DFG edges for an [InitializerListExpression]. All values in the initializer flow to * this expression. - * - * TODO: This change seems to have performance issues! */ protected fun handleInitializerListExpression(node: InitializerListExpression) { - node.initializers.forEach { - it.registerTypeListener(node) - node.addPrevDFG(it) - } + node.initializers.forEach { node.addPrevDFG(it) } } /** diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TypeHierarchyResolver.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TypeHierarchyResolver.kt index 2ab2fffb06..2e5f10af8f 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TypeHierarchyResolver.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TypeHierarchyResolver.kt @@ -33,7 +33,6 @@ import de.fraunhofer.aisec.cpg.graph.declarations.EnumDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration import de.fraunhofer.aisec.cpg.graph.types.Type -import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker import de.fraunhofer.aisec.cpg.processing.IVisitor import de.fraunhofer.aisec.cpg.processing.strategy.Strategy import java.util.* @@ -75,8 +74,6 @@ open class TypeHierarchyResolver(ctx: TranslationContext) : ComponentPass(ctx) { directSupertypeRecords.map { findSupertypeRecords(it) }.flatten().toSet() enumDecl.superTypeDeclarations = allSupertypes } - - component.translationUnits.forEach { SubgraphWalker.refreshType(it) } } protected fun findRecordsAndEnums(node: Node) { 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 b970f07d75..6e38a9a360 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 @@ -27,11 +27,10 @@ package de.fraunhofer.aisec.cpg.passes import de.fraunhofer.aisec.cpg.TranslationContext import de.fraunhofer.aisec.cpg.graph.Component -import de.fraunhofer.aisec.cpg.graph.HasType -import de.fraunhofer.aisec.cpg.graph.HasType.SecondaryTypeEdge import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration import de.fraunhofer.aisec.cpg.graph.types.* +import de.fraunhofer.aisec.cpg.graph.types.SecondaryTypeEdge import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker.IterativeGraphWalker import de.fraunhofer.aisec.cpg.passes.order.DependsOn @@ -181,14 +180,16 @@ open class TypeResolver(ctx: TranslationContext) : ComponentPass(ctx) { // globally unique if (node is HasType && node.type !is ParameterizedType) { val type = node.type - val types = + val newType = if (type.isFirstOrderType) { - typeState.keys - } else { - typeState.computeIfAbsent(type.root, ::mutableListOf) - } - updateType(node, types) - node.updatePossibleSubtypes(ensureUniqueSubTypes(node.possibleSubTypes)) + typeState.keys + } else { + typeState.computeIfAbsent(type.root, ::mutableListOf) + } + .firstOrNull { it == type } + if (newType != null) { + node.type = newType + } } } @@ -196,27 +197,16 @@ open class TypeResolver(ctx: TranslationContext) : ComponentPass(ctx) { * ensures that the if a nodes contains secondary type edges, those types are also merged and no * duplicate is left * - * @param node implementing [HasType.SecondaryTypeEdge] + * @param node implementing [SecondaryTypeEdge] */ protected fun ensureUniqueSecondaryTypeEdge(node: Node) { if (node is SecondaryTypeEdge) { node.updateType(typeState.keys) } else if (node is HasType && node.type is SecondaryTypeEdge) { (node.type as SecondaryTypeEdge).updateType(typeState.keys) - for (possibleSubType in node.possibleSubTypes) { - if (possibleSubType is SecondaryTypeEdge) { - possibleSubType.updateType(typeState.keys) - } - } } } - protected fun updateType(node: HasType, types: Collection) { - // TODO: Why do we perform the update only for the first type? - val typeToUpdate = types.firstOrNull { it == node.type } ?: return - node.updateType(typeToUpdate) - } - /** * Creates the recordDeclaration relationship between ObjectTypes and RecordDeclaration (from * the Type to the Class) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/VariableUsageResolver.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/VariableUsageResolver.kt index f59a594076..d37ba927fd 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/VariableUsageResolver.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/VariableUsageResolver.kt @@ -37,6 +37,7 @@ import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberExpression import de.fraunhofer.aisec.cpg.graph.types.* import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker.ScopedWalker import de.fraunhofer.aisec.cpg.helpers.Util +import de.fraunhofer.aisec.cpg.passes.inference.Inference.TypeInferenceObserver import de.fraunhofer.aisec.cpg.passes.inference.startInference import de.fraunhofer.aisec.cpg.passes.order.DependsOn import org.slf4j.LoggerFactory @@ -87,10 +88,11 @@ open class VariableUsageResolver(ctx: TranslationContext) : SymbolResolverPass(c } } - protected fun resolveFunctionPtr(reference: DeclaredReferenceExpression): ValueDeclaration? { - // Without FunctionPointerType, we cannot resolve function pointers - val fptrType = reference.type as? FunctionPointerType ?: return null - + /** This function seems to resolve function pointers pointing to a [MethodDeclaration]. */ + protected fun resolveMethodFunctionPointer( + reference: DeclaredReferenceExpression, + type: FunctionPointerType + ): ValueDeclaration { val parent = reference.name.parent return handleUnknownFunction( @@ -100,7 +102,7 @@ open class VariableUsageResolver(ctx: TranslationContext) : SymbolResolverPass(c null }, reference.name, - fptrType + type ) } @@ -144,8 +146,10 @@ open class VariableUsageResolver(ctx: TranslationContext) : SymbolResolverPass(c if (currentClass != null) { recordDeclType = currentClass.toType() } - if (current.type is FunctionPointerType && refersTo == null) { - refersTo = resolveFunctionPtr(current) + + val helperType = current.resolutionHelper?.type + if (helperType is FunctionPointerType && refersTo == null) { + refersTo = resolveMethodFunctionPointer(current, helperType) } // only add new nodes for non-static unknown @@ -246,9 +250,12 @@ open class VariableUsageResolver(ctx: TranslationContext) : SymbolResolverPass(c base.refersTo = baseTarget // Explicitly set the type of the call's base to the super type base.type = superType - // And set the possible subtypes, to ensure, that really only our + (base.refersTo as? HasType)?.type = superType + // And set the assigned subtypes, to ensure, that really only our // super type is in there - base.updatePossibleSubtypes(listOf(superType)) + base.assignedTypes = mutableSetOf(superType) + (base.refersTo as? ValueDeclaration)?.assignedTypes = + mutableSetOf(superType) } } } else { @@ -336,21 +343,24 @@ open class VariableUsageResolver(ctx: TranslationContext) : SymbolResolverPass(c .map { it.definition } .firstOrNull() } - // Attention: using orElse instead of orElseGet will always invoke unknown declaration - // handling! - return member ?: handleUnknownField(containingClass, reference.name, reference.type) + return member ?: handleUnknownField(containingClass, reference) } // TODO(oxisto): Move to inference class - protected fun handleUnknownField(base: Type, name: Name, type: Type): FieldDeclaration? { + protected fun handleUnknownField( + base: Type, + ref: DeclaredReferenceExpression + ): FieldDeclaration? { + val name = ref.name + // unwrap a potential pointer-type if (base is PointerType) { - return handleUnknownField(base.elementType, name, type) + return handleUnknownField(base.elementType, ref) } if (base.name !in recordMap) { // No matching record in the map? If we should infer it, we do so, otherwise we stop. - if (config?.inferenceConfiguration?.inferRecords != true) return null + if (!config.inferenceConfiguration.inferRecords) return null // We access an unknown field of an unknown record. so we need to handle that val kind = @@ -380,7 +390,8 @@ open class VariableUsageResolver(ctx: TranslationContext) : SymbolResolverPass(c val declaration = recordDeclaration.newFieldDeclaration( name.localName, - type, + // we will set the type later through the type inference observer + unknownType(), listOf(), "", null, @@ -389,6 +400,11 @@ open class VariableUsageResolver(ctx: TranslationContext) : SymbolResolverPass(c ) recordDeclaration.addField(declaration) declaration.isInferred = true + + // We might be able to resolve the type later (or better), if a type is + // assigned to our reference later + ref.registerTypeObserver(TypeInferenceObserver(declaration)) + declaration } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/Inference.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/Inference.kt index 6d1af8f6f1..512a94f4e8 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/Inference.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/Inference.kt @@ -51,7 +51,6 @@ import org.slf4j.LoggerFactory */ class Inference(val start: Node, override val ctx: TranslationContext) : LanguageProvider, ScopeProvider, IsInferredProvider, ContextProvider { - val log: Logger = LoggerFactory.getLogger(Inference::class.java) override val language: Language<*>? get() = start.language @@ -92,7 +91,7 @@ class Inference(val start: Node, override val ctx: TranslationContext) : newFunctionDeclaration(name ?: "", code) } - log.debug( + Companion.log.debug( "Inferred a new {} declaration {} with parameter types {}", if (inferred is MethodDeclaration) "method" else "function", inferred.name, @@ -311,12 +310,12 @@ class Inference(val start: Node, override val ctx: TranslationContext) : kind: String = "class" ): RecordDeclaration? { if (type !is ObjectType) { - log.error( + Companion.log.error( "Trying to infer a record declaration of a non-object type. Not sure what to do? Should we change the type?" ) return null } - log.debug( + Companion.log.debug( "Encountered an unknown record type ${type.typeName} during a call. We are going to infer that record" ) @@ -338,7 +337,7 @@ class Inference(val start: Node, override val ctx: TranslationContext) : // delegate further operations to the scope manager. We also save the old scope so we can // restore it. return inferInScopeOf(start) { - log.debug( + Companion.log.debug( "Inferring a new namespace declaration {} {}", name, if (path != null) { @@ -359,6 +358,54 @@ class Inference(val start: Node, override val ctx: TranslationContext) : inferred } } + + /** + * This class implements a [HasType.TypeObserver] and uses the observed type to set the + * [ValueDeclaration.declaredType] of a [ValueDeclaration], based on the types we see. It can be + * registered on objects that are used to "start" an inference, for example a + * [MemberExpression], which infers a [FieldDeclaration]. Once the type of the member expression + * becomes known, we can use this information to set the type of the field. + * + * For now, this implementation uses the first type that we "see" and once the type of our + * [declaration] is known, we ignore further updates. In a future implementation, we could try + * to fine-tune this, e.g. by finding a common type (such as an interface) that is more + * probable, if multiple types are assigned. + */ + class TypeInferenceObserver(var declaration: ValueDeclaration) : HasType.TypeObserver { + override fun typeChanged(newType: Type, src: HasType) { + // Only set a new type, if it is unknown for now + if (declaration.type is UnknownType) { + declaration.type = newType + } else { + // TODO(oxisto): We could "refine" the type here based on further type + // observations + } + } + + override fun assignedTypeChanged(assignedTypes: Set, src: HasType) { + // Only set a new type, if it is unknown for now + if (declaration.type is UnknownType) { + // For now, just set it if there is only one type + if (assignedTypes.size == 1) { + val type = assignedTypes.single() + log.debug( + "Inferring type of declaration {} to be {}", + declaration.name, + type.name + ) + + declaration.type = type + } + } else { + // TODO(oxisto): We could "refine" the type here based on further type + // observations + } + } + } + + companion object { + val log: Logger = LoggerFactory.getLogger(Inference::class.java) + } } /** Provides information about the inference status of a node. */ diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/GraphExamples.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/GraphExamples.kt index 33d664c905..adf48e0eb2 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/GraphExamples.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/GraphExamples.kt @@ -293,14 +293,14 @@ class GraphExamples { URI("conditional_expression.cpp"), Region(5, 16, 5, 17) ) - } assign literal(2, t("int")), + } assignAsExpr { literal(2, t("int")) }, ref("b") { location = PhysicalLocation( URI("conditional_expression.cpp"), Region(5, 23, 5, 24) ) - } assign literal(3, t("int")) + } assignAsExpr { literal(3, t("int")) } ) } ref("a") { diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/InferenceTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/InferenceTest.kt index be5f5f8ada..caa7ffcc94 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/InferenceTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/InferenceTest.kt @@ -27,41 +27,46 @@ package de.fraunhofer.aisec.cpg.enhancements import de.fraunhofer.aisec.cpg.GraphExamples import de.fraunhofer.aisec.cpg.assertLocalName -import de.fraunhofer.aisec.cpg.graph.byNameOrNull +import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration -import de.fraunhofer.aisec.cpg.graph.get -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertNotNull +import kotlin.test.* class InferenceTest { @Test fun testRecordInference() { - val tu = - GraphExamples.getInferenceRecord() - .components - .firstOrNull() - ?.translationUnits - ?.firstOrNull() + val result = GraphExamples.getInferenceRecord() + val tu = result.components.firstOrNull()?.translationUnits?.firstOrNull() assertNotNull(tu) - val record = tu.byNameOrNull("T") - assertNotNull(record) - assertLocalName("T", record) - assertEquals(true, record.isInferred) - assertEquals("struct", record.kind) + with(tu) { + val main = tu.functions["main"] - assertEquals(2, record.fields.size) + val valueRef = main.refs["value"] + assertNotNull(valueRef) + assertContains(valueRef.assignedTypes, primitiveType("int")) - val valueField = record.fields["value"] - assertNotNull(valueField) - assertLocalName("int", valueField.type) + val nextRef = main.refs["next"] + assertNotNull(nextRef) + assertContains(nextRef.assignedTypes, objectType("T").pointer()) - val nextField = record.fields["next"] - assertNotNull(nextField) - assertLocalName("T*", nextField.type) + val record = tu.records["T"] + assertNotNull(record) + assertLocalName("T", record) + assertEquals(true, record.isInferred) + assertEquals("struct", record.kind) + + assertEquals(2, record.fields.size) + + val valueField = record.fields["value"] + assertNotNull(valueField) + assertLocalName("int", valueField.type) + + val nextField = record.fields["next"] + assertNotNull(nextField) + assertLocalName("T*", nextField.type) + } } @Test diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/FluentTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/FluentTest.kt index 470dbdde28..63bb5d4eec 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/FluentTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/FluentTest.kt @@ -61,7 +61,8 @@ class FluentTest { elseStmt { call("printf") { literal("else") } } } } - call("do") { call("some::func") } + declare { variable("some", t("SomeClass")) } + call("do") { call("some.func") } returnStmt { ref("a") + literal(2) } } @@ -69,7 +70,6 @@ class FluentTest { } } } - val tu = result.translationUnits.firstOrNull() // Let's assert that we did this correctly val main = result.functions["main"] @@ -138,18 +138,17 @@ class FluentTest { assertNotNull(lit1) assertEquals(1, lit1.value) - // Third line is th - // e CallExpression (containing another MemberCallExpression as argument) - val call = main[2] as? CallExpression + // Fourth line is the CallExpression (containing another MemberCallExpression as argument) + val call = main[3] as? CallExpression assertNotNull(call) assertLocalName("do", call) val mce = call.arguments[0] as? MemberCallExpression assertNotNull(mce) - assertFullName("some::func", mce) + assertFullName("UNKNOWN.func", mce) - // Fourth line is the ReturnStatement - val returnStatement = main[3] as? ReturnStatement + // Fifth line is the ReturnStatement + val returnStatement = main[4] as? ReturnStatement assertNotNull(returnStatement) assertNotNull(returnStatement.scope) @@ -171,7 +170,8 @@ class FluentTest { VariableUsageResolver(result.finalCtx).accept(result.components.first()) - // Now the reference should be resolved + // Now the reference should be resolved and the MCE name set assertRefersTo(ref, variable) + assertFullName("SomeClass::func", mce) } } diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/ShortcutsTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/ShortcutsTest.kt index 1f5f0c1d02..49ba3c23a7 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/ShortcutsTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/ShortcutsTest.kt @@ -25,9 +25,7 @@ */ package de.fraunhofer.aisec.cpg.graph -import de.fraunhofer.aisec.cpg.GraphExamples -import de.fraunhofer.aisec.cpg.TranslationConfiguration -import de.fraunhofer.aisec.cpg.TranslationResult +import de.fraunhofer.aisec.cpg.* import de.fraunhofer.aisec.cpg.frontends.TestLanguage import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration @@ -36,19 +34,9 @@ import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration import de.fraunhofer.aisec.cpg.graph.statements.CompoundStatement import de.fraunhofer.aisec.cpg.graph.statements.DeclarationStatement import de.fraunhofer.aisec.cpg.graph.statements.IfStatement -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.ConstructExpression -import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression -import de.fraunhofer.aisec.cpg.graph.statements.expressions.Literal -import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberCallExpression -import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberExpression -import de.fraunhofer.aisec.cpg.graph.statements.expressions.NewExpression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.* import de.fraunhofer.aisec.cpg.passes.EdgeCachePass -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertNotNull -import kotlin.test.assertTrue +import kotlin.test.* import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.TestInstance @@ -159,15 +147,25 @@ class ShortcutsTest { fun testCallersOf() { val classDecl = shortcutClassResult.records["ShortcutClass"] assertNotNull(classDecl) - val print = classDecl.byNameOrNull("print") + val print = classDecl.methods["print"] assertNotNull(print) - val actual = shortcutClassResult.callersOf(print) - val expected = mutableListOf() - val main = classDecl.byNameOrNull("main") + val main = classDecl.functions["main"] assertNotNull(main) + + val scRefs = main.refs("sc") + scRefs.forEach { + assertNotNull(it) + assertLocalName("ShortcutClass", it.type) + } + + val printCall = main.calls["print"] + assertFullName("ShortcutClass.print", printCall) expected.add(main) + + val actual = shortcutClassResult.callersOf(print) + assertTrue(expected.containsAll(actual)) assertTrue(actual.containsAll(expected)) } @@ -193,28 +191,37 @@ class ShortcutsTest { val nestedThen = thenStatement.thenStatement as CompoundStatement expected.add(nestedThen) expected.add(nestedThen.statements[0]) - expected.add((nestedThen.statements[0] as BinaryOperator).lhs) - expected.add(((nestedThen.statements[0] as BinaryOperator).lhs as MemberExpression).base) - expected.add((nestedThen.statements[0] as BinaryOperator).rhs) + expected.add((nestedThen.statements[0] as AssignExpression).lhs.first()) + expected.add( + ((nestedThen.statements[0] as AssignExpression).lhs.first() as MemberExpression).base + ) + expected.add((nestedThen.statements[0] as AssignExpression).rhs.first()) val nestedElse = thenStatement.elseStatement as CompoundStatement expected.add(nestedElse) expected.add(nestedElse.statements[0]) - expected.add((nestedElse.statements[0] as BinaryOperator).lhs) - expected.add(((nestedElse.statements[0] as BinaryOperator).lhs as MemberExpression).base) - expected.add((nestedElse.statements[0] as BinaryOperator).rhs) + expected.add((nestedElse.statements[0] as AssignExpression).lhs.first()) + expected.add( + ((nestedElse.statements[0] as AssignExpression).lhs.first() as MemberExpression).base + ) + expected.add((nestedElse.statements[0] as AssignExpression).rhs.first()) ifStatement.elseStatement?.let { expected.add(it) } expected.add((ifStatement.elseStatement as CompoundStatement).statements[0]) expected.add( - ((ifStatement.elseStatement as CompoundStatement).statements[0] as BinaryOperator).lhs + ((ifStatement.elseStatement as CompoundStatement).statements[0] as AssignExpression) + .lhs + .first() ) expected.add( - (((ifStatement.elseStatement as CompoundStatement).statements[0] as BinaryOperator).lhs - as MemberExpression) + (((ifStatement.elseStatement as CompoundStatement).statements[0] as AssignExpression) + .lhs + .first() as MemberExpression) .base ) expected.add( - ((ifStatement.elseStatement as CompoundStatement).statements[0] as BinaryOperator).rhs + ((ifStatement.elseStatement as CompoundStatement).statements[0] as AssignExpression) + .rhs + .first() ) assertTrue(expected.containsAll(actual)) @@ -264,8 +271,9 @@ class ShortcutsTest { ((((magic2.body as CompoundStatement).statements[1] as IfStatement).elseStatement as CompoundStatement) .statements[0] - as BinaryOperator) + as AssignExpression) .lhs + .first() val paramPassed2 = aAssignment2.followPrevDFGEdgesUntilHit { it is Literal<*> } assertEquals(1, paramPassed2.fulfilled.size) @@ -279,8 +287,9 @@ class ShortcutsTest { ((((magic.body as CompoundStatement).statements[0] as IfStatement).elseStatement as CompoundStatement) .statements[0] - as BinaryOperator) + as AssignExpression) .lhs + .first() val paramPassed = attrAssignment.followPrevDFGEdgesUntilHit { it is Literal<*> } assertEquals(1, paramPassed.fulfilled.size) @@ -299,8 +308,9 @@ class ShortcutsTest { ((((magic.body as CompoundStatement).statements[0] as IfStatement).elseStatement as CompoundStatement) .statements[0] - as BinaryOperator) + as AssignExpression) .lhs + .first() val paramPassed = attrAssignment.followPrevEOGEdgesUntilHit { it is Literal<*> } assertEquals(1, paramPassed.fulfilled.size) @@ -324,9 +334,9 @@ class ShortcutsTest { val paramPassed = ifCondition.followNextEOGEdgesUntilHit { - it is BinaryOperator && + it is AssignExpression && it.operatorCode == "=" && - (it.rhs as? DeclaredReferenceExpression)?.refersTo == + (it.rhs.first() as? DeclaredReferenceExpression)?.refersTo == (ifCondition.lhs as DeclaredReferenceExpression).refersTo } assertEquals(1, paramPassed.fulfilled.size) @@ -344,8 +354,9 @@ class ShortcutsTest { ((((magic.body as CompoundStatement).statements[0] as IfStatement).elseStatement as CompoundStatement) .statements[0] - as BinaryOperator) + as AssignExpression) .lhs + .first() val paramPassed = attrAssignment.followPrevDFG { it is Literal<*> } assertNotNull(paramPassed) diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/AssignExpressionTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/AssignExpressionTest.kt index 9d59e76ad2..a99c3f8647 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/AssignExpressionTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/AssignExpressionTest.kt @@ -25,7 +25,6 @@ */ package de.fraunhofer.aisec.cpg.graph.statements.expressions -import de.fraunhofer.aisec.cpg.assertLocalName import de.fraunhofer.aisec.cpg.frontends.TestLanguageFrontend import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.builder.function @@ -47,12 +46,12 @@ class AssignExpressionTest { val stmt = newAssignExpression(lhs = listOf(refA), rhs = listOf(refB)) // Type listeners should be configured - assertContains(refB.typeListeners, stmt) + assertContains(refB.typeObservers, stmt) - // Suddenly, we now we know the type of b. + // Suddenly, we now we know the type of "b". refB.type = objectType("MyClass") - // It should now propagate to a - assertLocalName("MyClass", refA.type) + // It should now propagate to the assigned type of "a" + assertContains(refA.assignedTypes, objectType("MyClass")) val assignments = stmt.assignments assertEquals(1, assignments.size) @@ -70,6 +69,7 @@ class AssignExpressionTest { "func", returnTypes = listOf(objectType("MyClass"), objectType("error")) ) + function("main") { val refA = newDeclaredReferenceExpression("a") val refErr = newDeclaredReferenceExpression("err") @@ -89,31 +89,36 @@ class AssignExpressionTest { } val tu = result.translationUnits.firstOrNull() - val call = tu.calls["func"] - val func = tu.functions["func"] - val refA = tu.refs["a"] - val refErr = tu.refs["err"] - - assertNotNull(call) - assertNotNull(func) - assertNotNull(refA) - assertNotNull(refErr) - - // This should now set the correct type of the call expression - call.invokes = listOf(func) - assertIs(call.type) - - assertLocalName("MyClass", refA.type) - assertLocalName("error", refErr.type) - - // Invoke the DFG pass - DFGPass(ctx).accept(result.components.first()) - - assertTrue(refA.prevDFG.contains(call)) - assertTrue(refErr.prevDFG.contains(call)) - - val assignments = tu.assignments - assertEquals(2, assignments.size) + with(tu) { + val call = tu.calls["func"] + val func = tu.functions["func"] + val refA = tu.refs["a"] + val refErr = tu.refs["err"] + + assertNotNull(call) + assertNotNull(func) + assertNotNull(refA) + assertNotNull(refErr) + + // This should now set the correct type of the call expression + call.invokes = listOf(func) + assertIs(call.type) + + // We should at least know the "assigned" type of the references. Their declared + // type is + // still unknown to us, because we don't know the declarations. + assertContains(refA.assignedTypes, objectType("MyClass")) + assertContains(refErr.assignedTypes, objectType("error")) + + // Invoke the DFG pass + DFGPass(ctx).accept(result.components.first()) + + assertTrue(refA.prevDFG.contains(call)) + assertTrue(refErr.prevDFG.contains(call)) + + val assignments = tu.assignments + assertEquals(2, assignments.size) + } } } } diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/types/TypePropagationTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/types/TypePropagationTest.kt index 2faaebd0ab..22449001cd 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/types/TypePropagationTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/types/TypePropagationTest.kt @@ -25,14 +25,17 @@ */ package de.fraunhofer.aisec.cpg.graph.types +import de.fraunhofer.aisec.cpg.* import de.fraunhofer.aisec.cpg.frontends.TestLanguageFrontend import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.builder.* import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration import de.fraunhofer.aisec.cpg.graph.statements.CompoundStatement -import de.fraunhofer.aisec.cpg.graph.statements.DeclarationStatement -import de.fraunhofer.aisec.cpg.graph.statements.expressions.BinaryOperator +import de.fraunhofer.aisec.cpg.graph.statements.ReturnStatement +import de.fraunhofer.aisec.cpg.graph.statements.expressions.AssignExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression +import de.fraunhofer.aisec.cpg.passes.ControlFlowSensitiveDFGPass +import de.fraunhofer.aisec.cpg.passes.EvaluationOrderGraphPass import de.fraunhofer.aisec.cpg.passes.VariableUsageResolver import kotlin.test.* @@ -59,13 +62,22 @@ class TypePropagationTest { } } - val binaryOp = - (((result.functions["main"]?.body as? CompoundStatement)?.statements?.get(2) - as? DeclarationStatement) - ?.singleDeclaration as? VariableDeclaration) - ?.initializer + VariableUsageResolver(result.finalCtx).accept(result.components.first()) + + val intVar = result.variables["intVar"] + assertNotNull(intVar) + assertLocalName("int", intVar.type) + + val intVarRef = result.refs["intVar"] + assertNotNull(intVarRef) + assertLocalName("int", intVarRef.type) + val addResult = result.variables["addResult"] + assertNotNull(addResult) + + val binaryOp = addResult.initializer assertNotNull(binaryOp) + assertTrue(binaryOp.type is IntegerType) assertEquals("int", (binaryOp.type as IntegerType).name.toString()) assertEquals(32, (binaryOp.type as IntegerType).bitWidth) @@ -73,9 +85,24 @@ class TypePropagationTest { @Test fun testAssignTypePropagation() { - // TODO: This test is related to issue 1071 (it models case 2). + val frontend = TestLanguageFrontend() + + /** + * This roughly represents the following program in C: + * ```c + * int main() { + * int intVar; + * short shortVar; + * shortVar = intVar; + * return shortVar; + * } + * ``` + * + * `shortVar` and `intVar` should hold `short` and `int` as their respective [HasType.type]. + * The assignment will then propagate `int` as the [HasType.assignedTypes] to `shortVar`. + */ val result = - TestLanguageFrontend().build { + frontend.build { translationResult { translationUnit("test") { function("main", t("int")) { @@ -83,39 +110,312 @@ class TypePropagationTest { declare { variable("intVar", t("int")) {} } declare { variable("shortVar", t("short")) {} } ref("shortVar") assign ref("intVar") - returnStmt { literal(0) } + returnStmt { ref("shortVar") } } } } } } + VariableUsageResolver(result.finalCtx).accept(result.components.first()) + EvaluationOrderGraphPass(result.finalCtx) + .accept(result.components.first().translationUnits.first()) + ControlFlowSensitiveDFGPass(result.finalCtx) + .accept(result.components.first().translationUnits.first()) - val binaryOp = - (result.functions["main"]?.body as? CompoundStatement)?.statements?.get(2) - as? BinaryOperator - assertNotNull(binaryOp) + with(frontend) { + val main = result.functions["main"] + assertNotNull(main) - val rhs = binaryOp.rhs as? DeclaredReferenceExpression - assertNotNull(rhs) - assertTrue(rhs.type is IntegerType) - assertEquals("int", (rhs.type as IntegerType).name.toString()) - assertEquals(32, (rhs.type as IntegerType).bitWidth) + val assign = (main.body as? CompoundStatement)?.statements?.get(2) as? AssignExpression + assertNotNull(assign) - assertTrue(binaryOp.type is IntegerType) - assertEquals("short", (binaryOp.type as IntegerType).name.toString()) - assertEquals(16, (binaryOp.type as IntegerType).bitWidth) - - val lhs = binaryOp.lhs as? DeclaredReferenceExpression - assertNotNull(lhs) - assertTrue(lhs.type is IntegerType) - assertEquals("short", (lhs.type as IntegerType).name.toString()) - assertEquals(16, (lhs.type as IntegerType).bitWidth) - - val refersTo = lhs.refersTo as? VariableDeclaration - assertNotNull(refersTo) - assertTrue(refersTo.type is IntegerType) - assertEquals("short", (refersTo.type as IntegerType).name.toString()) - assertEquals(16, (refersTo.type as IntegerType).bitWidth) + val shortVar = main.variables["shortVar"] + assertNotNull(shortVar) + // At this point, shortVar should only have "short" as type and assigned types + assertEquals(primitiveType("short"), shortVar.type) + assertEquals(setOf(primitiveType("short")), shortVar.assignedTypes) + + val rhs = assign.rhs.firstOrNull() as? DeclaredReferenceExpression + assertNotNull(rhs) + assertIs(rhs.type) + assertLocalName("int", rhs.type) + assertEquals(32, (rhs.type as IntegerType).bitWidth) + + val shortVarRefLhs = assign.lhs.firstOrNull() as? DeclaredReferenceExpression + assertNotNull(shortVarRefLhs) + // At this point, shortVar was target of an assignment of an int variable, however, the + // int gets truncated into a short, so only short is part of the assigned types. + assertEquals(primitiveType("short"), shortVarRefLhs.type) + assertEquals(setOf(primitiveType("short")), shortVarRefLhs.assignedTypes) + + val shortVarRefReturnValue = + main.allChildren().firstOrNull()?.returnValue + assertNotNull(shortVarRefReturnValue) + // Finally, the assigned types should propagate along the DFG + assertEquals(setOf(primitiveType("short")), shortVarRefLhs.assignedTypes) + + val refersTo = shortVarRefLhs.refersTo as? VariableDeclaration + assertNotNull(refersTo) + assertIs(refersTo.type) + assertLocalName("short", refersTo.type) + assertEquals(16, (refersTo.type as IntegerType).bitWidth) + } + } + + @Test + fun testNewPropagation() { + val frontend = TestLanguageFrontend() + + /** + * This roughly represents the following C++ code: + * ```cpp + * int main() { + * BaseClass *b = new DerivedClass(); + * b.doSomething(); + * } + * ``` + */ + val result = + frontend.build { + translationResult { + translationUnit("test") { + function("main", t("int")) { + body { + declare { + variable("b", t("BaseClass").pointer()) { + new { + construct("DerivedClass") + type = t("DerivedClass").pointer() + } + } + } + call("b.doSomething") + } + } + } + } + } + + VariableUsageResolver(result.finalCtx).accept(result.components.first()) + + with(frontend) { + val main = result.functions["main"] + assertNotNull(main) + + val b = main.variables["b"] + assertNotNull(b) + assertEquals(objectType("BaseClass").pointer(), b.type) + assertEquals( + setOf( + objectType("BaseClass").pointer(), + objectType("DerivedClass").pointer(), + ), + b.assignedTypes + ) + + val bRef = main.refs["b"] + assertNotNull(bRef) + assertEquals(b.type, bRef.type) + assertEquals(b.assignedTypes, bRef.assignedTypes) + } + } + + @Test + fun testComplexPropagation() { + val frontend = + TestLanguageFrontend( + ctx = + TranslationContext( + TranslationConfiguration.builder().defaultPasses().build(), + ScopeManager(), + TypeManager() + ) + ) + + /** + * This is a more complex scenario in which we want to follow some of the data-flows and see + * how the types propagate. + * + * This roughly represents the following C++ code: + * ```cpp + * class BaseClass { + * virtual void doSomething(); + * }; + * class DerivedClassA : public BaseClass { + * void doSomething(); + * }; + * class DerivedClassB : public BaseClass { + * void doSomething(); + * }; + * + * BaseClass *create(bool flip) + * { + * // Create memory for a pointer to the base class + * BaseClass *b; + * // Create either DerivedClassA or DerivedClassB. This should assign both to the assigned types + * b = (flip == true) ? (BaseClass *)new DerivedClassA() : (BaseClass *)new DerivedClassB(); + * + * // Create a new array of pointers and assign our base class pointer to it + * auto bb = {b} + * + * // Return the first element again with an array subscription expression + * return bb[0]; + * } + * + * int main() { + * // Call the create function. We don't know which of the derived classes we return + * BaseClass *b = create(random); + * b->doSomething(); + * } + * ``` + */ + val result = + frontend.build { + translationResult { + translationUnit("test") { + record("BaseClass") { method("doSomething") } + record("DerivedClassA") { + superClasses = mutableListOf(t("BaseClass")) + method("doSomething") + } + record("DerivedClassB") { + superClasses = mutableListOf(t("BaseClass")) + method("doSomething") + } + function("create", t("BaseClass").pointer().pointer()) { + param("flip", t("bool")) + body { + declare { variable("b", t("BaseClass").pointer()) } + ref("b") assign + { + conditional( + ref("flip") eq literal(true), + cast(t("BaseClass").pointer()) { + new { + construct("DerivedClassA") + type = t("DerivedClassA").pointer() + } + }, + cast(t("BaseClass").pointer()) { + new { + construct("DerivedClassB") + type = t("DerivedClassB").pointer() + } + }, + ) + } + declare { + variable("bb", autoType()) { + ile(t("BaseClass").pointer().array()) { ref("b") } + } + } + returnStmt { + ase { + ref("bb") + literal(1) + } + } + } + } + function("main", t("int")) { + body { + declare { variable("random", t("bool")) } + declare { + variable("b", t("BaseClass").pointer()) { + call("create") { ref("random") } + } + } + call("b.doSomething") + } + } + } + } + } + + val baseClass = result.records["BaseClass"] + assertNotNull(baseClass) + + val derivedClassA = result.records["DerivedClassA"] + assertNotNull(derivedClassA) + assertContains(derivedClassA.superTypeDeclarations, baseClass) + + val derivedClassB = result.records["DerivedClassB"] + assertNotNull(derivedClassB) + assertContains(derivedClassB.superTypeDeclarations, baseClass) + + val create = result.functions["create"] + assertNotNull(create) + + with(create) { + val b = variables["b"] + assertNotNull(b) + assertEquals(objectType("BaseClass").pointer(), b.type) + + val bRefs = refs("b") + bRefs.forEach { + // The "type" of a reference must always be the same as its declaration + assertEquals(b.type, it.type) + // The assigned types should now contain both classes and the base class + assertEquals( + setOf( + objectType("BaseClass").pointer(), + objectType("DerivedClassA").pointer(), + objectType("DerivedClassB").pointer() + ), + it.assignedTypes + ) + } + + val assign = (body as CompoundStatement).statements(1) + assertNotNull(assign) + + val bb = variables["bb"] + assertNotNull(bb) + // Auto type based on the initializer's type + assertEquals(objectType("BaseClass").pointer().array(), bb.type) + // Assigned types should additionally contain our two derived classes + assertEquals( + setOf( + objectType("BaseClass").pointer().array(), + objectType("DerivedClassA").pointer().array(), + objectType("DerivedClassB").pointer().array() + ), + bb.assignedTypes + ) + + val returnStatement = (body as CompoundStatement).statements(3) + assertNotNull(returnStatement) + + val returnValue = returnStatement.returnValue + assertNotNull(returnValue) + assertEquals(objectType("BaseClass").pointer(), returnValue.type) + // The assigned types should now contain both classes and the base class in a non-array + // form, since we are using a single element of the array + assertEquals( + setOf( + objectType("BaseClass").pointer(), + objectType("DerivedClassA").pointer(), + objectType("DerivedClassB").pointer() + ), + returnValue.assignedTypes + ) + + // At this point we stop for now since we do not properly propagate the types across + // functions (yet) + } + + val main = result.functions["main"] + assertNotNull(main) + + with(main) { + val createCall = main.calls["create"] + assertNotNull(createCall) + assertContains(createCall.invokes, create) + + val b = main.variables["b"] + assertNotNull(b) + assertEquals(objectType("BaseClass").pointer(), b.type) + } } } diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/helpers/SubgraphWalkerTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/helpers/SubgraphWalkerTest.kt index 3bbc46d397..cb8f7c3b37 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/helpers/SubgraphWalkerTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/helpers/SubgraphWalkerTest.kt @@ -63,7 +63,6 @@ internal class SubgraphWalkerTest : BaseTest() { .disableCleanup() .debugParser(true) .failOnError(true) - .typeSystemActiveInFrontend(false) .useParallelFrontends(true) .defaultLanguages() .registerLanguage(TestLanguage(".")) diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/DFGTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/DFGTest.kt index ec7ff3098c..db88bc6721 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/DFGTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/DFGTest.kt @@ -125,7 +125,7 @@ class DFGTest { val result = GraphExamples.getDelayedAssignmentAfterRHS() val binaryOperatorAssignment = - TestUtils.findByUniqueName(result.allChildren(), "=") + TestUtils.findByUniqueName(result.allChildren(), "=") assertNotNull(binaryOperatorAssignment) val binaryOperatorAddition = @@ -138,7 +138,7 @@ class DFGTest { val varB = TestUtils.findByUniqueName(result.variables, "b") assertNotNull(varB) - val lhsA = binaryOperatorAssignment.lhs as DeclaredReferenceExpression + val lhsA = binaryOperatorAssignment.lhs.first() as DeclaredReferenceExpression val rhsA = binaryOperatorAddition.lhs as DeclaredReferenceExpression val b = TestUtils.findByUniqueName(result.refs, "b") assertNotNull(b) diff --git a/cpg-core/src/testFixtures/kotlin/de/fraunhofer/aisec/cpg/TestUtils.kt b/cpg-core/src/testFixtures/kotlin/de/fraunhofer/aisec/cpg/TestUtils.kt index d36e20e5b1..79435e8922 100644 --- a/cpg-core/src/testFixtures/kotlin/de/fraunhofer/aisec/cpg/TestUtils.kt +++ b/cpg-core/src/testFixtures/kotlin/de/fraunhofer/aisec/cpg/TestUtils.kt @@ -131,7 +131,6 @@ object TestUtils { .disableCleanup() .debugParser(true) .failOnError(true) - .typeSystemActiveInFrontend(false) .useParallelFrontends(true) .defaultLanguages() if (usePasses) { diff --git a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CLanguage.kt b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CLanguage.kt index c9960715ed..5d7f0cc4cb 100644 --- a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CLanguage.kt +++ b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CLanguage.kt @@ -93,8 +93,22 @@ open class CLanguage : "long double" to FloatingPointType("long double", 128, this, NumericType.Modifier.SIGNED), - // Convenience types, defined in + // Convenience types, defined in headers such as or . They are not + // part of the language per se, but part of the standard library. We therefore also + // consider them to be "built-in" types, because we often don't parse all the headers + // which define them internally. "bool" to IntegerType("bool", 1, this, NumericType.Modifier.SIGNED), + "int8_t" to IntegerType("int8_t", 8, this, NumericType.Modifier.SIGNED), + "int16_t" to IntegerType("int16_t", 16, this, NumericType.Modifier.SIGNED), + "int32_t" to IntegerType("int32_t", 32, this, NumericType.Modifier.SIGNED), + "int64_t" to IntegerType("int64_t", 64, this, NumericType.Modifier.SIGNED), + "uint8_t" to IntegerType("uint8_t", 8, this, NumericType.Modifier.UNSIGNED), + "uint16_t" to IntegerType("uint16_t", 16, this, NumericType.Modifier.UNSIGNED), + "uint32_t" to IntegerType("uint32_t", 32, this, NumericType.Modifier.UNSIGNED), + "uint64_t" to IntegerType("uint64_t", 64, this, NumericType.Modifier.UNSIGNED), + + // Other commonly used extension types + "__int128" to IntegerType("__int128", 128, this, NumericType.Modifier.SIGNED), ) override fun refineNormalCallResolution( @@ -139,6 +153,6 @@ open class CLanguage : listOf( *ctx.scopeManager.resolveFunctionStopScopeTraversalOnDefinition(call).toTypedArray() ) - return resolveWithImplicitCast(call, initialInvocationCandidates, ctx) + return resolveWithImplicitCast(call, initialInvocationCandidates) } } diff --git a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CPPLanguage.kt b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CPPLanguage.kt index 8c22732914..03ade6b6a3 100644 --- a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CPPLanguage.kt +++ b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CPPLanguage.kt @@ -78,6 +78,7 @@ class CPPLanguage : "signed char" to IntegerType("signed char", 8, this, NumericType.Modifier.SIGNED), "unsigned char" to IntegerType("unsigned char", 8, this, NumericType.Modifier.UNSIGNED), "char" to IntegerType("char", 8, this, NumericType.Modifier.NOT_APPLICABLE), + "wchar_t" to IntegerType("wchar_t", 32, this, NumericType.Modifier.NOT_APPLICABLE), "char8_t" to IntegerType("char8_t", 8, this, NumericType.Modifier.NOT_APPLICABLE), "char16_t" to IntegerType("char16_t", 16, this, NumericType.Modifier.NOT_APPLICABLE), "char32_t" to IntegerType("char32_t", 32, this, NumericType.Modifier.NOT_APPLICABLE), @@ -88,8 +89,21 @@ class CPPLanguage : "long double" to FloatingPointType("long double", 128, this, NumericType.Modifier.SIGNED), - // Some convenience types + // Convenience types, defined in headers. They are not part of the language per se, but + // part of the standard library. We therefore also consider them to be "built-in" types, + // because we often don't parse all the headers which define them internally. "std::string" to StringType("std::string", this), + "int8_t" to IntegerType("int8_t", 8, this, NumericType.Modifier.SIGNED), + "int16_t" to IntegerType("int16_t", 16, this, NumericType.Modifier.SIGNED), + "int32_t" to IntegerType("int32_t", 32, this, NumericType.Modifier.SIGNED), + "int64_t" to IntegerType("int64_t", 64, this, NumericType.Modifier.SIGNED), + "uint8_t" to IntegerType("uint8_t", 8, this, NumericType.Modifier.UNSIGNED), + "uint16_t" to IntegerType("uint16_t", 16, this, NumericType.Modifier.UNSIGNED), + "uint32_t" to IntegerType("uint32_t", 32, this, NumericType.Modifier.UNSIGNED), + "uint64_t" to IntegerType("uint64_t", 64, this, NumericType.Modifier.UNSIGNED), + + // Other commonly used extension types + "__int128" to IntegerType("__int128", 128, this, NumericType.Modifier.SIGNED), ) /** @@ -174,8 +188,7 @@ class CPPLanguage : call, recordDeclaration.methods.filter { m -> namePattern.matcher(m.name).matches() /*&& !m.isImplicit()*/ - }, - ctx + } ) ) } diff --git a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXHandler.kt b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXHandler.kt index 3b2cba1412..abe28bed70 100644 --- a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXHandler.kt +++ b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXHandler.kt @@ -37,6 +37,7 @@ abstract class CXXHandler( configConstructor: Supplier, lang: CXXLanguageFrontend ) : Handler(configConstructor, lang) { + /** * We intentionally override the logic of [Handler.handle] because we do not want the map-based * logic, but rather want to make use of the Kotlin-when syntax. @@ -65,6 +66,8 @@ abstract class CXXHandler( frontend.setComment(node, ctx) frontend.process(ctx, node) + this.lastNode = node + return node } diff --git a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXLanguageFrontend.kt b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXLanguageFrontend.kt index 0e9d379fe9..69276887ca 100644 --- a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXLanguageFrontend.kt +++ b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXLanguageFrontend.kt @@ -32,10 +32,7 @@ import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend import de.fraunhofer.aisec.cpg.frontends.TranslationException import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.Annotation -import de.fraunhofer.aisec.cpg.graph.declarations.ConstructorDeclaration -import de.fraunhofer.aisec.cpg.graph.declarations.Declaration -import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration -import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration +import de.fraunhofer.aisec.cpg.graph.declarations.* import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression import de.fraunhofer.aisec.cpg.graph.types.* import de.fraunhofer.aisec.cpg.helpers.Benchmark @@ -206,6 +203,7 @@ class CXXLanguageFrontend(language: Language, ctx: Translat val symbols: HashMap = HashMap() symbols.putAll(config.symbols) + includePaths.addAll(config.includePaths.map { it.toAbsolutePath().toString() }) config.compilationDatabase?.getIncludePaths(file)?.let { includePaths.addAll(it) } @@ -556,20 +554,33 @@ class CXXLanguageFrontend(language: Language, ctx: Translat val name = specifier.rawSignature return when { - // auto type; we model this as an unknown type. Maybe in the future, we will - // differentiate between unknown types and "auto-deduced" types + // auto type specifier.type == IASTSimpleDeclSpecifier.t_auto -> { - unknownType() + autoType() } // void type specifier.type == IASTSimpleDeclSpecifier.t_void -> { IncompleteType() } + // __typeof__ type + specifier.type == IASTSimpleDeclSpecifier.t_typeof -> { + objectType("typeof(${specifier.declTypeExpression.rawSignature})") + } + // A decl type + specifier.type == IASTSimpleDeclSpecifier.t_decltype -> { + objectType("decltype(${specifier.declTypeExpression.rawSignature})") + } // The type of constructor declaration is always the declaration itself specifier.type == IASTSimpleDeclSpecifier.t_unspecified && hint is ConstructorDeclaration -> { hint.name.parent?.let { objectType(it) } ?: unknownType() } + // The type of conversion operator is also always the declaration itself + specifier.type == IASTSimpleDeclSpecifier.t_unspecified && + hint is MethodDeclaration && + hint.name.localName == "operator#0" -> { + hint.name.parent?.let { objectType(it) } ?: unknownType() + } // C (not C++) allows unspecified types in function declarations, they // default to int and usually produce a warning name == "" && language !is CPPLanguage -> { @@ -595,7 +606,18 @@ class CXXLanguageFrontend(language: Language, ctx: Translat // We need to remove qualifiers such as "const" from the name here, because // we model them as part of the variable declaration and not the type, so use // the "canonical" name - primitiveType(specifier.canonicalName) + val canonicalName = specifier.canonicalName + if (canonicalName == "") { + Util.errorWithFileLocation( + this, + specifier, + log, + "Could not determine canonical name for potential primitive type $name" + ) + newProblemType() + } else { + primitiveType(canonicalName) + } } } } @@ -777,14 +799,17 @@ private val IASTSimpleDeclSpecifier.canonicalName: CharSequence // Last part is the actual type (int, float, ...) when (type) { IASTSimpleDeclSpecifier.t_char -> parts += "char" + IASTSimpleDeclSpecifier.t_wchar_t -> parts += "wchar_t" IASTSimpleDeclSpecifier.t_char16_t -> parts += "char16_t" - IASTSimpleDeclSpecifier.t_char32_t -> parts += "chat32_t" + IASTSimpleDeclSpecifier.t_char32_t -> parts += "char32_t" IASTSimpleDeclSpecifier.t_int -> parts += "int" IASTSimpleDeclSpecifier.t_float -> parts += "float" IASTSimpleDeclSpecifier.t_double -> parts += "double" IASTSimpleDeclSpecifier.t_bool -> parts += "bool" IASTSimpleDeclSpecifier.t_unspecified -> { - // nothing to do + if (isSigned || isUnsigned) { + parts += "int" + } } IASTSimpleDeclSpecifier.t_auto -> parts = mutableListOf("auto") else -> { diff --git a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/DeclarationHandler.kt b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/DeclarationHandler.kt index 88a1faafe9..a70d6c933f 100644 --- a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/DeclarationHandler.kt +++ b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/DeclarationHandler.kt @@ -32,6 +32,8 @@ import de.fraunhofer.aisec.cpg.graph.scopes.TemplateScope import de.fraunhofer.aisec.cpg.graph.statements.CompoundStatement import de.fraunhofer.aisec.cpg.graph.statements.ReturnStatement import de.fraunhofer.aisec.cpg.graph.statements.Statement +import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.UnaryOperator import de.fraunhofer.aisec.cpg.graph.types.* import java.util.function.Supplier import org.eclipse.cdt.core.dom.ast.* @@ -410,14 +412,113 @@ class DeclarationHandler(lang: CXXLanguageFrontend) : } private fun handleSimpleDeclaration(ctx: IASTSimpleDeclaration): Declaration { - var primaryDeclaration: Declaration? = null val sequence = DeclarationSequence() + val declSpecifier = ctx.declSpecifier + + // check, whether the declaration specifier also contains declarations, e.g. class + // definitions or enums + val (primaryDeclaration, useNameOfDeclarator) = + handleDeclarationSpecifier(declSpecifier, ctx, sequence) + + // Fill template params, if needed + val templateParams: List? = extractTemplateParams(ctx, declSpecifier) + + // Loop through all declarators, as we can potentially have multiple declarations here + for (declarator in ctx.declarators) { + // If a previous step informed us that we should take the name of the primary + // declaration, we do so here. This most likely is the case of a typedef struct. + val declSpecifierToUse = + if (useNameOfDeclarator && declSpecifier is IASTCompositeTypeSpecifier) { + val copy = declSpecifier.copy() + copy.name = CPPASTName(primaryDeclaration?.name?.toString()?.toCharArray()) + copy + } else { + declSpecifier + } + + var type: Type + + if (ctx.isTypedef) { + type = frontend.typeOf(declarator, declSpecifierToUse) + + // Handle typedefs. + val declaration = handleTypedef(declarator, ctx, type) + + sequence.addDeclaration(declaration) + } else { + // Parse the declaration first, so we can supply the declaration as a hint to + // the typeOf function. + val declaration = + frontend.declaratorHandler.handle(declarator) as? ValueDeclaration ?: continue + + // Parse the type (with some hints) + type = frontend.typeOf(declarator, declSpecifierToUse, declaration) + + // For function *declarations*, we need to update the return types based on the + // function type. For function *definitions*, this is done in + // [handleFunctionDefinition]. + if (declaration is FunctionDeclaration) { + declaration.returnTypes = + (type as? FunctionType)?.returnTypes ?: listOf(IncompleteType()) + } + + // We also need to set the type, based on the declarator type. + declaration.type = type + + // process attributes + frontend.processAttributes(declaration, ctx) + sequence.addDeclaration(declaration) + + // We want to make sure that we parse the initializer *after* we have set the + // type. This has several advantages: + // * This way we can deduce, whether our initializer needs to have the + // declared type (in case of a ConstructExpression); + // * or if the declaration needs to have the same type as the initializer (when + // an auto-type is used). The latter case is done internally by the + // VariableDeclaration class and its type observer. + // * Additionally, it makes sure that the type is known before parsing the + // initializer. This allows us to guess cast vs. call expression in the + // initializer. + if (declaration is VariableDeclaration) { + // Set template parameters of the variable (if any) + declaration.templateParameters = templateParams + + // Parse the initializer, if we have one + declarator.initializer?.let { + val initializer = frontend.initializerHandler.handle(it) + when { + // We need to set a resolution "helper" for function pointers, so that a + // reference to this declaration can resolve the function pointer (using + // the type of this declaration). The typical (and only) scenario we + // support here is the assignment of a `&ref` as initializer. + initializer is UnaryOperator && type is FunctionPointerType -> { + val ref = initializer.input as? DeclaredReferenceExpression + ref?.resolutionHelper = declaration + } + } + + declaration.initializer = initializer + } + } + } + } + + return simplifySequence(sequence) + } + /** + * In C++, a [IASTDeclSpecifier] can potentially also contain declarations, e.g. records or + * enums. This function gathers these [Declaration] nodes, before processing the remainder of + * declarations within [IASTSimpleDeclaration.getDeclarators]. + */ + private fun handleDeclarationSpecifier( + declSpecifier: IASTDeclSpecifier?, + ctx: IASTSimpleDeclaration, + sequence: DeclarationSequence + ): Pair { + var primaryDeclaration: Declaration? = null var useNameOfDeclarator = false - // check, whether the declaration specifier also contains declarations, i.e. class - // definitions - val declSpecifier = ctx.declSpecifier when (declSpecifier) { is IASTCompositeTypeSpecifier -> { primaryDeclaration = @@ -444,6 +545,7 @@ class DeclarationHandler(lang: CXXLanguageFrontend) : } } frontend.processAttributes(primaryDeclaration, ctx) + sequence.addDeclaration(primaryDeclaration) } } @@ -463,72 +565,43 @@ class DeclarationHandler(lang: CXXLanguageFrontend) : } } + return Pair(primaryDeclaration, useNameOfDeclarator) + } + + /** + * Extracts template parameters (used for [VariableDeclaration.templateParameters] out of the + * declaration (if it has any), otherwise null is returned. + */ + private fun extractTemplateParams( + ctx: IASTSimpleDeclaration, + declSpecifier: IASTDeclSpecifier, + ): MutableList? { if ( !ctx.isTypedef && declSpecifier is CPPASTNamedTypeSpecifier && declSpecifier.name is CPPASTTemplateId ) { - handleTemplateUsage(declSpecifier, ctx, sequence) - } else { - for (declarator in ctx.declarators) { - // If a previous step informed us that we should take the name of the primary - // declaration, we do so here. This most likely is the case of a typedef struct. - val declSpecifierToUse = - if (useNameOfDeclarator && declSpecifier is IASTCompositeTypeSpecifier) { - val copy = declSpecifier.copy() - copy.name = CPPASTName(primaryDeclaration?.name?.toString()?.toCharArray()) - copy - } else { - declSpecifier - } - - var type: Type - - // If this is a variable declaration with initializer, it is important, that we - // parse the type first, so that the type is known before parsing the declaration. - // This allows us to guess cast vs. call expression in the initializer. - if (declarator !is IASTFunctionDeclarator && declarator.initializer != null) { - // We only need to parse it, but we are not really storing the result. That is - // not ideal, but probably the "best" way. - frontend.typeOf(declarator, declSpecifierToUse) - } - - if (ctx.isTypedef) { - type = frontend.typeOf(declarator, declSpecifierToUse) - - // Handle typedefs. - val declaration = handleTypedef(declarator, ctx, type) - - sequence.addDeclaration(declaration) - } else { - // Parse the declaration first, so we can supply the declaration as a hint to - // the typeOf function. - val declaration = - frontend.declaratorHandler.handle(declarator) as? ValueDeclaration - - type = frontend.typeOf(declarator, declSpecifierToUse, declaration) - - // For function *declarations*, we need to update the return types based on the - // function type. For function *definitions*, this is done in - // [handleFunctionDefinition]. - if (declaration is FunctionDeclaration) { - declaration.returnTypes = - (type as? FunctionType)?.returnTypes ?: listOf(IncompleteType()) - } - - if (declaration != null) { - // We also need to set the return type, based on the declarator type. - declaration.type = type - - // process attributes - frontend.processAttributes(declaration, ctx) - sequence.addDeclaration(declaration) - } + val templateParams = mutableListOf() + val templateId = declSpecifier.name as CPPASTTemplateId + for (templateArgument in templateId.templateArguments) { + if (templateArgument is CPPASTTypeId) { + val genericInstantiation = frontend.typeOf(templateArgument) + templateParams.add( + newTypeExpression( + genericInstantiation.name.toString(), + genericInstantiation, + ) + ) + } else if (templateArgument is IASTExpression) { + val expression = frontend.expressionHandler.handle(templateArgument) + expression?.let { templateParams.add(it) } } } + + return templateParams } - return simplifySequence(sequence) + return null } private fun handleTypedef( @@ -601,53 +674,6 @@ class DeclarationHandler(lang: CXXLanguageFrontend) : } } - /** - * Handles usage of Templates in SimpleDeclarations - * - * @param typeSpecifier - * @param ctx - * @param sequence - */ - private fun handleTemplateUsage( - typeSpecifier: CPPASTNamedTypeSpecifier, - ctx: IASTSimpleDeclaration, - sequence: DeclarationSequence - ) { - val templateId = typeSpecifier.name as CPPASTTemplateId - val templateParams = mutableListOf() - - for (templateArgument in templateId.templateArguments) { - if (templateArgument is CPPASTTypeId) { - val genericInstantiation = frontend.typeOf(templateArgument) - templateParams.add( - newTypeExpression( - genericInstantiation.name.toString(), - genericInstantiation, - ) - ) - } else if (templateArgument is IASTExpression) { - val expression = frontend.expressionHandler.handle(templateArgument) - expression?.let { templateParams.add(it) } - } - } - - for (declarator in ctx.declarators) { - val declaration = frontend.declaratorHandler.handle(declarator) as ValueDeclaration - - // Update Type - declaration.type = frontend.typeOf(declarator, typeSpecifier) - - // Set TemplateParameters into VariableDeclaration - if (declaration is VariableDeclaration) { - declaration.templateParameters = templateParams - } - - // process attributes - frontend.processAttributes(declaration, ctx) - sequence.addDeclaration(declaration) - } - } - private fun parseInclusions( includes: Array?, allIncludes: HashMap> diff --git a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/DeclaratorHandler.kt b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/DeclaratorHandler.kt index 7ed4122fb5..af98a6d8eb 100644 --- a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/DeclaratorHandler.kt +++ b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/DeclaratorHandler.kt @@ -35,7 +35,6 @@ import de.fraunhofer.aisec.cpg.graph.types.* import de.fraunhofer.aisec.cpg.helpers.Util import java.util.* import java.util.function.Supplier -import java.util.regex.Pattern import org.eclipse.cdt.core.dom.ast.* import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTCompositeTypeSpecifier import org.eclipse.cdt.core.dom.ast.gnu.cpp.GPPLanguage @@ -122,12 +121,6 @@ class DeclaratorHandler(lang: CXXLanguageFrontend) : implicitInitializerAllowed, ) - // Parse the initializer, if we have one - val init = ctx.initializer - if (init != null) { - declaration.initializer = frontend.initializerHandler.handle(init) - } - // Add this declaration to the current scope frontend.scopeManager.addDeclaration(declaration) @@ -402,9 +395,6 @@ class DeclaratorHandler(lang: CXXLanguageFrontend) : } private fun handleFunctionPointer(ctx: IASTFunctionDeclarator, name: String): ValueDeclaration { - val initializer = - if (ctx.initializer == null) null - else frontend.initializerHandler.handle(ctx.initializer) // unfortunately we are not told whether this is a field or not, so we have to find it out // ourselves val result: ValueDeclaration @@ -412,25 +402,18 @@ class DeclaratorHandler(lang: CXXLanguageFrontend) : if (recordDeclaration == null) { // variable result = newVariableDeclaration(name, unknownType(), ctx.rawSignature, true) - result.initializer = initializer } else { // field val code = ctx.rawSignature - val namePattern = Pattern.compile("\\((\\*|.+\\*)(?[^)]*)") - val matcher = namePattern.matcher(code) - var fieldName: String? = "" - if (matcher.find()) { - fieldName = matcher.group("name").trim() - } result = newFieldDeclaration( - fieldName, + name, unknownType(), emptyList(), code, frontend.locationOf(ctx), - initializer, - true + null, + false, ) } diff --git a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/ExpressionHandler.kt b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/ExpressionHandler.kt index a4b7597780..9e4e4e7bc4 100644 --- a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/ExpressionHandler.kt +++ b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/ExpressionHandler.kt @@ -28,8 +28,6 @@ package de.fraunhofer.aisec.cpg.frontends.cxx import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration -import de.fraunhofer.aisec.cpg.graph.edge.Properties -import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge import de.fraunhofer.aisec.cpg.graph.statements.expressions.* import de.fraunhofer.aisec.cpg.graph.types.* import de.fraunhofer.aisec.cpg.helpers.Util @@ -44,6 +42,7 @@ import org.eclipse.cdt.core.dom.ast.IASTLiteralExpression.* import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTLambdaExpression import org.eclipse.cdt.internal.core.dom.parser.c.CASTDesignatedInitializer import org.eclipse.cdt.internal.core.dom.parser.cpp.* +import org.eclipse.cdt.internal.core.model.ASTStringUtil /** * Note: CDT expresses hierarchies in Interfaces to allow to have multi-inheritance in java. Because @@ -64,8 +63,9 @@ class ExpressionHandler(lang: CXXLanguageFrontend) : is IASTFieldReference -> handleFieldReference(node) is IASTFunctionCallExpression -> handleFunctionCallExpression(node) is IASTCastExpression -> handleCastExpression(node) - is IASTInitializerList -> handleInitializerList(node) is IASTExpressionList -> handleExpressionList(node) + is IASTInitializerList -> frontend.initializerHandler.handle(node) + ?: ProblemExpression("could not parse initializer list") is IASTArraySubscriptExpression -> handleArraySubscriptExpression(node) is IASTTypeIdExpression -> handleTypeIdExpression(node) is CPPASTNewExpression -> handleNewExpression(node) @@ -103,7 +103,7 @@ class ExpressionHandler(lang: CXXLanguageFrontend) : } } - // By default, the outer variables passed by value to the lambda are unmutable. But we can + // By default, the outer variables passed by value to the lambda are immutable. But we can // either make the function "mutable" or pass everything by reference. lambda.areVariablesMutable = (node.declarator as? CPPASTFunctionDeclarator)?.isMutable == true || @@ -112,6 +112,7 @@ class ExpressionHandler(lang: CXXLanguageFrontend) : val anonymousFunction = node.declarator?.let { frontend.declaratorHandler.handle(it) as? FunctionDeclaration } ?: newFunctionDeclaration("lambda${lambda.hashCode()}") + anonymousFunction.type = FunctionType.computeType(anonymousFunction) frontend.scopeManager.enterScope(anonymousFunction) anonymousFunction.body = frontend.statementHandler.handle(node.body) @@ -211,6 +212,7 @@ class ExpressionHandler(lang: CXXLanguageFrontend) : // an implicit one initializer = newConstructExpression(t.name.localName, "${t.name.localName}()") initializer.isImplicit = true + initializer.type = t } // we also need to "forward" our template parameters (if we have any) to the construct @@ -290,8 +292,6 @@ class ExpressionHandler(lang: CXXLanguageFrontend) : if (isPrimitive(castExpression.castType) || ctx.operator == 4) { castExpression.type = castExpression.castType - } else { - castExpression.expression.registerTypeListener(castExpression) } return castExpression @@ -437,8 +437,8 @@ class ExpressionHandler(lang: CXXLanguageFrontend) : reference is CastExpression -> { // this really is a cast expression in disguise reference.expression = - handle(ctx.arguments.first()) - ?: ProblemExpression("could not parse argument for cast") + ctx.arguments.firstOrNull()?.let { handle(it) } + ?: newProblemExpression("could not parse argument for cast") return reference } else -> { @@ -476,46 +476,27 @@ class ExpressionHandler(lang: CXXLanguageFrontend) : return expressionList } - private fun handleBinaryExpression(ctx: IASTBinaryExpression): BinaryOperator { - var operatorCode = "" - when (ctx.operator) { - op_multiply -> operatorCode = "*" - op_divide -> operatorCode = "/" - op_modulo -> operatorCode = "%" - op_plus -> operatorCode = "+" - op_minus -> operatorCode = "-" - op_shiftLeft -> operatorCode = "<<" - op_shiftRight -> operatorCode = ">>" - op_lessThan -> operatorCode = "<" - op_greaterThan -> operatorCode = ">" - op_lessEqual -> operatorCode = "<=" - op_greaterEqual -> operatorCode = ">=" - op_binaryAnd -> operatorCode = "&" - op_binaryXor -> operatorCode = "^" - op_binaryOr -> operatorCode = "|" - op_logicalAnd -> operatorCode = "&&" - op_logicalOr -> operatorCode = "||" - op_assign -> operatorCode = "=" - op_multiplyAssign -> operatorCode = "*=" - op_divideAssign -> operatorCode = "/=" - op_moduloAssign -> operatorCode = "%=" - op_plusAssign -> operatorCode = "+=" - op_minusAssign -> operatorCode = "-=" - op_shiftLeftAssign -> operatorCode = "<<=" - op_shiftRightAssign -> operatorCode = ">>=" - op_binaryAndAssign -> operatorCode = "&=" - op_binaryXorAssign -> operatorCode = "^=" - op_binaryOrAssign -> operatorCode = "|=" - op_equals -> operatorCode = "==" - op_notequals -> operatorCode = "!=" - op_pmdot -> operatorCode = ".*" - op_pmarrow -> operatorCode = "->*" - op_max -> operatorCode = ">?" - op_min -> operatorCode = "?<" - op_ellipses -> operatorCode = "..." - else -> - Util.errorWithFileLocation(frontend, ctx, log, "unknown operator {}", ctx.operator) - } + private fun handleBinaryExpression(ctx: IASTBinaryExpression): Expression { + val operatorCode = + when (ctx.operator) { + op_assign, + op_multiplyAssign, + op_divideAssign, + op_moduloAssign, + op_plusAssign, + op_minusAssign, + op_shiftLeftAssign, + op_shiftRightAssign, + op_binaryAndAssign, + op_binaryXorAssign, + op_binaryOrAssign -> { + return handleAssignment(ctx) + } + op_pmdot -> ".*" + op_pmarrow -> "->*" + else -> String(ASTStringUtil.getBinaryOperatorString(ctx)) + } + val binaryOperator = newBinaryOperator(operatorCode, ctx.rawSignature) val lhs = handle(ctx.operand1) ?: newProblemExpression("could not parse lhs") val rhs = @@ -531,6 +512,25 @@ class ExpressionHandler(lang: CXXLanguageFrontend) : return binaryOperator } + private fun handleAssignment(ctx: IASTBinaryExpression): Expression { + val lhs = handle(ctx.operand1) ?: newProblemExpression("missing LHS") + val rhs = + if (ctx.operand2 != null) { + handle(ctx.operand2) + } else { + handle(ctx.initOperand2) + } + ?: newProblemExpression("missing RHS") + + val operatorCode = String(ASTStringUtil.getBinaryOperatorString(ctx)) + val assign = newAssignExpression(operatorCode, listOf(lhs), listOf(rhs), rawNode = ctx) + if (rhs is UnaryOperator && rhs.input is DeclaredReferenceExpression) { + (rhs.input as DeclaredReferenceExpression).resolutionHelper = lhs + } + + return assign + } + private fun handleLiteralExpression(ctx: IASTLiteralExpression): Expression { return when (ctx.kind) { lk_integer_constant -> handleIntegerLiteral(ctx) @@ -550,22 +550,6 @@ class ExpressionHandler(lang: CXXLanguageFrontend) : } } - private fun handleInitializerList(ctx: IASTInitializerList): InitializerListExpression { - val expression = newInitializerListExpression(ctx.rawSignature) - - for (clause in ctx.clauses) { - handle(clause)?.let { - val edge = PropertyEdge(expression, it) - edge.addProperty(Properties.INDEX, expression.initializerEdges.size) - - expression.initializerEdges.add(edge) - expression.addPrevDFG(it) - } - } - - return expression - } - private fun handleCXXDesignatedInitializer( ctx: CPPASTDesignatedInitializer ): DesignatedInitializerExpression { diff --git a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/InitializerHandler.kt b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/InitializerHandler.kt index be3311744a..dd7be1da52 100644 --- a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/InitializerHandler.kt +++ b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/InitializerHandler.kt @@ -25,10 +25,17 @@ */ package de.fraunhofer.aisec.cpg.frontends.cxx +import de.fraunhofer.aisec.cpg.graph.declarations.ValueDeclaration +import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration +import de.fraunhofer.aisec.cpg.graph.edge.Properties +import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge import de.fraunhofer.aisec.cpg.graph.newConstructExpression +import de.fraunhofer.aisec.cpg.graph.newInitializerListExpression import de.fraunhofer.aisec.cpg.graph.newProblemExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.InitializerListExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.ProblemExpression +import de.fraunhofer.aisec.cpg.graph.unknownType import java.util.function.Supplier import org.eclipse.cdt.core.dom.ast.IASTEqualsInitializer import org.eclipse.cdt.core.dom.ast.IASTInitializer @@ -41,10 +48,7 @@ class InitializerHandler(lang: CXXLanguageFrontend) : override fun handleNode(node: IASTInitializer): Expression { return when (node) { is IASTEqualsInitializer -> handleEqualsInitializer(node) - // TODO: Initializer List is handled in ExpressionsHandler that actually handles - // InitializerClauses often used where - // one expects an expression. - is IASTInitializerList -> frontend.expressionHandler.handle(node) as Expression + is IASTInitializerList -> handleInitializerList(node) is CPPASTConstructorInitializer -> handleConstructorInitializer(node) else -> { return handleNotSupported(node, node.javaClass.name) @@ -54,6 +58,8 @@ class InitializerHandler(lang: CXXLanguageFrontend) : private fun handleConstructorInitializer(ctx: CPPASTConstructorInitializer): Expression { val constructExpression = newConstructExpression(ctx.rawSignature) + constructExpression.type = + (frontend.declaratorHandler.lastNode as? VariableDeclaration)?.type ?: unknownType() for ((i, argument) in ctx.arguments.withIndex()) { val arg = frontend.expressionHandler.handle(argument) @@ -66,6 +72,28 @@ class InitializerHandler(lang: CXXLanguageFrontend) : return constructExpression } + private fun handleInitializerList(ctx: IASTInitializerList): InitializerListExpression { + // Because an initializer list expression is used for many different things, it is important + // for us to know which kind of variable (or rather of which kind), we are initializing. + // This information can be found in the lastNode property of our declarator handler. + val targetType = + (frontend.declaratorHandler.lastNode as? ValueDeclaration)?.type ?: unknownType() + + val expression = newInitializerListExpression(targetType, ctx.rawSignature) + + for (clause in ctx.clauses) { + frontend.expressionHandler.handle(clause)?.let { + val edge = PropertyEdge(expression, it) + edge.addProperty(Properties.INDEX, expression.initializerEdges.size) + + expression.initializerEdges.add(edge) + expression.addPrevDFG(it) + } + } + + return expression + } + private fun handleEqualsInitializer(ctx: IASTEqualsInitializer): Expression { return frontend.expressionHandler.handle(ctx.initializerClause) ?: return newProblemExpression("could not parse initializer clause") diff --git a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/PerformanceRegressionTest.kt b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/PerformanceRegressionTest.kt index 03b473a6af..c8b4fcb8ae 100644 --- a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/PerformanceRegressionTest.kt +++ b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/PerformanceRegressionTest.kt @@ -73,7 +73,6 @@ class PerformanceRegressionTest { // enough for those special moments where for some reasons the GitHub runners // are slowing down (maybe because of some hidden quota). it.useParallelFrontends(false) - it.typeSystemActiveInFrontend(true) } assertNotNull(tu) } diff --git a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/calls/FunctionPointerTest.kt b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/calls/FunctionPointerTest.kt index 7ac666565a..a911cf74a5 100644 --- a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/calls/FunctionPointerTest.kt +++ b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/calls/FunctionPointerTest.kt @@ -102,7 +102,9 @@ internal class FunctionPointerTest : BaseTest() { "no_param", "no_param_uninitialized", "no_param_field", - "no_param_field_uninitialized" -> assertEquals(listOf(noParam), call.invokes) + "no_param_field_uninitialized" -> { + assertEquals(listOf(noParam), call.invokes) + } "single_param", "single_param_uninitialized", "single_param_field", diff --git a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/templates/FunctionTemplateTest.kt b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/templates/FunctionTemplateTest.kt index 102ae391e7..a9dfa42cd6 100644 --- a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/templates/FunctionTemplateTest.kt +++ b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/templates/FunctionTemplateTest.kt @@ -38,10 +38,7 @@ import de.fraunhofer.aisec.cpg.graph.statements.expressions.* import de.fraunhofer.aisec.cpg.graph.types.* import java.nio.file.Path import java.util.function.Predicate -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertNotNull -import kotlin.test.assertTrue +import kotlin.test.* internal class FunctionTemplateTest : BaseTest() { private val topLevel = Path.of("src", "test", "resources", "templates", "functiontemplates") @@ -55,18 +52,17 @@ internal class FunctionTemplateTest : BaseTest() { topLevel, true ) - val variableDeclarations = result.variables - val x = findByUniqueName(variableDeclarations, "x") - assertEquals(UnknownType.getUnknownType(CPPLanguage()), x.type) - - val declaredReferenceExpressions = result.refs - val xDeclaredReferenceExpression = findByUniqueName(declaredReferenceExpressions, "x") - assertEquals(UnknownType.getUnknownType(CPPLanguage()), xDeclaredReferenceExpression.type) - - val binaryOperators = result.allChildren() - val dependentOperation = - findByUniquePredicate(binaryOperators) { b: BinaryOperator -> b.code == "val * N" } - assertEquals(UnknownType.getUnknownType(CPPLanguage()), dependentOperation.type) + val x = result.variables["x"] + assertNotNull(x) + assertIs(x.type) + + val xRef = result.refs["x"] + assertNotNull(xRef) + assertIs(xRef.type) + + val binOp = result.allChildren()[{ it.code == "val * N" }] + assertNotNull(binOp) + assertIs(binOp.type) } private fun testFunctionTemplateArguments( diff --git a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/types/TypeTests.kt b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/types/TypeTests.kt index 57efa21b86..12104eccd2 100644 --- a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/types/TypeTests.kt +++ b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/types/TypeTests.kt @@ -268,11 +268,11 @@ internal class TypeTests : BaseTest() { assertTrue(ptr.type is PointerType) assertEquals((ptr.type as PointerType).elementType, regularInt.type) - // Test type Propagation (auto) UnknownType + // Unresolved auto type propagation val unknown = findByUniqueName(variableDeclarations, "unknown") - assertEquals(UnknownType.getUnknownType(CPPLanguage()), unknown.type) + assertIs(unknown.type) - // Test type Propagation auto + // Resolved auto type propagation val propagated = findByUniqueName(variableDeclarations, "propagated") assertEquals(regularInt.type, propagated.type) } diff --git a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXLanguageFrontendTest.kt b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXLanguageFrontendTest.kt index d5fd09b57b..0a0434d109 100644 --- a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXLanguageFrontendTest.kt +++ b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXLanguageFrontendTest.kt @@ -54,7 +54,7 @@ internal class CXXLanguageFrontendTest : BaseTest() { @Test @Throws(Exception::class) fun testForEach() { - val file = File("src/test/resources/components/foreachstmt.cpp") + val file = File("src/test/resources/cxx/foreachstmt.cpp") val tu = analyzeAndGetFirstTU(listOf(file), file.parentFile.toPath(), true) val main = tu.getDeclarationsByName("main", FunctionDeclaration::class.java) assertFalse(main.isEmpty()) @@ -80,7 +80,7 @@ internal class CXXLanguageFrontendTest : BaseTest() { val i = stmt.singleDeclaration as VariableDeclaration assertNotNull(i) assertLocalName("i", i) - assertEquals(UnknownType.getUnknownType(CPPLanguage()), i.type) + assertIs(i.type) } @Test @@ -157,21 +157,17 @@ internal class CXXLanguageFrontendTest : BaseTest() { @Test @Throws(Exception::class) fun testCast() { - val file = File("src/test/resources/components/castexpr.cpp") + val file = File("src/test/resources/cxx/castexpr.cpp") val tu = analyzeAndGetFirstTU(listOf(file), file.parentFile.toPath(), true) with(tu) { - val main = tu.getDeclarationAs(0, FunctionDeclaration::class.java) - val e = - Objects.requireNonNull( - main!!.getBodyStatementAs(0, DeclarationStatement::class.java) - ) - ?.singleDeclaration as VariableDeclaration + val main = tu.functions["main"] + assertNotNull(main) + + val e = main.variables["e"] assertNotNull(e) assertEquals(objectType("ExtendedClass").pointer(), e.type) - val b = - Objects.requireNonNull(main.getBodyStatementAs(1, DeclarationStatement::class.java)) - ?.singleDeclaration as VariableDeclaration + val b = main.variables["b"] assertNotNull(b) assertEquals(objectType("BaseClass").pointer(), b.type) @@ -180,21 +176,19 @@ internal class CXXLanguageFrontendTest : BaseTest() { assertNotNull(cast) assertEquals(objectType("BaseClass").pointer(), cast.castType) - val staticCast = main.getBodyStatementAs(2, BinaryOperator::class.java) + val staticCast = main.getBodyStatementAs(2, AssignExpression::class.java) assertNotNull(staticCast) - cast = staticCast.rhs as CastExpression + cast = staticCast.rhs() assertNotNull(cast) assertLocalName("static_cast", cast) - val reinterpretCast = main.getBodyStatementAs(3, BinaryOperator::class.java) + val reinterpretCast = main.getBodyStatementAs(3, AssignExpression::class.java) assertNotNull(reinterpretCast) - cast = reinterpretCast.rhs as CastExpression + cast = reinterpretCast.rhs() assertNotNull(cast) assertLocalName("reinterpret_cast", cast) - val d = - Objects.requireNonNull(main.getBodyStatementAs(4, DeclarationStatement::class.java)) - ?.singleDeclaration as VariableDeclaration + val d = main.variables["d"] assertNotNull(d) cast = d.initializer as? CastExpression @@ -508,47 +502,45 @@ internal class CXXLanguageFrontendTest : BaseTest() { @Test @Throws(Exception::class) fun testAssignmentExpression() { - val file = File("src/test/resources/assignmentexpression.cpp") - val declaration = analyzeAndGetFirstTU(listOf(file), file.parentFile.toPath(), true) + val file = File("src/test/resources/cxx/assignmentexpression.cpp") + val tu = analyzeAndGetFirstTU(listOf(file), file.parentFile.toPath(), true) // just take a look at the second function - val functionDeclaration = declaration.getDeclarationAs(1, FunctionDeclaration::class.java) - val statements = functionDeclaration?.statements + val main = tu.functions["main"] + assertNotNull(main) + + val statements = main.statements assertNotNull(statements) - val declareA = statements[0] - val a = (declareA as DeclarationStatement).singleDeclaration + val a = main.variables["a"] val assignA = statements[1] - assertTrue(assignA is BinaryOperator) + assertTrue(assignA is AssignExpression) - var lhs = assignA.lhs - var rhs = assignA.rhs + var lhs = assignA.lhs() + var rhs = assignA.rhs() assertLocalName("a", lhs) assertEquals(2, (rhs as? Literal<*>)?.value) - assertRefersTo(assignA.lhs, a) - - val declareB = statements[2] - assertTrue(declareB is DeclarationStatement) + assertRefersTo(lhs, a) - val b = declareB.singleDeclaration + val b = main.variables["b"] // a = b val assignB = statements[3] - assertTrue(assignB is BinaryOperator) + assertTrue(assignB is AssignExpression) - lhs = assignB.lhs - rhs = assignB.rhs + lhs = assignB.lhs() + rhs = assignB.rhs() assertLocalName("a", lhs) assertTrue(rhs is DeclaredReferenceExpression) assertLocalName("b", rhs) assertRefersTo(rhs, b) val assignBWithFunction = statements[4] - assertTrue(assignBWithFunction is BinaryOperator) - assertLocalName("a", assignBWithFunction.lhs) - assertTrue(assignBWithFunction.rhs is CallExpression) + assertTrue(assignBWithFunction is AssignExpression) + assertLocalName("a", assignBWithFunction.lhs()) - val call = assignBWithFunction.rhs as CallExpression + val call = assignBWithFunction.rhs() + assertNotNull(call) assertLocalName("someFunction", call) assertRefersTo(call.arguments[0], b) } @@ -620,8 +612,10 @@ internal class CXXLanguageFrontendTest : BaseTest() { assertNotNull(statement) // b = *ptr; - val assign = statements[++line] as BinaryOperator - val dereference = assign.rhs as UnaryOperator + val assign = statements[++line] as AssignExpression + + val dereference = assign.rhs() + assertNotNull(dereference) input = dereference.input assertLocalName("ptr", input) assertEquals("*", dereference.operatorCode) @@ -631,51 +625,63 @@ internal class CXXLanguageFrontendTest : BaseTest() { @Test @Throws(Exception::class) fun testBinaryOperator() { - val file = File("src/test/resources/binaryoperator.cpp") + val file = File("src/test/resources/cxx/binaryoperator.cpp") val tu = analyzeAndGetFirstTU(listOf(file), file.parentFile.toPath(), true) - val statements = tu.getDeclarationAs(0, FunctionDeclaration::class.java)?.statements + + val main = tu.functions["main"] + assertNotNull(main) + + val statements = main.statements assertNotNull(statements) // first two statements are just declarations // a = b * 2 - var operator = statements[2] as? BinaryOperator - assertNotNull(operator) - assertLocalName("a", operator.lhs) - assertTrue(operator.rhs is BinaryOperator) + var assign = statements[2] as? AssignExpression + assertNotNull(assign) + + var ref = assign.lhs() + assertNotNull(ref) + assertLocalName("a", ref) - var rhs = operator.rhs as BinaryOperator - assertTrue(rhs.lhs is DeclaredReferenceExpression) - assertLocalName("b", rhs.lhs) - assertTrue(rhs.rhs is Literal<*>) - assertEquals(2, (rhs.rhs as Literal<*>).value) + var binOp = assign.rhs() + assertNotNull(binOp) + + assertTrue(binOp.lhs is DeclaredReferenceExpression) + assertLocalName("b", binOp.lhs) + assertTrue(binOp.rhs is Literal<*>) + assertEquals(2, (binOp.rhs as Literal<*>).value) // a = 1 * 1 - operator = statements[3] as? BinaryOperator - assertNotNull(operator) - assertLocalName("a", operator.lhs) - assertTrue(operator.rhs is BinaryOperator) + assign = statements[3] as? AssignExpression + assertNotNull(assign) + + ref = assign.lhs() + assertNotNull(ref) + assertLocalName("a", ref) - rhs = operator.rhs as BinaryOperator - assertTrue(rhs.lhs is Literal<*>) - assertEquals(1, (rhs.lhs as Literal<*>).value) - assertTrue(rhs.rhs is Literal<*>) - assertEquals(1, (rhs.rhs as Literal<*>).value) + binOp = assign.rhs() + assertNotNull(binOp) + + assertTrue(binOp.lhs is Literal<*>) + assertEquals(1, (binOp.lhs as Literal<*>).value) + assertTrue(binOp.rhs is Literal<*>) + assertEquals(1, (binOp.rhs as Literal<*>).value) // std::string* notMultiplication // this is not a multiplication, but a variable declaration with a pointer type, but - // syntactically no different than the previous ones + // syntactically no different from the previous ones val stmt = statements[4] as DeclarationStatement val decl = stmt.singleDeclaration as VariableDeclaration with(tu) { assertEquals(objectType("std::string").pointer(), decl.type) } assertLocalName("notMultiplication", decl) assertTrue(decl.initializer is BinaryOperator) - operator = decl.initializer as? BinaryOperator - assertNotNull(operator) - assertTrue(operator.lhs is Literal<*>) - assertEquals(0, (operator.lhs as Literal<*>).value) - assertTrue(operator.rhs is Literal<*>) - assertEquals(0, (operator.rhs as Literal<*>).value) + binOp = decl.initializer as? BinaryOperator + assertNotNull(binOp) + assertTrue(binOp.lhs is Literal<*>) + assertEquals(0, (binOp.lhs as Literal<*>).value) + assertTrue(binOp.rhs is Literal<*>) + assertEquals(0, (binOp.rhs as Literal<*>).value) } @Test @@ -1090,7 +1096,7 @@ internal class CXXLanguageFrontendTest : BaseTest() { @Test @Throws(Exception::class) fun testLocation() { - val file = File("src/test/resources/components/foreachstmt.cpp") + val file = File("src/test/resources/cxx/foreachstmt.cpp") val tu = analyzeAndGetFirstTU(listOf(file), file.parentFile.toPath(), true) val main = tu.functions["main"] assertNotNull(main) @@ -1140,7 +1146,7 @@ internal class CXXLanguageFrontendTest : BaseTest() { .sourceLocations(listOf(file)) .topLevel(file.parentFile) .defaultPasses() - .defaultLanguages() + .registerLanguage() .processAnnotations(true) .symbols( mapOf( @@ -1197,7 +1203,7 @@ internal class CXXLanguageFrontendTest : BaseTest() { .useUnityBuild(true) .loadIncludes(true) .defaultPasses() - .defaultLanguages() + .registerLanguage() ) assertEquals(1, declarations.size) // should contain 3 declarations (2 include and 1 function decl from the include) @@ -1325,16 +1331,16 @@ internal class CXXLanguageFrontendTest : BaseTest() { val file = File("src/test/resources/c/struct.c") val tu = analyzeAndGetFirstTU(listOf(file), file.parentFile.toPath(), true) - val main = tu.byNameOrNull("main") + val main = tu.functions["main"] assertNotNull(main) - val myStruct = tu.byNameOrNull("MyStruct") + val myStruct = tu.records["MyStruct"] assertNotNull(myStruct) - val field = myStruct.byNameOrNull("field") + val field = myStruct.fields["field"] assertNotNull(field) - val s = main.bodyOrNull()?.singleDeclaration as? VariableDeclaration + val s = main.variables["s"] assertNotNull(s) assertEquals(myStruct, (s.type as? ObjectType)?.recordDeclaration) @@ -1574,4 +1580,16 @@ internal class CXXLanguageFrontendTest : BaseTest() { assertLocalName("int", tu.functions["main"]?.returnTypes?.firstOrNull()) } + + @Test + @Throws(Exception::class) + fun testFancyTypes() { + val file = File("src/test/resources/cxx/fancy_types.cpp") + val tu = analyzeAndGetFirstTU(listOf(file), file.parentFile.toPath(), true) + assertNotNull(tu) + + val ptr = tu.variables["ptr"] + assertNotNull(ptr) + assertLocalName("decltype(nullptr)", ptr.type) + } } diff --git a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/LambdaTest.kt b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/LambdaTest.kt index eb970ae368..f223455397 100644 --- a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/LambdaTest.kt +++ b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/LambdaTest.kt @@ -29,6 +29,7 @@ import de.fraunhofer.aisec.cpg.TranslationConfiguration import de.fraunhofer.aisec.cpg.TranslationManager import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.statements.expressions.* +import de.fraunhofer.aisec.cpg.graph.types.FunctionPointerType import java.io.File import kotlin.test.* @@ -40,7 +41,7 @@ class CPPLambdaTest { TranslationConfiguration.builder() .sourceLocations(File("src/test/resources/cxx/lambdas.cpp")) .defaultPasses() - .defaultLanguages() + .registerLanguage() .build() val analyzer = TranslationManager.builder().config(config).build() val result = analyzer.analyze().get() @@ -51,10 +52,12 @@ class CPPLambdaTest { val lambdaVar = function.variables["this_is_a_lambda"] assertNotNull(lambdaVar) + assertIs(lambdaVar.type) + val lambda = lambdaVar.initializer as? LambdaExpression assertNotNull(lambda) - assertTrue(lambda in lambdaVar.prevEOG) + val printFunctionCall = function.calls["print_function"] assertNotNull(printFunctionCall) assertTrue(printFunctionCall in lambda.prevEOG) @@ -70,7 +73,7 @@ class CPPLambdaTest { TranslationConfiguration.builder() .sourceLocations(File("src/test/resources/cxx/lambdas.cpp")) .defaultPasses() - .defaultLanguages() + .registerLanguage() .build() val analyzer = TranslationManager.builder().config(config).build() val result = analyzer.analyze().get() @@ -104,7 +107,7 @@ class CPPLambdaTest { TranslationConfiguration.builder() .sourceLocations(File("src/test/resources/cxx/lambdas.cpp")) .defaultPasses() - .defaultLanguages() + .registerLanguage() .build() val analyzer = TranslationManager.builder().config(config).build() val result = analyzer.analyze().get() @@ -131,7 +134,7 @@ class CPPLambdaTest { TranslationConfiguration.builder() .sourceLocations(File("src/test/resources/cxx/lambdas.cpp")) .defaultPasses() - .defaultLanguages() + .registerLanguage() .build() val analyzer = TranslationManager.builder().config(config).build() val result = analyzer.analyze().get() @@ -167,7 +170,7 @@ class CPPLambdaTest { TranslationConfiguration.builder() .sourceLocations(File("src/test/resources/cxx/lambdas.cpp")) .defaultPasses() - .defaultLanguages() + .registerLanguage() .build() val analyzer = TranslationManager.builder().config(config).build() val result = analyzer.analyze().get() @@ -204,7 +207,7 @@ class CPPLambdaTest { TranslationConfiguration.builder() .sourceLocations(File("src/test/resources/cxx/lambdas.cpp")) .defaultPasses() - .defaultLanguages() + .registerLanguage() .build() val analyzer = TranslationManager.builder().config(config).build() val result = analyzer.analyze().get() @@ -240,7 +243,7 @@ class CPPLambdaTest { TranslationConfiguration.builder() .sourceLocations(File("src/test/resources/cxx/lambdas.cpp")) .defaultPasses() - .defaultLanguages() + .registerLanguage() .build() val analyzer = TranslationManager.builder().config(config).build() val result = analyzer.analyze().get() @@ -274,7 +277,7 @@ class CPPLambdaTest { TranslationConfiguration.builder() .sourceLocations(File("src/test/resources/cxx/lambdas.cpp")) .defaultPasses() - .defaultLanguages() + .registerLanguage() .build() val analyzer = TranslationManager.builder().config(config).build() val result = analyzer.analyze().get() @@ -310,7 +313,7 @@ class CPPLambdaTest { TranslationConfiguration.builder() .sourceLocations(File("src/test/resources/cxx/lambdas.cpp")) .defaultPasses() - .defaultLanguages() + .registerLanguage() .build() val analyzer = TranslationManager.builder().config(config).build() val result = analyzer.analyze().get() @@ -337,7 +340,7 @@ class CPPLambdaTest { TranslationConfiguration.builder() .sourceLocations(File("src/test/resources/cxx/lambdas.cpp")) .defaultPasses() - .defaultLanguages() + .registerLanguage() .build() val analyzer = TranslationManager.builder().config(config).build() val result = analyzer.analyze().get() @@ -358,7 +361,7 @@ class CPPLambdaTest { TranslationConfiguration.builder() .sourceLocations(File("src/test/resources/cxx/lambdas.cpp")) .defaultPasses() - .defaultLanguages() + .registerLanguage() .build() val analyzer = TranslationManager.builder().config(config).build() val result = analyzer.analyze().get() diff --git a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/helpers/BenchmarkCXXTest.kt b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/helpers/BenchmarkCXXTest.kt index d0e5b5cbe4..1d283bf9b5 100644 --- a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/helpers/BenchmarkCXXTest.kt +++ b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/helpers/BenchmarkCXXTest.kt @@ -37,7 +37,7 @@ class BenchmarkCXXTest { @Test fun testGetBenchmarkResult() { - val file = File("src/test/resources/components/foreachstmt.cpp") + val file = File("src/test/resources/cxx/foreachstmt.cpp") val tr = TestUtils.analyze(listOf(file), file.parentFile.toPath(), true) assertNotNull(tr) @@ -58,7 +58,7 @@ class BenchmarkCXXTest { @Test fun testPrintBenchmark() { - val file = File("src/test/resources/components/foreachstmt.cpp") + val file = File("src/test/resources/cxx/foreachstmt.cpp") val tr = TestUtils.analyze(listOf(file), file.parentFile.toPath(), true) assertNotNull(tr) diff --git a/cpg-language-cxx/src/test/resources/assignmentexpression.cpp b/cpg-language-cxx/src/test/resources/cxx/assignmentexpression.cpp similarity index 100% rename from cpg-language-cxx/src/test/resources/assignmentexpression.cpp rename to cpg-language-cxx/src/test/resources/cxx/assignmentexpression.cpp diff --git a/cpg-language-cxx/src/test/resources/binaryoperator.cpp b/cpg-language-cxx/src/test/resources/cxx/binaryoperator.cpp similarity index 100% rename from cpg-language-cxx/src/test/resources/binaryoperator.cpp rename to cpg-language-cxx/src/test/resources/cxx/binaryoperator.cpp diff --git a/cpg-language-cxx/src/test/resources/components/castexpr.cpp b/cpg-language-cxx/src/test/resources/cxx/castexpr.cpp similarity index 100% rename from cpg-language-cxx/src/test/resources/components/castexpr.cpp rename to cpg-language-cxx/src/test/resources/cxx/castexpr.cpp diff --git a/cpg-language-cxx/src/test/resources/cxx/fancy_types.cpp b/cpg-language-cxx/src/test/resources/cxx/fancy_types.cpp new file mode 100644 index 0000000000..b9851fe027 --- /dev/null +++ b/cpg-language-cxx/src/test/resources/cxx/fancy_types.cpp @@ -0,0 +1,3 @@ +typedef __decltype(nullptr) nullptr_t; + +nullptr_t ptr; diff --git a/cpg-language-cxx/src/test/resources/components/foreachstmt.cpp b/cpg-language-cxx/src/test/resources/cxx/foreachstmt.cpp similarity index 100% rename from cpg-language-cxx/src/test/resources/components/foreachstmt.cpp rename to cpg-language-cxx/src/test/resources/cxx/foreachstmt.cpp diff --git a/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/ExpressionHandler.kt b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/ExpressionHandler.kt index 996360809d..6d268e6b2e 100644 --- a/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/ExpressionHandler.kt +++ b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/ExpressionHandler.kt @@ -238,7 +238,7 @@ class ExpressionHandler(frontend: GoLanguageFrontend) : // Pass the remaining arguments for (expr in args.subList(1.coerceAtMost(args.size - 1), args.size - 1)) { - handle(expr)?.let { construct += it } + handle(expr).let { construct += it } } construct @@ -347,7 +347,7 @@ class ExpressionHandler(frontend: GoLanguageFrontend) : val construct = newConstructExpression(type.name, rawNode = compositeLit) construct.type = type - val list = newInitializerListExpression(rawNode = compositeLit) + val list = newInitializerListExpression(type, rawNode = compositeLit) construct += list // Normally, the construct expression would not have DFG edge, but in this case we are diff --git a/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoHandler.kt b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoHandler.kt index cfac505f13..dc21160c7a 100644 --- a/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoHandler.kt +++ b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoHandler.kt @@ -53,6 +53,8 @@ abstract class GoHandler, oldType: Type) { + (forEach.iterable as HasType).registerTypeObserver( + object : HasType.TypeObserver { + override fun typeChanged(newType: Type, src: HasType) { if (src.type is UnknownType) { return } @@ -154,7 +155,7 @@ class GoExtraPass(ctx: TranslationContext) : ComponentPass(ctx), ScopeProvider { } } - override fun possibleSubTypesChanged(src: HasType, root: MutableList) { + override fun assignedTypeChanged(assignedTypes: Set, src: HasType) { // Nothing to do } } @@ -178,7 +179,7 @@ class GoExtraPass(ctx: TranslationContext) : ComponentPass(ctx), ScopeProvider { val ref = scopeManager.resolveReference(expr) if (ref == null) { // We need to implicitly declare it, if its not declared before. - val decl = newVariableDeclaration(expr.name, expr.type) + val decl = newVariableDeclaration(expr.name, expr.autoType()) decl.location = expr.location decl.isImplicit = true decl.initializer = assign.findValue(expr) diff --git a/cpg-language-go/src/test/resources/golang/literal.go b/cpg-language-go/src/test/resources/golang/literal.go index 0450e78c4b..4b7207b8c0 100644 --- a/cpg-language-go/src/test/resources/golang/literal.go +++ b/cpg-language-go/src/test/resources/golang/literal.go @@ -4,8 +4,9 @@ const a = 1 const s = "test" const f = 1.0 const f32 float32 = 1.00 + var n *int = nil var fn = func(_ int) int { - return 1 -} \ No newline at end of file + return 1 +} 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 2b0978b562..5950b651d6 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 @@ -28,6 +28,7 @@ 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 @@ -42,6 +43,7 @@ import de.fraunhofer.aisec.cpg.graph.statements.expressions.* import de.fraunhofer.aisec.cpg.graph.types.* import java.util.function.Supplier import kotlin.collections.set +import kotlin.jvm.optionals.getOrNull import org.slf4j.LoggerFactory class ExpressionHandler(lang: JavaLanguageFrontend) : @@ -95,7 +97,7 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : castExpression.type = frontend.typeOf(castExpr.type.resolve().asPrimitive()) } else { // Get Runtime type from cast expression for complex types; - castExpression.expression.registerTypeListener(castExpression) + // castExpression.expression.registerTypeListener(castExpression) } return castExpression } @@ -130,9 +132,18 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : private fun handleArrayInitializerExpr(expr: Expression): Statement { val arrayInitializerExpr = expr as ArrayInitializerExpr + + // We need to go back to the parent to get the array type + val arrayType = + when (val parent = expr.parentNode.getOrNull()) { + is ArrayCreationExpr -> frontend.typeOf(parent.elementType).array() + is VariableDeclarator -> frontend.typeOf(parent.type) + else -> unknownType() + } + // ArrayInitializerExpressions are converted into InitializerListExpressions to reduce the // syntactic distance a CPP and JAVA CPG - val initList = this.newInitializerListExpression(expr.toString()) + val initList = this.newInitializerListExpression(arrayType, expr.toString()) val initializers = arrayInitializerExpr.values .map { handle(it) } @@ -196,7 +207,7 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : return this.newConditionalExpression(condition, thenExpr, elseExpr, superType) } - private fun handleAssignmentExpression(expr: Expression): BinaryOperator { + private fun handleAssignmentExpression(expr: Expression): AssignExpression { val assignExpr = expr.asAssignExpr() // first, handle the target. this is the first argument of the operator call @@ -210,11 +221,15 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : handle(assignExpr.value) as? de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression ?: newProblemExpression("could not parse lhs") - val binaryOperator = - this.newBinaryOperator(assignExpr.operator.asString(), assignExpr.toString()) - binaryOperator.lhs = lhs - binaryOperator.rhs = rhs - return binaryOperator + val assign = + this.newAssignExpression( + assignExpr.operator.asString(), + listOf(lhs), + listOf(rhs), + rawNode = assignExpr + ) + + return assign } // Not sure how to handle this exactly yet diff --git a/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/StatementHandler.kt b/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/StatementHandler.kt index adffc066ac..4dd85bb37e 100644 --- a/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/StatementHandler.kt +++ b/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/StatementHandler.kt @@ -569,7 +569,7 @@ class StatementHandler(lang: JavaLanguageFrontend?) : ): de.fraunhofer.aisec.cpg.graph.statements.CatchClause { val cClause = this.newCatchClause(catchCls.toString()) frontend.scopeManager.enterScope(cClause) - val possibleTypes: MutableList = ArrayList() + val possibleTypes = mutableSetOf() val concreteType: Type if (catchCls.parameter.type is UnionType) { for (t in (catchCls.parameter.type as UnionType).elements) { @@ -590,7 +590,7 @@ class StatementHandler(lang: JavaLanguageFrontend?) : catchCls.parameter.toString(), false ) - parameter.possibleSubTypes = possibleTypes + parameter.addAssignedTypes(possibleTypes) val body = handleBlockStatement(catchCls.body) cClause.body = body cClause.parameter = parameter diff --git a/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/JavaCallResolverHelper.kt b/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/JavaCallResolverHelper.kt index d2940fa7dd..d1ddc7973a 100644 --- a/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/JavaCallResolverHelper.kt +++ b/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/JavaCallResolverHelper.kt @@ -34,6 +34,7 @@ import de.fraunhofer.aisec.cpg.graph.objectType import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberCallExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberExpression +import de.fraunhofer.aisec.cpg.graph.types.HasType import de.fraunhofer.aisec.cpg.helpers.Util import de.fraunhofer.aisec.cpg.passes.CallResolver.Companion.LOGGER @@ -91,12 +92,17 @@ class JavaCallResolverHelper { if (target != null) { val superType = target.toType() // Explicitly set the type of the call's base to the super type, basically "casting" - // the - // "this" object to the super class + // the "this" object to the super class callee.base.type = superType - // And set the possible subtypes, to ensure, that really only our super type is in - // there - callee.base.updatePossibleSubtypes(listOf(superType)) + + val refersTo = (callee.base as? DeclaredReferenceExpression)?.refersTo + if (refersTo is HasType) { + refersTo.type = superType + refersTo.assignedTypes = mutableSetOf(superType) + } + + // Make sure that really only our super class is in the list of assigned types + callee.base.assignedTypes = mutableSetOf(superType) return true } diff --git a/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/java/JavaLanguageFrontendTest.kt b/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/java/JavaLanguageFrontendTest.kt index f538719246..ccc0f737ad 100644 --- a/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/java/JavaLanguageFrontendTest.kt +++ b/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/java/JavaLanguageFrontendTest.kt @@ -627,10 +627,10 @@ internal class JavaLanguageFrontendTest : BaseTest() { assertNotNull(record) val constructor = record.constructors[0] - val op = constructor.getBodyStatementAs(0, BinaryOperator::class.java) + val op = constructor.getBodyStatementAs(0, AssignExpression::class.java) assertNotNull(op) - val lhs = op.lhs as? MemberExpression + val lhs = op.lhs() val receiver = (lhs?.base as? DeclaredReferenceExpression)?.refersTo as? VariableDeclaration? assertNotNull(receiver) @@ -768,10 +768,10 @@ internal class JavaLanguageFrontendTest : BaseTest() { val doSomething = evenMoreInnerClass.methods["doSomething"] assertNotNull(doSomething) - val binOp = doSomething.bodyOrNull() - assertNotNull(binOp) + val assign = doSomething.bodyOrNull() + assertNotNull(assign) - val ref = ((binOp.rhs as? MemberExpression)?.base as DeclaredReferenceExpression).refersTo + val ref = ((assign.rhs())?.base as DeclaredReferenceExpression).refersTo assertNotNull(ref) assertSame(ref, thisOuterClass) } diff --git a/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/types/TypeTests.kt b/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/types/TypeTests.kt index 5302853f54..6d89f197a0 100644 --- a/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/types/TypeTests.kt +++ b/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/types/TypeTests.kt @@ -57,14 +57,14 @@ internal class TypeTests : BaseTest() { // Type of field t val fieldDeclarations = result.fields val fieldDeclarationT = findByUniqueName(fieldDeclarations, "t") - assertTrue(fieldDeclarationT.possibleSubTypes.contains(typeT)) + assertTrue(fieldDeclarationT.assignedTypes.contains(typeT)) // Parameter of set Method val methodDeclarations = result.methods val methodDeclarationSet = findByUniqueName(methodDeclarations, "set") val t = methodDeclarationSet.parameters[0] assertEquals(typeT, t.type) - assertTrue(t.possibleSubTypes.contains(typeT)) + assertTrue(t.assignedTypes.contains(typeT)) // Return Type of get Method val methodDeclarationGet = findByUniqueName(methodDeclarations, "get") diff --git a/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/ExpressionHandler.kt b/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/ExpressionHandler.kt index 72be04e5b3..f38f01ff39 100644 --- a/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/ExpressionHandler.kt +++ b/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/ExpressionHandler.kt @@ -312,8 +312,9 @@ class ExpressionHandler(lang: LLVMIRLanguageFrontend) : return newLiteral(string, frontend.typeOf(valueRef), frontend.codeOf(valueRef)) } - val list = newInitializerListExpression(frontend.codeOf(valueRef)) val arrayType = LLVMTypeOf(valueRef) + val list = + newInitializerListExpression(frontend.typeOf(valueRef), frontend.codeOf(valueRef)) val length = if (LLVMIsAConstantDataArray(valueRef) != null) { LLVMGetArrayLength(arrayType) diff --git a/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/StatementHandler.kt b/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/StatementHandler.kt index bb741ce560..93f6c86170 100644 --- a/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/StatementHandler.kt +++ b/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/StatementHandler.kt @@ -1291,7 +1291,7 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : private fun handleShufflevector(instr: LLVMValueRef): Statement { val instrStr = frontend.codeOf(instr) - val list = newInitializerListExpression(instrStr) + val list = newInitializerListExpression(frontend.typeOf(instr), instrStr) val elementType = frontend.typeOf(instr).dereference() val initializers = mutableListOf() @@ -1448,10 +1448,6 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : code ) (assignment.lhs.first() as DeclaredReferenceExpression).type = type - (assignment.lhs.first() as DeclaredReferenceExpression).unregisterTypeListener( - assignment - ) - assignment.unregisterTypeListener(assignment.lhs.first() as DeclaredReferenceExpression) (assignment.lhs.first() as DeclaredReferenceExpression).refersTo = declaration flatAST.add(assignment) diff --git a/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonLanguage.kt b/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonLanguage.kt index 15a48176d8..eb96e1abc0 100644 --- a/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonLanguage.kt +++ b/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonLanguage.kt @@ -81,20 +81,17 @@ class PythonLanguage : Language(), HasShortCircuitOperat val unknownType = UnknownType.getUnknownType(this) if ( operation.operatorCode == "/" && - operation.lhs.propagationType is NumericType && - operation.rhs.propagationType is NumericType + operation.lhs.type is NumericType && + operation.rhs.type is NumericType ) { // In Python, the / operation automatically casts the result to a float return getSimpleTypeOf("float") ?: unknownType } else if ( operation.operatorCode == "//" && - operation.lhs.propagationType is NumericType && - operation.rhs.propagationType is NumericType + operation.lhs.type is NumericType && + operation.rhs.type is NumericType ) { - return if ( - operation.lhs.propagationType is IntegerType && - operation.rhs.propagationType is IntegerType - ) { + return if (operation.lhs.type is IntegerType && operation.rhs.type is IntegerType) { // In Python, the // operation keeps the type as an int if both inputs are integers // or casts it to a float otherwise. getSimpleTypeOf("int") ?: unknownType diff --git a/cpg-language-python/src/main/python/CPGPython/_expressions.py b/cpg-language-python/src/main/python/CPGPython/_expressions.py index 366d917141..b7ee1506a8 100644 --- a/cpg-language-python/src/main/python/CPGPython/_expressions.py +++ b/cpg-language-python/src/main/python/CPGPython/_expressions.py @@ -107,7 +107,9 @@ def handle_expression_impl(self, expr): return r elif isinstance(expr, ast.Dict): ile = ExpressionBuilderKt.newInitializerListExpression( - self.frontend, self.get_src_code(expr)) + self.frontend, + UnknownType.getUnknownType(self.frontend.getLanguage()), + self.get_src_code(expr)) lst = [] @@ -357,7 +359,9 @@ def handle_expression_impl(self, expr): return r elif isinstance(expr, ast.List): ile = ExpressionBuilderKt.newInitializerListExpression( - self.frontend, self.get_src_code(expr)) + self.frontend, + UnknownType.getUnknownType(self.frontend.getLanguage()), + self.get_src_code(expr)) lst = [] @@ -370,7 +374,9 @@ def handle_expression_impl(self, expr): return ile elif isinstance(expr, ast.Tuple): ile = ExpressionBuilderKt.newInitializerListExpression( - self.frontend, self.get_src_code(expr)) + self.frontend, + UnknownType.getUnknownType(self.frontend.getLanguage()), + self.get_src_code(expr)) lst = [] diff --git a/cpg-language-python/src/main/python/CPGPython/_statements.py b/cpg-language-python/src/main/python/CPGPython/_statements.py index 5b27a9f519..40f7b98a48 100644 --- a/cpg-language-python/src/main/python/CPGPython/_statements.py +++ b/cpg-language-python/src/main/python/CPGPython/_statements.py @@ -31,6 +31,7 @@ from de.fraunhofer.aisec.cpg.graph import ExpressionBuilderKt from de.fraunhofer.aisec.cpg.graph.statements import CompoundStatement from de.fraunhofer.aisec.cpg.graph.types import UnknownType +from java.util import ArrayList import ast @@ -510,16 +511,20 @@ def handle_assign_impl(self, stmt): target = self.handle_expression(stmt.target) op = self.handle_operator_code(stmt.op) value = self.handle_expression(stmt.value) - r = ExpressionBuilderKt.newBinaryOperator(self.frontend, - op, self.get_src_code(stmt)) - r.setLhs(target) - r.setRhs(value) + lhs = ArrayList() + lhs.add(target) + rhs = ArrayList() + rhs.add(value) + r = ExpressionBuilderKt.newAssignExpression(self.frontend, + op, lhs, rhs, + self.get_src_code(stmt)) return r if isinstance(stmt, ast.Assign) and len(stmt.targets) != 1: self.log_with_loc(NOT_IMPLEMENTED_MSG, loglevel="ERROR") - r = ExpressionBuilderKt.newBinaryOperator(self.frontend, - "=", self.get_src_code(stmt) - ) + r = ExpressionBuilderKt.newAssignExpression(self.frontend, + "=", ArrayList(), + ArrayList(), + self.get_src_code(stmt)) return r if isinstance(stmt, ast.Assign): target = stmt.targets[0] @@ -539,9 +544,9 @@ def handle_assign_impl(self, stmt): "Expected a DeclaredReferenceExpression or MemberExpression " "but got \"%s\". Skipping." % lhs.java_name, loglevel="ERROR") - r = ExpressionBuilderKt.newBinaryOperator(self.frontend, - "=", - self.get_src_code(stmt)) + r = ExpressionBuilderKt.newArrayList(self.frontend, + "=", ArrayList(), ArrayList(), + self.get_src_code(stmt)) return r resolved_lhs = self.scopemanager.resolveReference(lhs) @@ -549,12 +554,16 @@ def handle_assign_impl(self, stmt): in_function = self.scopemanager.isInFunction() if resolved_lhs is not None: - # found var => BinaryOperator "=" - binop = ExpressionBuilderKt.newBinaryOperator( - self.frontend, "=", self.get_src_code(stmt)) - binop.setLhs(lhs) + lhsList = ArrayList() + lhsList.add(lhs) + rhsList = ArrayList() if rhs is not None: - binop.setRhs(rhs) + rhsList.add(rhs) + + # found var => BinaryOperator "=" + binop = ExpressionBuilderKt.newAssignExpression( + self.frontend, "=", lhsList, rhsList, self.get_src_code(stmt)) + return binop else: if in_record and not in_function: @@ -601,13 +610,13 @@ def bar(self): if rhs is not None: v = DeclarationBuilderKt.newVariableDeclaration( self.frontend, lhs.getName(), - rhs.getType(), + TypeBuilderKt.autoType(self.frontend), self.get_src_code(stmt), False) else: v = DeclarationBuilderKt.newVariableDeclaration( self.frontend, lhs.getName(), - TypeBuilderKt.unknownType(self.frontend), + TypeBuilderKt.autoType(self.frontend), self.get_src_code(stmt), False) if rhs is not None: @@ -661,13 +670,13 @@ def bar(self): if rhs is not None: v = DeclarationBuilderKt.newVariableDeclaration( self.frontend, lhs.getName(), - rhs.getType(), + TypeBuilderKt.autoType(self.frontend), self.get_src_code(stmt), False) else: v = DeclarationBuilderKt.newVariableDeclaration( self.frontend, lhs.getName(), - TypeBuilderKt.unknownType(self.frontend), + TypeBuilderKt.autoType(self.frontend), self.get_src_code(stmt), False) if rhs is not None: diff --git a/cpg-language-python/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonFrontendTest.kt b/cpg-language-python/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonFrontendTest.kt index 39fd3815a6..ec275da66d 100644 --- a/cpg-language-python/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonFrontendTest.kt +++ b/cpg-language-python/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonFrontendTest.kt @@ -593,11 +593,11 @@ class PythonFrontendTest : BaseTest() { val methBody = meth.body as? CompoundStatement assertNotNull(methBody) - val assign = methBody.statements[0] as? BinaryOperator + val assign = methBody.statements[0] as? AssignExpression assertNotNull(assign) - val assignLhs = assign.lhs as? MemberExpression - val assignRhs = assign.rhs as? BinaryOperator + val assignLhs = assign.lhs() + val assignRhs = assign.rhs() assertEquals("=", assign.operatorCode) assertNotNull(assignLhs) assertNotNull(assignRhs) @@ -657,15 +657,15 @@ class PythonFrontendTest : BaseTest() { assertNotNull(classFieldWithInit) // classFieldNoInitializer = classFieldWithInit - val assignClsFieldOutsideFunc = clsFoo.statements[2] as? BinaryOperator + val assignClsFieldOutsideFunc = clsFoo.statements[2] as? AssignExpression assertNotNull(assignClsFieldOutsideFunc) assertEquals( classFieldNoInitializer, - (assignClsFieldOutsideFunc.lhs as? DeclaredReferenceExpression)?.refersTo + (assignClsFieldOutsideFunc.lhs())?.refersTo ) assertEquals( classFieldWithInit, - (assignClsFieldOutsideFunc.rhs as? DeclaredReferenceExpression)?.refersTo + (assignClsFieldOutsideFunc.rhs())?.refersTo ) assertEquals("=", assignClsFieldOutsideFunc.operatorCode) @@ -680,41 +680,41 @@ class PythonFrontendTest : BaseTest() { assertNotNull(decl0.initializer) // self.classFieldNoInitializer = 789 - val barStmt1 = barBody.statements[1] as? BinaryOperator + val barStmt1 = barBody.statements[1] as? AssignExpression assertNotNull(barStmt1) - assertEquals(classFieldNoInitializer, (barStmt1.lhs as? MemberExpression)?.refersTo) + assertEquals(classFieldNoInitializer, (barStmt1.lhs())?.refersTo) // self.classFieldWithInit = 12 - val barStmt2 = barBody.statements[2] as? BinaryOperator + val barStmt2 = barBody.statements[2] as? AssignExpression assertNotNull(barStmt2) - assertEquals(classFieldWithInit, (barStmt2.lhs as? MemberExpression)?.refersTo) + assertEquals(classFieldWithInit, (barStmt2.lhs())?.refersTo) // classFieldNoInitializer = "shadowed" - val barStmt3 = barBody.statements[3] as? BinaryOperator + val barStmt3 = barBody.statements[3] as? AssignExpression assertNotNull(barStmt3) assertEquals("=", barStmt3.operatorCode) assertEquals( classFieldNoInitializer, - (barStmt3.lhs as? DeclaredReferenceExpression)?.refersTo + (barStmt3.lhs())?.refersTo ) - assertEquals("shadowed", (barStmt3.rhs as? Literal<*>)?.value) + assertEquals("shadowed", (barStmt3.rhs>())?.value) // classFieldWithInit = "shadowed" - val barStmt4 = barBody.statements[4] as? BinaryOperator + val barStmt4 = barBody.statements[4] as? AssignExpression assertNotNull(barStmt4) assertEquals("=", barStmt4.operatorCode) - assertEquals(classFieldWithInit, (barStmt4.lhs as? DeclaredReferenceExpression)?.refersTo) - assertEquals("shadowed", (barStmt4.rhs as? Literal<*>)?.value) + assertEquals(classFieldWithInit, (barStmt4.lhs())?.refersTo) + assertEquals("shadowed", (barStmt4.rhs>())?.value) // classFieldDeclaredInFunction = "shadowed" - val barStmt5 = barBody.statements[5] as? BinaryOperator + val barStmt5 = barBody.statements[5] as? AssignExpression assertNotNull(barStmt5) assertEquals("=", barStmt5.operatorCode) assertEquals( classFieldDeclaredInFunction, - (barStmt5.lhs as? DeclaredReferenceExpression)?.refersTo + (barStmt5.lhs())?.refersTo ) - assertEquals("shadowed", (barStmt5.rhs as? Literal<*>)?.value) + assertEquals("shadowed", (barStmt5.rhs>())?.value) /* TODO: foo = Foo() @@ -943,10 +943,10 @@ class PythonFrontendTest : BaseTest() { assertLocalName("z", elseStmt1) // phr = {**z, **content} - val elseStmt2 = ifElse.statements[1] as? BinaryOperator + val elseStmt2 = ifElse.statements(1) assertNotNull(elseStmt2) assertEquals("=", elseStmt2.operatorCode) - val elseStmt2Rhs = elseStmt2.rhs as? InitializerListExpression + val elseStmt2Rhs = elseStmt2.rhs() assertNotNull(elseStmt2Rhs) } diff --git a/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/ExpressionHandler.kt b/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/ExpressionHandler.kt index 9069f4910a..9b50a431f7 100644 --- a/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/ExpressionHandler.kt +++ b/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/ExpressionHandler.kt @@ -143,7 +143,7 @@ class ExpressionHandler(lang: TypeScriptLanguageFrontend) : } private fun handleObjectLiteralExpression(node: TypeScriptNode): InitializerListExpression { - val ile = newInitializerListExpression(this.frontend.codeOf(node)) + val ile = newInitializerListExpression(unknownType(), this.frontend.codeOf(node)) ile.initializers = node.children?.mapNotNull { this.handle(it) } ?: emptyList() diff --git a/cpg-language-typescript/src/main/nodejs/package-lock.json b/cpg-language-typescript/src/main/nodejs/package-lock.json index 1fe34fba97..06c54debc4 100644 --- a/cpg-language-typescript/src/main/nodejs/package-lock.json +++ b/cpg-language-typescript/src/main/nodejs/package-lock.json @@ -13,7 +13,7 @@ "@rollup/plugin-commonjs": "^25.0.3", "@rollup/plugin-node-resolve": "^15.1.0", "@rollup/plugin-typescript": "^11.1.2", - "rollup": "^3.27.0", + "rollup": "^3.26.3", "tslib": "^2.6.0" } }, diff --git a/cpg-neo4j/src/test/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/ApplicationTest.kt b/cpg-neo4j/src/test/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/ApplicationTest.kt index 33f38fe2cb..b7e26cecee 100644 --- a/cpg-neo4j/src/test/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/ApplicationTest.kt +++ b/cpg-neo4j/src/test/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/ApplicationTest.kt @@ -25,9 +25,11 @@ */ package de.fraunhofer.aisec.cpg_vis_neo4j -import de.fraunhofer.aisec.cpg.TranslationManager +import de.fraunhofer.aisec.cpg.* +import de.fraunhofer.aisec.cpg.graph.builder.* import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration import de.fraunhofer.aisec.cpg.graph.functions +import de.fraunhofer.aisec.cpg.graph.types.* import java.nio.file.Paths import kotlin.test.Test import kotlin.test.assertEquals From ef8df7e761add7e69bb2ca04ffc930d731afda71 Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Wed, 9 Aug 2023 11:23:40 +0200 Subject: [PATCH 119/143] Trying to reduce loops in `equals` (#1274) Some nodes were using an exhaustive list of properties in their `equals` method. However, this also included property, which could loop back to the node itself. For example a function declaration includes all body nodes, which could include a call expression, which included the `invoke` property, which could loop back to the function declaration. Fixes #1226 --- .../graph/declarations/FunctionDeclaration.kt | 6 ++---- .../cpg/graph/statements/GotoStatement.kt | 4 +--- .../statements/expressions/CallExpression.kt | 6 ++---- .../frontends/cxx/CXXLanguageFrontendTest.kt | 18 ++++++++++++++++++ .../src/test/resources/cxx/fix-1226/header.h | 9 +++++++++ .../src/test/resources/cxx/fix-1226/main1.cpp | 7 +++++++ .../src/test/resources/cxx/fix-1226/main2.cpp | 7 +++++++ 7 files changed, 46 insertions(+), 11 deletions(-) create mode 100644 cpg-language-cxx/src/test/resources/cxx/fix-1226/header.h create mode 100644 cpg-language-cxx/src/test/resources/cxx/fix-1226/main1.cpp create mode 100644 cpg-language-cxx/src/test/resources/cxx/fix-1226/main2.cpp diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/FunctionDeclaration.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/FunctionDeclaration.kt index 043737c0a4..3ac1f9f573 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/FunctionDeclaration.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/FunctionDeclaration.kt @@ -233,12 +233,10 @@ open class FunctionDeclaration : ValueDeclaration(), DeclarationHolder { body == other.body && parameters == other.parameters && propertyEqualsList(parameterEdges, other.parameterEdges) && - throwsTypes == other.throwsTypes && - overriddenBy == other.overriddenBy && - overrides == other.overrides) + throwsTypes == other.throwsTypes) } - override fun hashCode() = Objects.hash(super.hashCode(), parameters, throwsTypes, overrides) + override fun hashCode() = Objects.hash(super.hashCode(), body, parameters, throwsTypes) override fun addDeclaration(declaration: Declaration) { if (declaration is ParamVariableDeclaration) { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/GotoStatement.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/GotoStatement.kt index 48c4c1f8d0..813e59742a 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/GotoStatement.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/GotoStatement.kt @@ -38,9 +38,7 @@ class GotoStatement : Statement() { if (other !is GotoStatement) { return false } - return super.equals(other) && - labelName == other.labelName && - targetLabel == other.targetLabel + return super.equals(other) && labelName == other.labelName } override fun hashCode() = Objects.hash(super.hashCode(), labelName, targetLabel) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/CallExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/CallExpression.kt index 93d0e0fc5a..e9b895359f 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/CallExpression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/CallExpression.kt @@ -303,8 +303,6 @@ open class CallExpression : Expression(), HasType.TypeObserver, SecondaryTypeEdg return super.equals(other) && arguments == other.arguments && propertyEqualsList(argumentEdges, other.argumentEdges) && - invokes == other.invokes && - propertyEqualsList(invokeEdges, other.invokeEdges) && templateParameters == other.templateParameters && propertyEqualsList(templateParameterEdges, other.templateParameterEdges) && templateInstantiation == other.templateInstantiation && @@ -312,8 +310,8 @@ open class CallExpression : Expression(), HasType.TypeObserver, SecondaryTypeEdg } // TODO: Not sure if we can add the template, templateParameters, templateInstantiation fields - // here - override fun hashCode() = Objects.hash(super.hashCode(), arguments, invokes) + // here + override fun hashCode() = Objects.hash(super.hashCode(), arguments) override fun updateType(typeState: Collection) { for (t in typeTemplateParameters) { diff --git a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXLanguageFrontendTest.kt b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXLanguageFrontendTest.kt index 0a0434d109..b618ca00f9 100644 --- a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXLanguageFrontendTest.kt +++ b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXLanguageFrontendTest.kt @@ -1592,4 +1592,22 @@ internal class CXXLanguageFrontendTest : BaseTest() { assertNotNull(ptr) assertLocalName("decltype(nullptr)", ptr.type) } + + @Test + fun testRecursiveHeaderFunction() { + val file = File("src/test/resources/cxx/fix-1226") + val result = + analyze( + listOf(file.resolve("main1.cpp"), file.resolve("main2.cpp")), + file.toPath(), + true + ) + assertNotNull(result) + + // For now, we have duplicate functions because we include the header twice. This might + // change in the future. The important thing is that this gets parsed at all because we + // previously had a loop in our equals method + val functions = result.functions { it.name.localName == "foo" && it.isDefinition } + assertEquals(2, functions.size) + } } diff --git a/cpg-language-cxx/src/test/resources/cxx/fix-1226/header.h b/cpg-language-cxx/src/test/resources/cxx/fix-1226/header.h new file mode 100644 index 0000000000..f1c1575fd5 --- /dev/null +++ b/cpg-language-cxx/src/test/resources/cxx/fix-1226/header.h @@ -0,0 +1,9 @@ +template +struct A { + int foo(int i); +}; + +template +int A::foo(int i) { + return foo(i + 1); +} \ No newline at end of file diff --git a/cpg-language-cxx/src/test/resources/cxx/fix-1226/main1.cpp b/cpg-language-cxx/src/test/resources/cxx/fix-1226/main1.cpp new file mode 100644 index 0000000000..96e7be7c61 --- /dev/null +++ b/cpg-language-cxx/src/test/resources/cxx/fix-1226/main1.cpp @@ -0,0 +1,7 @@ +#include "header.h" + +int main() { + int i = 1; + A a; + return a.foo(i); +} \ No newline at end of file diff --git a/cpg-language-cxx/src/test/resources/cxx/fix-1226/main2.cpp b/cpg-language-cxx/src/test/resources/cxx/fix-1226/main2.cpp new file mode 100644 index 0000000000..96e7be7c61 --- /dev/null +++ b/cpg-language-cxx/src/test/resources/cxx/fix-1226/main2.cpp @@ -0,0 +1,7 @@ +#include "header.h" + +int main() { + int i = 1; + A a; + return a.foo(i); +} \ No newline at end of file From 6192503d804a4642a914f80fb39fe700490e2b9c Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Wed, 9 Aug 2023 11:37:22 +0200 Subject: [PATCH 120/143] Using `IdentityHashSet` in EOG pass to remove superfluous EOG nodes (#1275) * EOG pass speedup There was an issue that massivly slowed down the EOG pass for programs that a LOT (>500.000) EOG nodes. Now we are using an identity hash set instead of a regular hash set to store the set of unique EOG nodes. * Add some comments * revert js change --------- Co-authored-by: Alexander Kuechler --- .../cpg/passes/EvaluationOrderGraphPass.kt | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt index a23e07bca4..7cdc6d1acc 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt @@ -37,6 +37,7 @@ import de.fraunhofer.aisec.cpg.graph.scopes.* import de.fraunhofer.aisec.cpg.graph.statements.* import de.fraunhofer.aisec.cpg.graph.statements.expressions.* import de.fraunhofer.aisec.cpg.graph.types.Type +import de.fraunhofer.aisec.cpg.helpers.IdentitySet import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker import de.fraunhofer.aisec.cpg.helpers.Util import de.fraunhofer.aisec.cpg.passes.order.DependsOn @@ -177,15 +178,19 @@ open class EvaluationOrderGraphPass(ctx: TranslationContext) : TranslationUnitPa /** * Removes EOG edges by first building the negative set of nodes that cannot be visited and then - * remove there outgoing edges.In contrast to truncateLooseEdges this also removes cycles. + * remove there outgoing edges. This also removes cycles. */ protected fun removeUnreachableEOGEdges(tu: TranslationUnitDeclaration) { - val eognodes = - SubgraphWalker.flattenAST(tu) - .filter { it.prevEOG.isNotEmpty() || it.nextEOG.isNotEmpty() } - .toMutableList() + // All nodes which have an eog edge + val eogNodes = IdentitySet() + eogNodes.addAll( + SubgraphWalker.flattenAST(tu).filter { + it.prevEOG.isNotEmpty() || it.nextEOG.isNotEmpty() + } + ) + // only eog entry points var validStarts = - eognodes + eogNodes .filter { node -> node is FunctionDeclaration || node is RecordDeclaration || @@ -193,12 +198,14 @@ open class EvaluationOrderGraphPass(ctx: TranslationContext) : TranslationUnitPa node is TranslationUnitDeclaration } .toSet() + // Remove all nodes from eogNodes which are reachable from validStarts and transitively. while (validStarts.isNotEmpty()) { - eognodes.removeAll(validStarts) - validStarts = validStarts.flatMap { it.nextEOG }.filter { it in eognodes }.toSet() + eogNodes.removeAll(validStarts) + validStarts = validStarts.flatMap { it.nextEOG }.filter { it in eogNodes }.toSet() } - // remaining eognodes were not visited and have to be removed from the EOG - for (unvisitedNode in eognodes) { + // The remaining nodes are unreachable from the entry points. We delete their outgoing EOG + // edges. + for (unvisitedNode in eogNodes) { unvisitedNode.nextEOGEdges.forEach { next -> next.end.removePrevEOGEntry(unvisitedNode) } From 8746d62ef21222e7bb497cbd061defb71c4f2c08 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 10 Aug 2023 09:36:13 +0200 Subject: [PATCH 121/143] Update dependency rollup to v3.28.0 (#1278) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- cpg-language-typescript/src/main/nodejs/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cpg-language-typescript/src/main/nodejs/package-lock.json b/cpg-language-typescript/src/main/nodejs/package-lock.json index 06c54debc4..3ff17cd4b5 100644 --- a/cpg-language-typescript/src/main/nodejs/package-lock.json +++ b/cpg-language-typescript/src/main/nodejs/package-lock.json @@ -370,9 +370,9 @@ } }, "node_modules/rollup": { - "version": "3.27.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.27.0.tgz", - "integrity": "sha512-aOltLCrYZ0FhJDm7fCqwTjIUEVjWjcydKBV/Zeid6Mn8BWgDCUBBWT5beM5ieForYNo/1ZHuGJdka26kvQ3Gzg==", + "version": "3.28.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.28.0.tgz", + "integrity": "sha512-d7zhvo1OUY2SXSM6pfNjgD5+d0Nz87CUp4mt8l/GgVP3oBsPwzNvSzyu1me6BSG9JIgWNTVcafIXBIyM8yQ3yw==", "dev": true, "bin": { "rollup": "dist/bin/rollup" From 607b49861f1f93ad2d81909cdf34609f63b44563 Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Mon, 14 Aug 2023 11:41:39 +0200 Subject: [PATCH 122/143] Execute `TypeResolver` before `VariableUsageResolver` (#1277) * Execute `TypeResolver` before `VariableUsageResolver` This PR moves the execution order of the `TypeResolver` before the `VariableUsageResolver` and `CallResolver`. This should help clean up the type manager code that deals with record / type hierachies in a better way. * Update cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/WrapState.kt Co-authored-by: KuechA <31155350+KuechA@users.noreply.github.com> * Specified better import order --------- Co-authored-by: KuechA <31155350+KuechA@users.noreply.github.com> --- .../aisec/cpg/TranslationManager.kt | 1 - .../de/fraunhofer/aisec/cpg/TypeManager.kt | 550 ++++++------------ .../graph/declarations/FunctionDeclaration.kt | 4 +- .../graph/declarations/RecordDeclaration.kt | 29 +- .../declarations/TypeParamDeclaration.kt | 8 +- .../statements/expressions/CallExpression.kt | 27 +- .../expressions/ConditionalExpression.kt | 8 +- ...aryTypeEdge.kt => HasSecondaryTypeEdge.kt} | 8 +- .../aisec/cpg/graph/types/HasType.kt | 11 - .../aisec/cpg/graph/types/ObjectType.kt | 15 +- .../fraunhofer/aisec/cpg/graph/types/Type.kt | 28 + .../aisec/cpg/graph/types/WrapState.kt | 36 +- .../aisec/cpg/passes/CXXCallResolverHelper.kt | 35 +- .../cpg/passes/EvaluationOrderGraphPass.kt | 3 +- .../aisec/cpg/passes/SymbolResolverPass.kt | 8 + .../aisec/cpg/passes/TypeHierarchyResolver.kt | 10 +- .../aisec/cpg/passes/TypeResolver.kt | 12 +- .../cpg/graph/types/TypePropagationTest.kt | 13 + .../aisec/cpg/frontends/cxx/CPPLanguage.kt | 3 +- .../aisec/cpg/enhancements/types/TypeTests.kt | 143 ++--- .../cpg/enhancements/types/TypedefTest.kt | 10 - .../cpg/frontends/java/DeclarationHandler.kt | 4 +- .../aisec/cpg/graph/types/TypeTests.kt | 66 +-- 23 files changed, 419 insertions(+), 613 deletions(-) rename cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/{SecondaryTypeEdge.kt => HasSecondaryTypeEdge.kt} (84%) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationManager.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationManager.kt index dee1e5ac01..55f3ee5469 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationManager.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationManager.kt @@ -109,7 +109,6 @@ private constructor( log.debug("Cleaning up {} Frontends", executedFrontends.size) executedFrontends.forEach { it.cleanup() } - ctx.typeManager.cleanup() } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TypeManager.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TypeManager.kt index 0081741eb8..14f846c4f2 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TypeManager.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TypeManager.kt @@ -32,21 +32,12 @@ import de.fraunhofer.aisec.cpg.graph.declarations.Declaration import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.TemplateDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.TypedefDeclaration -import de.fraunhofer.aisec.cpg.graph.scopes.NameScope -import de.fraunhofer.aisec.cpg.graph.scopes.RecordScope import de.fraunhofer.aisec.cpg.graph.scopes.Scope import de.fraunhofer.aisec.cpg.graph.scopes.TemplateScope import de.fraunhofer.aisec.cpg.graph.types.* -import de.fraunhofer.aisec.cpg.graph.types.PointerType.PointerOrigin import java.util.* -import java.util.function.Consumer -import java.util.stream.Collectors -import org.apache.commons.lang3.builder.ToStringBuilder -import org.slf4j.LoggerFactory class TypeManager { - private val typeToRecord = Collections.synchronizedMap(HashMap()) - /** * Stores the relationship between parameterized RecordDeclarations (e.g. Classes using * Generics) to the ParameterizedType to be able to resolve the Type of the fields, since @@ -59,8 +50,8 @@ class TypeManager { mutableMapOf>() ) - val firstOrderTypes = Collections.synchronizedSet(HashSet()) - val secondOrderTypes = Collections.synchronizedSet(HashSet()) + val firstOrderTypes: MutableSet = Collections.synchronizedSet(HashSet()) + val secondOrderTypes: MutableSet = Collections.synchronizedSet(HashSet()) /** * @param recordDeclaration that is instantiated by a template containing parameterizedtypes @@ -101,7 +92,7 @@ class TypeManager { * @param name name of the ParameterizedType we are looking for * @return */ - fun getTypeParameter( + private fun getTypeParameter( templateDeclaration: TemplateDeclaration, name: String ): ParameterizedType? { @@ -196,8 +187,8 @@ class TypeManager { return parameterizedType } - fun registerType(t: T): T { - if (t!!.isFirstOrderType) { + fun registerType(t: T): T { + if (t.isFirstOrderType) { firstOrderTypes.add(t) } else { secondOrderTypes.add(t) @@ -210,300 +201,6 @@ class TypeManager { return firstOrderTypes.stream().anyMatch { type: Type -> type.root.name.toString() == name } } - fun isUnknown(type: Type?): Boolean { - return type is UnknownType - } - - /** - * @param generics the list of parameter types - * @return true if the generics contain parameterized Types - */ - fun containsParameterizedType(generics: List): Boolean { - for (t in generics) { - if (t is ParameterizedType) { - return true - } - } - return false - } - - private fun rewrapType( - type: Type, - depth: Int, - pointerOrigins: Array, - reference: Boolean, - referenceType: ReferenceType? - ): Optional { - var type = type - if (depth > 0) { - for (i in depth - 1 downTo 0) { - type = type.reference(pointerOrigins[i]) - } - } - if (reference) { - referenceType!!.elementType = type - return Optional.of(referenceType) - } - return Optional.of(type) - } - - private fun unwrapTypes(types: Collection, wrapState: WrapState): Set { - // TODO Performance: This method is called very often (for each setType()) and does four - // iterations over "types". Reduce number of iterations. - var types = types - val original: Set = HashSet(types) - val unwrappedTypes = mutableSetOf() - var pointerOrigins = arrayOfNulls(0) - var depth = 0 - var counter = 0 - var reference = false - var referenceType: ReferenceType? = null - val t1 = types.stream().findAny().orElse(null) - if (t1 is ReferenceType) { - for (t in types) { - referenceType = t as ReferenceType? - if (!referenceType!!.isSimilar(t)) { - return emptySet() - } - unwrappedTypes.add(t.elementType) - reference = true - } - types = unwrappedTypes - } - val t2 = types.stream().findAny().orElse(null) - if (t2 is PointerType) { - for (t in types) { - if (counter == 0) { - depth = t.referenceDepth - counter++ - } - if (t.referenceDepth != depth) { - return emptySet() - } - unwrappedTypes.add(t.root) - pointerOrigins = arrayOfNulls(depth) - var containedType: Type = t2 - var i = 0 - pointerOrigins[i] = (containedType as PointerType).pointerOrigin - while (containedType is PointerType) { - containedType = containedType.elementType - if (containedType is PointerType) { - pointerOrigins[++i] = containedType.pointerOrigin - } - } - } - } - wrapState.depth = depth - wrapState.setPointerOrigin(pointerOrigins) - wrapState.isReference = reference - wrapState.referenceType = referenceType - return if (unwrappedTypes.isEmpty() && original.isNotEmpty()) { - original - } else { - unwrappedTypes - } - } - - /** - * This function is a relict from the old ages. It iterates through a collection of types and - * returns the type they have in *common*. For example, if two types `A` and `B` both derive - * from the interface `C`` then `C` would be returned. Because this contains some legacy code - * that does crazy stuff, we need access to scope information, so we can build a map between - * type information and their record declarations. We want to get rid of that in the future. - * - * @param types the types to compare - * @param ctx a [TranslationContext]. - * @return the common type - */ - fun getCommonType(types: Collection, ctx: TranslationContext?): Optional { - var types = types - val provider = ctx!!.scopeManager - - // TODO: Documentation needed. - val sameType = - (types - .stream() - .map { t: Type -> t.javaClass.canonicalName } - .collect(Collectors.toSet()) - .size == 1) - if (!sameType) { - // No commonType for different Types - return Optional.empty() - } - val wrapState = WrapState() - types = unwrapTypes(types, wrapState) - if (types.isEmpty()) { - return Optional.empty() - } else if (types.size == 1) { - return rewrapType( - types.iterator().next(), - wrapState.depth, - wrapState.pointerOrigins, - wrapState.isReference, - wrapState.referenceType - ) - } - val scope = provider.scope ?: return Optional.empty() - - // We need to find the global scope - val globalScope = scope.globalScope ?: return Optional.empty() - for (child in globalScope.children) { - if (child is RecordScope && child.astNode is RecordDeclaration) { - typeToRecord[(child.astNode as RecordDeclaration?)!!.toType()] = - child.astNode as RecordDeclaration? - } - - // HACKY HACK HACK - if (child is NameScope) { - for (child2 in child.children) { - if (child2 is RecordScope && child2.astNode is RecordDeclaration) { - typeToRecord[(child2.astNode as RecordDeclaration?)!!.toType()] = - child2.astNode as RecordDeclaration? - } - } - } - } - val allAncestors = - types - .map { t: Type? -> typeToRecord.getOrDefault(t, null) } - .filter { obj: RecordDeclaration? -> Objects.nonNull(obj) } - .map { r: RecordDeclaration? -> getAncestors(r, 0) } - - // normalize/reverse depth: roots start at 0, increasing on each level - for (ancestors in allAncestors) { - val farthest = ancestors.stream().max(Comparator.comparingInt(Ancestor::depth)) - if (farthest.isPresent) { - val maxDepth: Int = farthest.get().depth - ancestors.forEach(Consumer { a: Ancestor -> a.depth = (maxDepth - a.depth) }) - } - } - var commonAncestors: MutableSet = HashSet() - for (i in allAncestors.indices) { - if (i == 0) { - commonAncestors.addAll(allAncestors[i]) - } else { - val others = allAncestors[i] - val newCommonAncestors = mutableSetOf() - // like Collection#retainAll but swaps relevant items out if the other set's - // matching ancestor has a higher depth - for (curr in commonAncestors) { - val toRetain = - others - .filter { a: Ancestor -> a == curr } - .map { a: Ancestor -> if (curr.depth >= a.depth) curr else a } - .firstOrNull() - toRetain?.let { newCommonAncestors.add(it) } - } - commonAncestors = newCommonAncestors - } - } - val lca = commonAncestors.stream().max(Comparator.comparingInt(Ancestor::depth)) - val commonType = lca.map { it.record?.toType() ?: it.record.unknownType() } - val finalType: Type - finalType = - if (commonType.isPresent) { - commonType.get() - } else { - return commonType - } - return rewrapType( - finalType, - wrapState.depth, - wrapState.pointerOrigins, - wrapState.isReference, - wrapState.referenceType - ) - } - - private fun getAncestors(recordDeclaration: RecordDeclaration?, depth: Int): Set { - if (recordDeclaration!!.superTypes.isEmpty()) { - val ret = HashSet() - ret.add(Ancestor(recordDeclaration, depth)) - return ret - } - val ancestors = - recordDeclaration.superTypes - .stream() - .map { s: Type? -> typeToRecord.getOrDefault(s, null) } - .filter { obj: RecordDeclaration? -> Objects.nonNull(obj) } - .map { s: RecordDeclaration? -> getAncestors(s, depth + 1) } - .flatMap { obj: Set -> obj.stream() } - .collect(Collectors.toSet()) - ancestors.add(Ancestor(recordDeclaration, depth)) - return ancestors - } - - fun isSupertypeOf(superType: Type, subType: Type, provider: MetadataProvider): Boolean { - var language: Language<*>? = null - val ctx: TranslationContext? - if (superType is UnknownType && subType is UnknownType) return true - if (superType.referenceDepth != subType.referenceDepth) { - return false - } - if (provider is LanguageProvider) { - language = provider.language - } - ctx = - if (provider is ContextProvider) { - provider.ctx - } else { - log.error("Missing context provider") - return false - } - - // arrays and pointers match in C/C++ - // TODO: Make this independent from the specific language - if (isCXX(language) && checkArrayAndPointer(superType, subType)) { - return true - } - - // ObjectTypes can be passed as ReferenceTypes - if (superType is ReferenceType) { - return isSupertypeOf(superType.elementType, subType, provider) - } - - // We cannot proceed without a scope provider - if (provider !is ScopeProvider) { - return false - } - val commonType = getCommonType(HashSet(java.util.List.of(superType, subType)), ctx) - return if (commonType.isPresent) { - commonType.get() == superType - } else { - // If array depth matches: check whether these are types from the standard library - try { - val superCls = Class.forName(superType.typeName) - val subCls = Class.forName(subType.typeName) - superCls.isAssignableFrom(subCls) - } catch (e: ClassNotFoundException) { - // Not in the class path or other linkage exception, can't help here - false - } catch (e: NoClassDefFoundError) { - false - } - } - } - - private fun isCXX(language: Language<*>?): Boolean { - return (language != null && - (language.javaClass.simpleName == "CLanguage" || - language.javaClass.simpleName == "CPPLanguage")) - } - - fun checkArrayAndPointer(first: Type, second: Type): Boolean { - val firstDepth = first.referenceDepth - val secondDepth = second.referenceDepth - return if (firstDepth == secondDepth) { - (first.root.name == second.root.name && first.isSimilar(second)) - } else { - false - } - } - - fun cleanup() { - typeToRecord.clear() - } - /** * Creates a typedef / type alias in the form of a [TypedefDeclaration] to the scope manager and * returns it. @@ -544,72 +241,203 @@ class TypeManager { return if (applicable == null) { alias } else { - reWrapType(alias, applicable) + alias.changeRoot(applicable) } } +} - /** - * Reconstructs the type chain when the root node is modified e.g. when swapping with alias - * (typedef) - * - * @param oldChain containing all types until the root - * @param newRoot root the chain is swapped with - * @return oldchain but root replaced with newRoot - */ - private fun reWrapType(oldChain: Type, newRoot: Type): Type { - if (oldChain.isFirstOrderType) { - newRoot.typeOrigin = oldChain.typeOrigin - } - if (!newRoot.isFirstOrderType) { - return newRoot - } - return when { - oldChain is ObjectType && newRoot is ObjectType -> { - (newRoot.root as ObjectType).generics = oldChain.generics - newRoot - } - oldChain is ReferenceType -> { - val reference = reWrapType(oldChain.elementType, newRoot) - val newChain = oldChain.duplicate() as ReferenceType - newChain.elementType = reference - newChain.refreshName() - newChain - } - oldChain is PointerType -> { - val newChain = oldChain.duplicate() as PointerType - newChain.root = reWrapType(oldChain.root, newRoot) - newChain.refreshNames() - newChain - } - else -> newRoot - } +val Type.ancestors: Set + get() { + return this.getAncestors(0) } - private class Ancestor(val record: RecordDeclaration?, var depth: Int) { +internal fun Type.getAncestors(depth: Int): Set { + val types = mutableSetOf() - override fun hashCode(): Int { - return Objects.hash(record) + // Recursively call ourselves on our super types. There is a little hack here that we need to do + // for object types created from RecordDeclaration::toType() because their supertypes might not + // be set correctly. This would be better, if we change a RecordDeclaration to a + // ValueDeclaration and set the corresponding object type to its type. + val superTypes = + if (this is ObjectType) { + this.recordDeclaration?.superTypes ?: setOf() + } else { + superTypes } - override fun equals(other: Any?): Boolean { - if (this === other) { - return true - } - if (other !is Ancestor) { - return false + types += superTypes.flatMap { it.getAncestors(depth + 1) } + + // Since the chain starts with our type, we add ourselves to it + types += Type.Ancestor(this, depth) + + return types +} + +/** Checks, if this [Type] is either derived from or equals to [superType]. */ +fun Type.isDerivedFrom(superType: Type): Boolean { + // Retrieve all ancestor types of our type (more concretely of the root type) + val root = this.root + val superTypes = root.ancestors.map { it.type } + + // Check, if super type (or its root) is in the list + return superType.root in superTypes +} + +/** + * This computed property returns the common type in a [Collection] of [Type] objects. For example, + * if two types `A` and `B` both derive from the interface `C`` then `C` would be returned. + * + * More specifically, the lowest common ancestors (LCA) in a tree containing all ancestors of all + * types in the set is returned. + */ +val Collection.commonType: Type? + get() { + // If we only have one type, we can just directly return it + val single = this.singleOrNull() + if (single != null) { + return single + } + + // Make sure, we only compare types of the same "kind" of type (e.g. ObjectType vs. + // NumericType) + val sameKind = this.map { it::class.simpleName }.toSet().size == 1 + if (!sameKind) { + return null + } + + // We also need to make sure that we compare the same reference depth and wrap state + // (which contains the pointer origins), because otherwise we need to re-create the + // equivalent wrap state at the end. Make sure we only have one wrap state before we + // proceed. + val wrapStates = this.map { it.wrapState }.toSet() + val wrapState = wrapStates.singleOrNull() ?: return null + + // Build all ancestors out of the root types. This way we compare the most inner type, + // regardless of the wrap state. + val allAncestors = this.map { it.root.ancestors } + + // Find the lowest common ancestor (LCA) by maintaining a list of common ancestors, filling + // them with the ancestors of the first type and then eliminate the list of common ancestors + // step-by-step by looping over the ancestor list of all other types. + var commonAncestors = allAncestors.first().toList() + for (others in allAncestors.subList(1, allAncestors.size)) { + // In the remaining loop, we are trying to eliminate potential candidates from the + // list, or more specifically, we are doing an intersect of both lists. If both have an + // ancestor in common, but on a different depth, the item which has a higher depth is + // chosen. + commonAncestors = + commonAncestors.mapNotNull { ancestor -> + val other = + others.find { + // The equals/hashcode method of an Ancestor will ignore its depth, but + // only look at its type. Therefore, ancestors with the same type but + // different depths will match here. + it == ancestor + } + ?: return@mapNotNull null + + // We then need to select one of both, depending on the depth + if (ancestor.depth >= other.depth) { + ancestor + } else { + other + } + } + } + + // Find the one with the largest depth (which is closest to the original type, since the + // root node is 0) and re-wrap the final common type back into the original wrap state + return commonAncestors.minByOrNull(Type.Ancestor::depth)?.type?.wrap(wrapState) + } + +/** + * Calculates and returns the [WrapState] of the current type. A [WrapState] can be used to compute + * a "wrapped" type, for example a [PointerType] back from its [Type.root]. + */ +val Type.wrapState: WrapState + get() { + val wrapState = WrapState() + var type = this + + // A reference can only be the last item, so we check if the most outer type is a reference + // type + if (type is ReferenceType) { + wrapState.referenceType = this as ReferenceType? + wrapState.isReference = true + type = type.elementType + } + + // We already know the depth, so we can just set this and allocate the pointer origins array + wrapState.depth = this.referenceDepth + wrapState.pointerOrigins = arrayOfNulls(wrapState.depth) + + // If we have a pointer type, "unwrap" the type until we are back at the element type + if (type is PointerType) { + var i = 0 + wrapState.pointerOrigins[i] = type.pointerOrigin + while (type is PointerType) { + type = type.elementType + if (type is PointerType) { + wrapState.pointerOrigins[++i] = type.pointerOrigin + } } - return record == other.record } - override fun toString(): String { - return ToStringBuilder(this, Node.TO_STRING_STYLE) - .append("record", record!!.name) - .append("depth", depth) - .toString() + return wrapState + } + +/** + * Wraps the given [Type] into a chain of [PointerType]s and [ReferenceType]s, given the + * instructions in [WrapState]. + */ +fun Type.wrap(wrapState: WrapState): Type { + var type = this + if (wrapState.depth > 0) { + for (i in wrapState.depth - 1 downTo 0) { + type = type.reference(wrapState.pointerOrigins[i]) } } - companion object { - private val log = LoggerFactory.getLogger(TypeManager::class.java) + if (wrapState.isReference) { + wrapState.referenceType?.elementType = type + return wrapState.referenceType!! + } + + return type +} + +/** + * Reconstructs the type chain when the root node is modified e.g. when swapping with alias + * (typedef) + * + * @param newRoot root the chain is swapped with + * @return the type but root replaced with newRoot + */ +private fun Type.changeRoot(newRoot: Type): Type { + if (this.isFirstOrderType) { + newRoot.typeOrigin = this.typeOrigin + } + if (!newRoot.isFirstOrderType) { + return newRoot + } + return when { + this is ObjectType && newRoot is ObjectType -> { + newRoot.generics = this.generics + newRoot + } + this is ReferenceType -> { + val reference = this.elementType.changeRoot(newRoot) + val newChain = this.duplicate() as ReferenceType + newChain.elementType = reference + newChain.refreshName() + newChain + } + this is PointerType -> { + val newChain = this.duplicate() as PointerType + newChain.root = this.root.changeRoot(newRoot) + newChain.refreshNames() + newChain + } + else -> newRoot } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/FunctionDeclaration.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/FunctionDeclaration.kt index 3ac1f9f573..e92b8d7e7a 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/FunctionDeclaration.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/FunctionDeclaration.kt @@ -34,7 +34,7 @@ import de.fraunhofer.aisec.cpg.graph.statements.CompoundStatement import de.fraunhofer.aisec.cpg.graph.statements.Statement import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression import de.fraunhofer.aisec.cpg.graph.types.Type -import de.fraunhofer.aisec.cpg.graph.types.isSupertypeOf +import de.fraunhofer.aisec.cpg.isDerivedFrom import java.util.* import java.util.stream.Collectors import org.apache.commons.lang3.builder.ToStringBuilder @@ -131,7 +131,7 @@ open class FunctionDeclaration : ValueDeclaration(), DeclarationHolder { return true } val provided = targetSignature[i] - if (!isSupertypeOf(declared.type, provided)) { + if (!provided.isDerivedFrom(declared.type)) { return false } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/RecordDeclaration.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/RecordDeclaration.kt index f9bf682090..a759f55c03 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/RecordDeclaration.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/RecordDeclaration.kt @@ -30,16 +30,15 @@ import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.propertyEqualsList import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdgeDelegate import de.fraunhofer.aisec.cpg.graph.statements.Statement +import de.fraunhofer.aisec.cpg.graph.types.HasSecondaryTypeEdge import de.fraunhofer.aisec.cpg.graph.types.ObjectType import de.fraunhofer.aisec.cpg.graph.types.Type -import java.util.stream.Collectors -import java.util.stream.Stream import org.apache.commons.lang3.builder.ToStringBuilder import org.neo4j.ogm.annotation.Relationship import org.neo4j.ogm.annotation.Transient /** Represents a C++ union/struct/class or Java class */ -class RecordDeclaration : Declaration(), DeclarationHolder, StatementHolder { +class RecordDeclaration : Declaration(), DeclarationHolder, StatementHolder, HasSecondaryTypeEdge { /** The kind, i.e. struct, class, union or enum. */ var kind: String? = null @@ -87,7 +86,7 @@ class RecordDeclaration : Declaration(), DeclarationHolder, StatementHolder { * * @return the list of implemented interfaces */ - @Transient var implementedInterfaces: List = ArrayList() + @Transient var implementedInterfaces = mutableListOf() @Relationship var superTypeDeclarations: Set = HashSet() @@ -150,10 +149,7 @@ class RecordDeclaration : Declaration(), DeclarationHolder, StatementHolder { * * @return concatenation of [.getSuperClasses] and [.getImplementedInterfaces] */ - get() = - Stream.of(superClasses, implementedInterfaces) - .flatMap { obj: List -> obj.stream() } - .collect(Collectors.toList()) + get() = superClasses + implementedInterfaces /** * Adds a type to the list of super classes for this record declaration. @@ -177,6 +173,22 @@ class RecordDeclaration : Declaration(), DeclarationHolder, StatementHolder { .toString() } + override fun updateType(typeState: Collection) { + // Replace occurrences of the super classes and interfaces with the one combined type + replaceType(superClasses, typeState) + replaceType(implementedInterfaces, typeState) + } + + private fun replaceType(list: MutableList, typeState: Collection) { + for ((idx, t) in list.withIndex()) { + for (newType in typeState) { + if (newType == t) { + list[idx] = newType + } + } + } + } + override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is RecordDeclaration) return false @@ -220,6 +232,7 @@ class RecordDeclaration : Declaration(), DeclarationHolder, StatementHolder { // this here. type.recordDeclaration = this } + type.superTypes.addAll(this.superTypes) return type } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/TypeParamDeclaration.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/TypeParamDeclaration.kt index 22176fd81e..e3e1940342 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/TypeParamDeclaration.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/TypeParamDeclaration.kt @@ -27,18 +27,18 @@ package de.fraunhofer.aisec.cpg.graph.declarations import de.fraunhofer.aisec.cpg.graph.AST import de.fraunhofer.aisec.cpg.graph.HasDefault -import de.fraunhofer.aisec.cpg.graph.types.SecondaryTypeEdge +import de.fraunhofer.aisec.cpg.graph.types.HasSecondaryTypeEdge import de.fraunhofer.aisec.cpg.graph.types.Type import java.util.* import org.neo4j.ogm.annotation.Relationship /** A declaration of a type template parameter */ -class TypeParamDeclaration : ValueDeclaration(), SecondaryTypeEdge, HasDefault { +class TypeParamDeclaration : ValueDeclaration(), HasSecondaryTypeEdge, HasDefault { /** * TemplateParameters can define a default for the type parameter Since the primary type edge * points to the ParameterizedType, the default edge is a secondary type edge. Therefore, the - * TypeResolver requires to implement the [SecondaryTypeEdge] to be aware of the edge to be able - * to merge the type nodes. + * TypeResolver requires to implement the [HasSecondaryTypeEdge] to be aware of the edge to be + * able to merge the type nodes. */ @Relationship(value = "DEFAULT", direction = Relationship.Direction.OUTGOING) @AST diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/CallExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/CallExpression.kt index e9b895359f..867348b768 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/CallExpression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/CallExpression.kt @@ -26,6 +26,7 @@ package de.fraunhofer.aisec.cpg.graph.statements.expressions import de.fraunhofer.aisec.cpg.PopulatedByPass +import de.fraunhofer.aisec.cpg.commonType import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.TemplateDeclaration @@ -36,7 +37,7 @@ import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.propertyEqualsL import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.transformIntoOutgoingPropertyEdgeList import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.unwrap import de.fraunhofer.aisec.cpg.graph.types.* -import de.fraunhofer.aisec.cpg.graph.types.SecondaryTypeEdge +import de.fraunhofer.aisec.cpg.graph.types.HasSecondaryTypeEdge import de.fraunhofer.aisec.cpg.passes.CallResolver import de.fraunhofer.aisec.cpg.passes.VariableUsageResolver import java.util.* @@ -47,7 +48,8 @@ import org.neo4j.ogm.annotation.Relationship * An expression, which calls another function. It has a list of arguments (list of [Expression]s) * and is connected via the INVOKES edge to its [FunctionDeclaration]. */ -open class CallExpression : Expression(), HasType.TypeObserver, SecondaryTypeEdge, ArgumentHolder { +open class CallExpression : + Expression(), HasType.TypeObserver, HasSecondaryTypeEdge, ArgumentHolder { /** * Connection to its [FunctionDeclaration]. This will be populated by the [CallResolver]. This * will have an effect on the [type] @@ -275,16 +277,19 @@ open class CallExpression : Expression(), HasType.TypeObserver, SecondaryTypeEdg // TODO(oxisto): We could actually use the newType (which is a FunctionType now) val types = - invokeEdges.map(PropertyEdge::end).mapNotNull { - if (it.returnTypes.size == 1) { - return@mapNotNull it.returnTypes.firstOrNull() - } else if (it.returnTypes.size > 1) { - return@mapNotNull TupleType(it.returnTypes) + invokeEdges + .map(PropertyEdge::end) + .mapNotNull { + if (it.returnTypes.size == 1) { + return@mapNotNull it.returnTypes.firstOrNull() + } else if (it.returnTypes.size > 1) { + return@mapNotNull TupleType(it.returnTypes) + } + null } - null - } - val alternative = if (types.isNotEmpty()) types[0] else unknownType() - val commonType = getCommonType(types).orElse(alternative) + .toSet() + val alternative = if (types.isNotEmpty()) types.first() else unknownType() + val commonType = types.commonType ?: alternative this.type = commonType } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ConditionalExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ConditionalExpression.kt index 0374329164..9ba7a88023 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ConditionalExpression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ConditionalExpression.kt @@ -25,10 +25,10 @@ */ package de.fraunhofer.aisec.cpg.graph.statements.expressions +import de.fraunhofer.aisec.cpg.commonType import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.types.HasType import de.fraunhofer.aisec.cpg.graph.types.Type -import de.fraunhofer.aisec.cpg.graph.types.getCommonType import java.util.Objects import org.apache.commons.lang3.builder.ToStringBuilder @@ -77,13 +77,13 @@ class ConditionalExpression : Expression(), ArgumentHolder, BranchingNode, HasTy } override fun typeChanged(newType: Type, src: HasType) { - val types = mutableListOf() + val types = mutableSetOf() thenExpr?.type?.let { types.add(it) } elseExpr?.type?.let { types.add(it) } - val alternative = if (types.isNotEmpty()) types[0] else unknownType() - this.type = getCommonType(types).orElse(alternative) + val alternative = if (types.isNotEmpty()) types.first() else unknownType() + this.type = types.commonType ?: alternative } override fun assignedTypeChanged(assignedTypes: Set, src: HasType) { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/SecondaryTypeEdge.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/HasSecondaryTypeEdge.kt similarity index 84% rename from cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/SecondaryTypeEdge.kt rename to cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/HasSecondaryTypeEdge.kt index c0519df0d6..2f708cd20f 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/SecondaryTypeEdge.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/HasSecondaryTypeEdge.kt @@ -30,10 +30,10 @@ import de.fraunhofer.aisec.cpg.passes.TypeResolver /** * The [TypeResolver] needs to be aware of all outgoing edges to types in order to merge equal types - * to the same node. For the primary type edge, this is achieved through the hasType interface. If a - * node has additional type edges (e.g. [TypeParamDeclaration.default]) the node must implement the - * [updateType] method, so that the current type is always replaced with the merged one + * to the same node. For the primary type edge, this is achieved through the [HasType] interface. If + * a node has additional type edges (e.g. [TypeParamDeclaration.default]) the node must implement + * the [updateType] method, so that the current type is always replaced with the merged one. */ -interface SecondaryTypeEdge { +fun interface HasSecondaryTypeEdge { fun updateType(typeState: Collection) } 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 8df00cad74..a3e2a73968 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 @@ -34,7 +34,6 @@ import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExp import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression import de.fraunhofer.aisec.cpg.graph.statements.expressions.Literal import de.fraunhofer.aisec.cpg.graph.statements.expressions.UnaryOperator -import java.util.* /** * This interfaces denotes that the given [Node] has a "type". Currently, we only have two known @@ -186,17 +185,7 @@ interface HasType : ContextProvider, LanguageProvider { } } -fun Node.isSupertypeOf(superType: Type, subType: Type): Boolean { - val c = ctx ?: throw TranslationException("context not available") - return c.typeManager.isSupertypeOf(superType, subType, this) -} - fun Node.registerType(type: T): T { val c = ctx ?: throw TranslationException("context not available") return c.typeManager.registerType(type) } - -fun Node.getCommonType(types: Collection): Optional { - val c = ctx ?: throw TranslationException("context not available") - return c.typeManager.getCommonType(types, this.ctx) -} diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/ObjectType.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/ObjectType.kt index bbe1d32282..76629e485c 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/ObjectType.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/ObjectType.kt @@ -25,6 +25,7 @@ */ package de.fraunhofer.aisec.cpg.graph.types +import de.fraunhofer.aisec.cpg.PopulatedByPass import de.fraunhofer.aisec.cpg.frontends.Language import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration import de.fraunhofer.aisec.cpg.graph.edge.Properties @@ -33,7 +34,8 @@ import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.propertyEqualsL import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.transformIntoOutgoingPropertyEdgeList import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdgeDelegate import de.fraunhofer.aisec.cpg.graph.types.PointerType.PointerOrigin -import de.fraunhofer.aisec.cpg.graph.types.UnknownType.Companion.getUnknownType +import de.fraunhofer.aisec.cpg.graph.unknownType +import de.fraunhofer.aisec.cpg.passes.TypeResolver import java.util.* import org.neo4j.ogm.annotation.Relationship @@ -41,11 +43,12 @@ import org.neo4j.ogm.annotation.Relationship * This is the main type in the Type system. ObjectTypes describe objects, as instances of a class. * This also includes primitive data types. */ -open class ObjectType : Type, SecondaryTypeEdge { +open class ObjectType : Type, HasSecondaryTypeEdge { /** - * Reference from the ObjectType to its class (RecordDeclaration) only if the class is available + * Reference from the [ObjectType] to its class ([RecordDeclaration]), only if the class is + * available. This is set by the [TypeResolver]. */ - var recordDeclaration: RecordDeclaration? = null + @PopulatedByPass(TypeResolver::class) var recordDeclaration: RecordDeclaration? = null @Relationship(value = "GENERICS", direction = Relationship.Direction.OUTGOING) var genericsPropertyEdges: MutableList> = mutableListOf() @@ -109,11 +112,11 @@ open class ObjectType : Type, SecondaryTypeEdge { } /** - * @return UnknownType, as we cannot infer any type information when dereferencing an + * @return UnknownType, as we cannot infer any type information when de-referencing an * ObjectType, as it is just some memory and its interpretation is unknown */ override fun dereference(): Type { - return getUnknownType(language) + return unknownType() } override fun duplicate(): Type { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/Type.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/Type.kt index 4ebd21cef4..8324271847 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/Type.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/Type.kt @@ -195,4 +195,32 @@ abstract class Type : Node { companion object { const val UNKNOWN_TYPE_STRING = "UNKNOWN" } + + /** + * An ancestor is an item in a tree of types spanning from one particular [Type] to all of its + * [Type.superTypes] (and their [Type.superTypes], and so on). Each item holds information about + * the current "depth" within the tree. + */ + class Ancestor(val type: Type, var depth: Int) { + override fun hashCode(): Int { + return Objects.hash(type) + } + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + if (other !is Ancestor) { + return false + } + return type == other.type + } + + override fun toString(): String { + return ToStringBuilder(this, TO_STRING_STYLE) + .append("type", type.name) + .append("depth", depth) + .toString() + } + } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/WrapState.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/WrapState.kt index c84ee87448..42d6b36824 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/WrapState.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/WrapState.kt @@ -26,19 +26,39 @@ package de.fraunhofer.aisec.cpg.graph.types import de.fraunhofer.aisec.cpg.graph.types.PointerType.PointerOrigin +import java.util.* -/** Stores State for rewrap when typeinformation has been unwrapped */ +/** + * A [WrapState] holds information how a "wrapped" type (for example a [PointerType]) is built from + * its element types(s). This can potentially be a chain of different pointer/array operations. + */ class WrapState { - @JvmField var depth = 0 + /** The total depth of "wrapping". This is usually equal to [Type.referenceDepth] */ + var depth = 0 + + /** + * An array of [PointerType.PointerOrigin] values, applied in the order the types are wrapped + * in. + */ + var pointerOrigins: Array = arrayOf(PointerOrigin.ARRAY) + + /** True, if the original type was a [ReferenceType]. It is then stored in [referenceType]. */ var isReference = false - @JvmField var pointerOrigins: Array - @JvmField var referenceType: ReferenceType? = null - init { - pointerOrigins = arrayOf(PointerOrigin.ARRAY) + /** The [ReferenceType], if [isReference] is true. */ + var referenceType: ReferenceType? = null + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is WrapState) return false + + return depth == other.depth && + isReference == other.isReference && + pointerOrigins.contentEquals(other.pointerOrigins) && + referenceType == other.referenceType } - fun setPointerOrigin(pointerOrigin: Array) { - pointerOrigins = pointerOrigin + override fun hashCode(): Int { + return Objects.hash(depth, isReference, pointerOrigins.contentHashCode(), referenceType) } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CXXCallResolverHelper.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CXXCallResolverHelper.kt index ed9a77e044..17e807fd89 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CXXCallResolverHelper.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CXXCallResolverHelper.kt @@ -26,11 +26,11 @@ package de.fraunhofer.aisec.cpg.passes 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.* import de.fraunhofer.aisec.cpg.graph.statements.expressions.* import de.fraunhofer.aisec.cpg.graph.types.* +import de.fraunhofer.aisec.cpg.isDerivedFrom import java.util.HashMap import java.util.regex.Pattern @@ -40,16 +40,12 @@ import java.util.regex.Pattern * @return true if the CallExpression signature can be transformed into the FunctionDeclaration * signature by means of casting */ -fun compatibleSignatures( - call: CallExpression, - callSignature: List, - functionSignature: List -): Boolean { +fun compatibleSignatures(callSignature: List, functionSignature: List): Boolean { return if (callSignature.size == functionSignature.size) { for (i in callSignature.indices) { if ( callSignature[i].isPrimitive != functionSignature[i].isPrimitive && - !call.isSupertypeOf(functionSignature[i], callSignature[i]) + !callSignature[i].isDerivedFrom(functionSignature[i]) ) { return false } @@ -103,7 +99,7 @@ fun resolveWithImplicitCast( for (functionDeclaration in initialInvocationCandidates) { val callSignature = getCallSignatureWithDefaults(call, functionDeclaration) // Check if the signatures match by implicit casts - if (compatibleSignatures(call, callSignature, functionDeclaration.signatureTypes)) { + if (compatibleSignatures(callSignature, functionDeclaration.signatureTypes)) { val implicitCastTargets = signatureWithImplicitCastTransformation( call, @@ -118,7 +114,7 @@ fun resolveWithImplicitCast( // to the same target type checkMostCommonImplicitCast(implicitCasts, implicitCastTargets) } - if (compatibleSignatures(call, call.signature, functionDeclaration.signatureTypes)) { + if (compatibleSignatures(call.signature, functionDeclaration.signatureTypes)) { invocationTargetsWithImplicitCast.add(functionDeclaration) } else { invocationTargetsWithImplicitCastAndDefaults.add(functionDeclaration) @@ -269,7 +265,6 @@ fun resolveConstructorWithImplicitCast( } if ( compatibleSignatures( - constructExpression, constructExpression.signature, constructorDeclaration.signatureTypes, ) @@ -285,7 +280,6 @@ fun resolveConstructorWithImplicitCast( return constructorDeclaration } else if ( compatibleSignatures( - constructExpression, workingSignature, constructorDeclaration.signatureTypes, ) @@ -468,8 +462,7 @@ fun getTemplateInitializationSignature( templateCall: CallExpression, instantiationType: MutableMap, orderedInitializationSignature: MutableMap, - explicitInstantiated: MutableList, - ctx: TranslationContext + explicitInstantiated: MutableList ): Map? { // Construct Signature val signature = @@ -478,8 +471,7 @@ fun getTemplateInitializationSignature( templateCall, instantiationType, orderedInitializationSignature, - explicitInstantiated, - ctx + explicitInstantiated ) ?: return null val parameterizedTypeResolution = getParameterizedSignaturesFromInitialization(signature) @@ -532,15 +524,14 @@ fun constructTemplateInitializationSignatureFromTemplateParameters( templateCall: CallExpression, instantiationType: MutableMap, orderedInitializationSignature: MutableMap, - explicitInstantiated: MutableList, - ctx: TranslationContext + explicitInstantiated: MutableList ): MutableMap? { val instantiationSignature: MutableMap = HashMap() for (i in functionTemplateDeclaration.parameters.indices) { if (i < templateCall.templateParameters.size) { val callParameter = templateCall.templateParameters[i] val templateParameter = functionTemplateDeclaration.parameters[i] - if (isInstantiated(callParameter, templateParameter, ctx.typeManager)) { + if (isInstantiated(callParameter, templateParameter)) { instantiationSignature[templateParameter] = callParameter instantiationType[callParameter] = TemplateDeclaration.TemplateInitialization.EXPLICIT @@ -575,11 +566,7 @@ fun constructTemplateInitializationSignatureFromTemplateParameters( * callParameterArg must be an Expression and its type must match the type of the * ParamVariableDeclaration (same type or subtype) => returns true Otherwise return false */ -fun isInstantiated( - callParameterArg: Node, - templateParameter: Declaration?, - typeManager: TypeManager -): Boolean { +fun isInstantiated(callParameterArg: Node, templateParameter: Declaration?): Boolean { var callParameter = callParameterArg if (callParameter is TypeExpression) { callParameter = callParameter.type @@ -588,7 +575,7 @@ fun isInstantiated( callParameter is ObjectType } else if (callParameter is Expression && templateParameter is ParamVariableDeclaration) { callParameter.type == templateParameter.type || - typeManager.isSupertypeOf(templateParameter.type, callParameter.type, callParameterArg) + callParameter.type.isDerivedFrom(templateParameter.type) } else { false } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt index 7cdc6d1acc..a1cf6f69f3 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt @@ -40,6 +40,7 @@ import de.fraunhofer.aisec.cpg.graph.types.Type import de.fraunhofer.aisec.cpg.helpers.IdentitySet import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker import de.fraunhofer.aisec.cpg.helpers.Util +import de.fraunhofer.aisec.cpg.isDerivedFrom import de.fraunhofer.aisec.cpg.passes.order.DependsOn import de.fraunhofer.aisec.cpg.passes.order.ReplacePass import java.util.* @@ -603,7 +604,7 @@ open class EvaluationOrderGraphPass(ctx: TranslationContext) : TranslationUnitPa val catchParam = catchClause.parameter if (catchParam == null) { // e.g. catch (...) currentPredecessors.addAll(eogEdges) - } else if (typeManager.isSupertypeOf(catchParam.type, throwType, node)) { + } else if (throwType.isDerivedFrom(catchParam.type)) { currentPredecessors.addAll(eogEdges) toRemove.add(throwType) } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/SymbolResolverPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/SymbolResolverPass.kt index fbd440fab5..3f04655003 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/SymbolResolverPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/SymbolResolverPass.kt @@ -32,7 +32,15 @@ import de.fraunhofer.aisec.cpg.graph.declarations.* import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression import de.fraunhofer.aisec.cpg.graph.types.* import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker +import de.fraunhofer.aisec.cpg.passes.order.DependsOn +/** + * A common class for passes that resolve symbols. In order to do that, we need both: + * - our "resolved" (squashed) types and their associated records (see [TypeResolver]) + * - and the type hierarchy (see [TypeHierarchyResolver]). + */ +@DependsOn(TypeResolver::class) +@DependsOn(TypeHierarchyResolver::class) abstract class SymbolResolverPass(ctx: TranslationContext) : ComponentPass(ctx) { protected lateinit var walker: SubgraphWalker.ScopedWalker lateinit var currentTU: TranslationUnitDeclaration diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TypeHierarchyResolver.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TypeHierarchyResolver.kt index 2e5f10af8f..41cdded80b 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TypeHierarchyResolver.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TypeHierarchyResolver.kt @@ -30,9 +30,11 @@ import de.fraunhofer.aisec.cpg.graph.Component import de.fraunhofer.aisec.cpg.graph.Name import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.declarations.EnumDeclaration +import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration -import de.fraunhofer.aisec.cpg.graph.types.Type +import de.fraunhofer.aisec.cpg.graph.types.ObjectType +import de.fraunhofer.aisec.cpg.passes.order.DependsOn import de.fraunhofer.aisec.cpg.processing.IVisitor import de.fraunhofer.aisec.cpg.processing.strategy.Strategy import java.util.* @@ -54,6 +56,7 @@ import java.util.* * at places where it is crucial to have parsed all [RecordDeclaration]s. Otherwise, type * information in the graph might not be fully correct */ +@DependsOn(TypeResolver::class) open class TypeHierarchyResolver(ctx: TranslationContext) : ComponentPass(ctx) { protected val recordMap = mutableMapOf() protected val enums = mutableListOf() @@ -69,7 +72,7 @@ open class TypeHierarchyResolver(ctx: TranslationContext) : ComponentPass(ctx) { } for (enumDecl in enums) { val directSupertypeRecords = - enumDecl.superTypes.mapNotNull { s: Type -> recordMap[s.name] }.toSet() + enumDecl.superTypes.mapNotNull { (it as? ObjectType)?.recordDeclaration }.toSet() val allSupertypes = directSupertypeRecords.map { findSupertypeRecords(it) }.flatten().toSet() enumDecl.superTypeDeclarations = allSupertypes @@ -99,7 +102,8 @@ open class TypeHierarchyResolver(ctx: TranslationContext) : ComponentPass(ctx) { } protected fun findSupertypeRecords(recordDecl: RecordDeclaration): Set { - val superTypeDeclarations = recordDecl.superTypes.mapNotNull { recordMap[it.name] }.toSet() + val superTypeDeclarations = + recordDecl.superTypes.mapNotNull { (it as ObjectType).recordDeclaration }.toSet() recordDecl.superTypeDeclarations = superTypeDeclarations return superTypeDeclarations } 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 6e38a9a360..ef82aaf708 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 @@ -30,11 +30,9 @@ import de.fraunhofer.aisec.cpg.graph.Component import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration import de.fraunhofer.aisec.cpg.graph.types.* -import de.fraunhofer.aisec.cpg.graph.types.SecondaryTypeEdge +import de.fraunhofer.aisec.cpg.graph.types.HasSecondaryTypeEdge import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker.IterativeGraphWalker -import de.fraunhofer.aisec.cpg.passes.order.DependsOn -@DependsOn(CallResolver::class) open class TypeResolver(ctx: TranslationContext) : ComponentPass(ctx) { protected val firstOrderTypes = mutableSetOf() protected val typeState = mutableMapOf>() @@ -197,13 +195,13 @@ open class TypeResolver(ctx: TranslationContext) : ComponentPass(ctx) { * ensures that the if a nodes contains secondary type edges, those types are also merged and no * duplicate is left * - * @param node implementing [SecondaryTypeEdge] + * @param node implementing [HasSecondaryTypeEdge] */ protected fun ensureUniqueSecondaryTypeEdge(node: Node) { - if (node is SecondaryTypeEdge) { + if (node is HasSecondaryTypeEdge) { node.updateType(typeState.keys) - } else if (node is HasType && node.type is SecondaryTypeEdge) { - (node.type as SecondaryTypeEdge).updateType(typeState.keys) + } else if (node is HasType && node.type is HasSecondaryTypeEdge) { + (node.type as HasSecondaryTypeEdge).updateType(typeState.keys) } } diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/types/TypePropagationTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/types/TypePropagationTest.kt index 22449001cd..05f85022ef 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/types/TypePropagationTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/types/TypePropagationTest.kt @@ -367,6 +367,19 @@ class TypePropagationTest { ) } + val baseClassType = (b.type as? PointerType)?.elementType + assertNotNull(baseClassType) + + assertEquals( + baseClassType.array(), + setOf( + baseClassType.array(), + derivedClassA.toType().array(), + derivedClassB.toType().array(), + ) + .commonType + ) + val assign = (body as CompoundStatement).statements(1) assertNotNull(assign) diff --git a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CPPLanguage.kt b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CPPLanguage.kt index 03ade6b6a3..8a39561a65 100644 --- a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CPPLanguage.kt +++ b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CPPLanguage.kt @@ -282,8 +282,7 @@ class CPPLanguage : templateCall, initializationType, orderedInitializationSignature, - explicitInstantiation, - ctx + explicitInstantiation ) val function = functionTemplateDeclaration.realization[0] if ( diff --git a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/types/TypeTests.kt b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/types/TypeTests.kt index 12104eccd2..873a2bc158 100644 --- a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/types/TypeTests.kt +++ b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/types/TypeTests.kt @@ -172,12 +172,12 @@ internal class TypeTests : BaseTest() { val topLevel = Path.of("src", "test", "resources", "compiling", "hierarchy", "multistep") val result = analyze("simple_inheritance.cpp", topLevel, true) - val root = objectType("Root") - val level0 = objectType("Level0") - val level1 = objectType("Level1") - val level1b = objectType("Level1B") - val level2 = objectType("Level2") - val unrelated = objectType("Unrelated") + val root = assertNotNull(result.records["Root"]).toType() + val level0 = assertNotNull(result.records["Level0"]).toType() + val level1 = assertNotNull(result.records["Level1"]).toType() + val level1b = assertNotNull(result.records["Level1B"]).toType() + val level2 = assertNotNull(result.records["Level2"]).toType() + val unrelated = assertNotNull(result.records["Unrelated"]).toType() getCommonTypeTestGeneral(root, level0, level1, level1b, level2, unrelated, result) } } @@ -186,73 +186,42 @@ internal class TypeTests : BaseTest() { @Throws(Exception::class) @Test fun testCommonTypeTestCppMultiInheritance() { - with( - CXXLanguageFrontend( - CPPLanguage(), - TranslationContext( - TranslationConfiguration.builder().build(), - ScopeManager(), - TypeManager() - ) - ) - ) { - val topLevel = - Path.of("src", "test", "resources", "compiling", "hierarchy", "multistep") - val result = analyze("multi_inheritance.cpp", topLevel, true) - - val root = objectType("Root") - val level0 = objectType("Level0") - val level0b = objectType("Level0B") - val level1 = objectType("Level1") - val level1b = objectType("Level1B") - val level1c = objectType("Level1C") - val level2 = objectType("Level2") - val level2b = objectType("Level2B") - - val typeManager = result.finalCtx.typeManager - /* - Type hierarchy: - Root------------ - | | - Level0 Level0B | - / \ / \ | - Level1 Level1B Level1C - | \ / - Level2 Level2B - */ - // Root is the top, but unrelated to Level0B - for (t in listOf(root, level0, level1, level1b, level1c, level2, level2b)) { - assertEquals(Optional.of(t), typeManager.getCommonType(listOf(t), result.finalCtx)) - } - assertEquals( - Optional.empty(), - typeManager.getCommonType(listOf(root, level0b), result.finalCtx) - ) - for (t in listOf(level0, level1, level2)) { - assertEquals( - Optional.empty(), - typeManager.getCommonType(listOf(t, level0b), result.finalCtx) - ) - } - assertEquals( - Optional.of(level0b), - typeManager.getCommonType(listOf(level1b, level1c), result.finalCtx) - ) - assertEquals( - Optional.of(level0), - typeManager.getCommonType(listOf(level1, level1b, level2, level2b), result.finalCtx) - ) - assertEquals( - Optional.of(root), - typeManager.getCommonType(listOf(level1, level1c), result.finalCtx) - ) + val topLevel = Path.of("src", "test", "resources", "compiling", "hierarchy", "multistep") + val result = analyze("multi_inheritance.cpp", topLevel, true) + + val root = assertNotNull(result.records["Root"]).toType() + val level0 = assertNotNull(result.records["Level0"]).toType() + val level0b = assertNotNull(result.records["Level0B"]).toType() + val level1 = assertNotNull(result.records["Level1"]).toType() + val level1b = assertNotNull(result.records["Level1B"]).toType() + val level1c = assertNotNull(result.records["Level1C"]).toType() + val level2 = assertNotNull(result.records["Level2"]).toType() + val level2b = assertNotNull(result.records["Level2B"]).toType() - // level2 and level2b have two intersections, both root and level0 -> level0 is lower - assertEquals( - Optional.of(level0), - typeManager.getCommonType(listOf(level2, level2b), result.finalCtx) - ) + /* + Type hierarchy: + Root------------ + | | + Level0 Level0B | + / \ / \ | + Level1 Level1B Level1C + | \ / + Level2 Level2B + */ + // Root is the top, but unrelated to Level0B + for (t in listOf(root, level0, level1, level1b, level1c, level2, level2b)) { + assertEquals(t, setOf(t).commonType) } + /*assertEquals(null, setOf(root, level0b).commonType) + for (t in listOf(level0, level1, level2)) { + assertEquals(null, setOf(t, level0b).commonType) + }*/ + assertEquals(level0b, setOf(level1b, level1c).commonType) + assertEquals(level0, setOf(level1, level1b, level2, level2b).commonType) + assertEquals(root, setOf(level1, level1c).commonType) + + // level2 and level2b have two intersections, both root and level0 -> level0 is lower + assertEquals(level0, setOf(level2, level2b).commonType) } @Test @@ -301,49 +270,31 @@ internal class TypeTests : BaseTest() { // A single type is its own least common ancestor for (t in listOf(root, level0, level1, level1b, level2)) { - assertEquals(Optional.of(t), typeManager.getCommonType(listOf(t), result.finalCtx)) + assertEquals(t, setOf(t).commonType) } // Root is the root of all types for (t in listOf(level0, level1, level1b, level2)) { - assertEquals( - Optional.of(root), - typeManager.getCommonType(listOf(t, root), result.finalCtx) - ) + assertEquals(root, setOf(t, root).commonType) } // Level0 is above all types but Root for (t in listOf(level1, level1b, level2)) { - assertEquals( - Optional.of(level0), - typeManager.getCommonType(listOf(t, level0), result.finalCtx) - ) + assertEquals(level0, setOf(t, level0).commonType) } // Level1 and Level1B have Level0 as common ancestor - assertEquals( - Optional.of(level0), - typeManager.getCommonType(listOf(level1, level1b), result.finalCtx) - ) + assertEquals(level0, setOf(level1, level1b).commonType) // Level2 and Level1B have Level0 as common ancestor - assertEquals( - Optional.of(level0), - typeManager.getCommonType(listOf(level2, level1b), result.finalCtx) - ) + assertEquals(level0, setOf(level2, level1b).commonType) // Level1 and Level2 have Level1 as common ancestor - assertEquals( - Optional.of(level1), - typeManager.getCommonType(listOf(level1, level2), result.finalCtx) - ) + assertEquals(level1, setOf(level1, level2).commonType) // Check unrelated type behavior: No common root class for (t in listOf(root, level0, level1, level1b, level2)) { - assertEquals( - Optional.empty(), - typeManager.getCommonType(listOf(unrelated, t), result.finalCtx) - ) + assertEquals(null, setOf(unrelated, t).commonType) } } } diff --git a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/types/TypedefTest.kt b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/types/TypedefTest.kt index 73ba90efaa..b9bab4af9e 100644 --- a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/types/TypedefTest.kt +++ b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/types/TypedefTest.kt @@ -84,7 +84,6 @@ internal class TypedefTest : BaseTest() { fun testWithModifier() { val result = analyze("cpp", topLevel, true) val variables = result.variables - val typeManager = result.finalCtx.typeManager // pointer val l1ptr = findByUniqueName(variables, "l1ptr") @@ -94,15 +93,6 @@ internal class TypedefTest : BaseTest() { assertEquals(l1ptr.type, l2ptr.type) assertEquals(l1ptr.type, l3ptr.type) assertEquals(l1ptr.type, l4ptr.type) - - // arrays - val l1arr = findByUniqueName(variables, "l1arr") - val l2arr = findByUniqueName(variables, "l2arr") - val l3arr = findByUniqueName(variables, "l3arr") - val l4arr = findByUniqueName(variables, "l4arr") - assertTrue(typeManager.checkArrayAndPointer(l1arr.type, l2arr.type)) - assertTrue(typeManager.checkArrayAndPointer(l1arr.type, l3arr.type)) - assertTrue(typeManager.checkArrayAndPointer(l1arr.type, l4arr.type)) } @Test diff --git a/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/DeclarationHandler.kt b/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/DeclarationHandler.kt index 2e6ab669df..c837e27051 100644 --- a/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/DeclarationHandler.kt +++ b/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/DeclarationHandler.kt @@ -187,7 +187,9 @@ open class DeclarationHandler(lang: JavaLanguageFrontend) : .map { type -> frontend.getTypeAsGoodAsPossible(type) } .toMutableList() recordDeclaration.implementedInterfaces = - classInterDecl.implementedTypes.map { type -> frontend.getTypeAsGoodAsPossible(type) } + classInterDecl.implementedTypes + .map { type -> frontend.getTypeAsGoodAsPossible(type) } + .toMutableList() frontend.typeManager.addTypeParameter( recordDeclaration, diff --git a/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/types/TypeTests.kt b/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/types/TypeTests.kt index 6d89f197a0..1a9e5fc359 100644 --- a/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/types/TypeTests.kt +++ b/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/types/TypeTests.kt @@ -30,7 +30,6 @@ import de.fraunhofer.aisec.cpg.TestUtils.analyze import de.fraunhofer.aisec.cpg.TestUtils.findByName import de.fraunhofer.aisec.cpg.TestUtils.findByUniqueName import de.fraunhofer.aisec.cpg.frontends.java.JavaLanguage -import de.fraunhofer.aisec.cpg.frontends.java.JavaLanguageFrontend import de.fraunhofer.aisec.cpg.graph.* import java.nio.file.Path import java.util.* @@ -122,34 +121,15 @@ internal class TypeTests : BaseTest() { @Throws(Exception::class) @Test fun testCommonTypeTestJava() { - with( - JavaLanguageFrontend( - JavaLanguage(), - TranslationContext( - TranslationConfiguration.builder().build(), - ScopeManager(), - TypeManager() - ) - ) - ) { - val topLevel = Path.of("src", "test", "resources", "compiling", "hierarchy") - val result = analyze("java", topLevel, true) { it.registerLanguage(JavaLanguage()) } - val root = objectType("multistep.Root") - val level0 = objectType("multistep.Level0") - val level1 = objectType("multistep.Level1") - val level1b = objectType("multistep.Level1B") - val level2 = objectType("multistep.Level2") - val unrelated = objectType("multistep.Unrelated") - getCommonTypeTestGeneral( - root, - level0, - level1, - level1b, - level2, - unrelated, - result.finalCtx - ) - } + val topLevel = Path.of("src", "test", "resources", "compiling", "hierarchy") + val result = analyze("java", topLevel, true) { it.registerLanguage(JavaLanguage()) } + val root = assertNotNull(result.records["multistep.Root"]).toType() + val level0 = assertNotNull(result.records["multistep.Level0"]).toType() + val level1 = assertNotNull(result.records["multistep.Level1"]).toType() + val level1b = assertNotNull(result.records["multistep.Level1B"]).toType() + val level2 = assertNotNull(result.records["multistep.Level2"]).toType() + val unrelated = assertNotNull(result.records["multistep.Unrelated"]).toType() + getCommonTypeTestGeneral(root, level0, level1, level1b, level2, unrelated) } private fun getCommonTypeTestGeneral( @@ -158,8 +138,7 @@ internal class TypeTests : BaseTest() { level1: Type, level1b: Type, level2: Type, - unrelated: Type, - ctx: TranslationContext + unrelated: Type ) { /* Type hierarchy: @@ -171,44 +150,33 @@ internal class TypeTests : BaseTest() { | Level2 */ - val provider = ctx.scopeManager - // A single type is its own least common ancestor for (t in listOf(root, level0, level1, level1b, level2)) { - assertEquals(Optional.of(t), ctx.typeManager.getCommonType(listOf(t), ctx)) + assertEquals(t, setOf(t).commonType) } // Root is the root of all types for (t in listOf(level0, level1, level1b, level2)) { - assertEquals(Optional.of(root), ctx.typeManager.getCommonType(listOf(t, root), ctx)) + assertEquals(root, setOf(t, root).commonType) } // Level0 is above all types but Root for (t in listOf(level1, level1b, level2)) { - assertEquals(Optional.of(level0), ctx.typeManager.getCommonType(listOf(t, level0), ctx)) + assertEquals(level0, setOf(t, level0).commonType) } // Level1 and Level1B have Level0 as common ancestor - assertEquals( - Optional.of(level0), - ctx.typeManager.getCommonType(listOf(level1, level1b), ctx) - ) + assertEquals(level0, setOf(level1, level1b).commonType) // Level2 and Level1B have Level0 as common ancestor - assertEquals( - Optional.of(level0), - ctx.typeManager.getCommonType(listOf(level2, level1b), ctx) - ) + assertEquals(level0, setOf(level2, level1b).commonType) // Level1 and Level2 have Level1 as common ancestor - assertEquals( - Optional.of(level1), - ctx.typeManager.getCommonType(listOf(level1, level2), ctx) - ) + assertEquals(level1, setOf(level1, level2).commonType) // Check unrelated type behavior: No common root class for (t in listOf(root, level0, level1, level1b, level2)) { - assertEquals(Optional.empty(), ctx.typeManager.getCommonType(listOf(unrelated, t), ctx)) + assertEquals(null, setOf(unrelated, t).commonType) } } } From f97178e7a2e30a747fb99cea0a27bea08d066cbe Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 16 Aug 2023 09:18:56 +0200 Subject: [PATCH 123/143] Update plugin node to v6 (#1280) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 627ee42d3c..df7568c893 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -56,4 +56,4 @@ kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin"} dokka = { id = "org.jetbrains.dokka", version.ref = "kotlin" } sonarqube = { id = "org.sonarqube", version.ref = "sonarqube" } spotless = { id = "com.diffplug.spotless", version.ref = "spotless" } -node = { id = "com.github.node-gradle.node", version = "5.0.0"} +node = { id = "com.github.node-gradle.node", version = "6.0.0"} From a2570d6eb91d11704e939e1b68d2c58b5fc230e2 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 17 Aug 2023 09:07:06 +0200 Subject: [PATCH 124/143] Update dependency @rollup/plugin-node-resolve to v15.2.0 (#1281) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- cpg-language-typescript/src/main/nodejs/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cpg-language-typescript/src/main/nodejs/package-lock.json b/cpg-language-typescript/src/main/nodejs/package-lock.json index 3ff17cd4b5..b1685a25a2 100644 --- a/cpg-language-typescript/src/main/nodejs/package-lock.json +++ b/cpg-language-typescript/src/main/nodejs/package-lock.json @@ -49,9 +49,9 @@ } }, "node_modules/@rollup/plugin-node-resolve": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.1.0.tgz", - "integrity": "sha512-xeZHCgsiZ9pzYVgAo9580eCGqwh/XCEUM9q6iQfGNocjgkufHAqC3exA+45URvhiYV8sBF9RlBai650eNs7AsA==", + "version": "15.2.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.2.0.tgz", + "integrity": "sha512-mKur03xNGT8O9ODO6FtT43ITGqHWZbKPdVJHZb+iV9QYcdlhUUB0wgknvA4KCUmC5oHJF6O2W1EgmyOQyVUI4Q==", "dev": true, "dependencies": { "@rollup/pluginutils": "^5.0.1", From fdc1c9652ab2881801bd31fbc0518e5ec0dddd38 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 18 Aug 2023 08:38:33 +0200 Subject: [PATCH 125/143] Update plugin node to v7 (#1284) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index df7568c893..03055b3406 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -56,4 +56,4 @@ kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin"} dokka = { id = "org.jetbrains.dokka", version.ref = "kotlin" } sonarqube = { id = "org.sonarqube", version.ref = "sonarqube" } spotless = { id = "com.diffplug.spotless", version.ref = "spotless" } -node = { id = "com.github.node-gradle.node", version = "6.0.0"} +node = { id = "com.github.node-gradle.node", version = "7.0.0"} From f3156b2862962fa42f420ed33c79b42caee8c829 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 18 Aug 2023 06:49:38 +0000 Subject: [PATCH 126/143] Update dependency gradle to v8.3 (#1283) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/wrapper/gradle-wrapper.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 15de90249f..db9a6b825d 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From a204d2c5a9a53b633c4f181832ef27aa99639a4f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 23 Aug 2023 08:52:34 +0200 Subject: [PATCH 127/143] Update dependency org.mockito:mockito-core to v5.5.0 (#1286) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 03055b3406..18a1cb8d8e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -37,7 +37,7 @@ llvm = { module = "org.bytedeco:llvm-platform", version = "16.0.4-1.5.9"} # test junit-params = { module = "org.junit.jupiter:junit-jupiter-params", version = "5.10.0"} -mockito = { module = "org.mockito:mockito-core", version = "5.4.0"} +mockito = { module = "org.mockito:mockito-core", version = "5.5.0"} # plugins needed for build.gradle.kts in buildSrc kotlin-gradle = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } From 30d15c439bd545747027923d40db1bf9aba22d35 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 25 Aug 2023 08:59:23 +0200 Subject: [PATCH 128/143] Update dependency typescript to v5.2.2 (#1291) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- cpg-language-typescript/src/main/nodejs/package-lock.json | 8 ++++---- cpg-language-typescript/src/main/nodejs/package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cpg-language-typescript/src/main/nodejs/package-lock.json b/cpg-language-typescript/src/main/nodejs/package-lock.json index b1685a25a2..38c5a539c2 100644 --- a/cpg-language-typescript/src/main/nodejs/package-lock.json +++ b/cpg-language-typescript/src/main/nodejs/package-lock.json @@ -7,7 +7,7 @@ "license": "Apache-2.0", "dependencies": { "@types/node": "^18.0.0", - "typescript": "5.1.3" + "typescript": "5.2.2" }, "devDependencies": { "@rollup/plugin-commonjs": "^25.0.3", @@ -404,9 +404,9 @@ "dev": true }, "node_modules/typescript": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.3.tgz", - "integrity": "sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/cpg-language-typescript/src/main/nodejs/package.json b/cpg-language-typescript/src/main/nodejs/package.json index 75842f4954..e62bf96a60 100644 --- a/cpg-language-typescript/src/main/nodejs/package.json +++ b/cpg-language-typescript/src/main/nodejs/package.json @@ -6,7 +6,7 @@ }, "dependencies": { "@types/node": "^18.0.0", - "typescript": "5.1.3" + "typescript": "5.2.2" }, "license": "Apache-2.0", "devDependencies": { From 87ad51e69063b3ebcf61a480b65ec77e37a746f7 Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Fri, 25 Aug 2023 09:42:01 +0200 Subject: [PATCH 129/143] Added `TupleDeclaration` (#1285) * Added `TupleDeclaration` This PR adds a new type of `Declaration`, which represenst a tuple of variable declarations. This is primarily needed for languages, such as Go, which support the assignment of multiple values in an initializer. In this case we cannot assign the initializer of the individual variables, but assigned it to the `TupleDeclaration` instead. Currently, the type of the `TupleDeclaration` and its elements MUST be set to the `AutoType`. * Added DFG edges for TupleDeclaration * DFGTest fails successfully * Fix cfsensitivedfgpass * Fix * Extend DFG spec * Create EOG for tupleDecl * eog spec for tupleDecl * Addressed some review comments * update specs --------- Co-authored-by: Alexander Kuechler --- .../aisec/cpg/graph/DeclarationBuilder.kt | 28 +++ .../graph/declarations/TupleDeclaration.kt | 82 ++++++++ .../graph/declarations/ValueDeclaration.kt | 3 +- .../graph/declarations/VariableDeclaration.kt | 32 ++- .../statements/expressions/Expression.kt | 3 +- .../expressions/InitializerListExpression.kt | 2 +- .../aisec/cpg/graph/types/HasType.kt | 2 +- .../aisec/cpg/graph/types/TupleType.kt | 5 +- .../cpg/passes/ControlFlowSensitiveDFGPass.kt | 35 +++- .../de/fraunhofer/aisec/cpg/passes/DFGPass.kt | 15 ++ .../cpg/passes/EvaluationOrderGraphPass.kt | 8 + .../aisec/cpg/passes/TypeHierarchyResolver.kt | 2 +- .../declarations/TupleDeclarationTest.kt | 191 ++++++++++++++++++ .../de/fraunhofer/aisec/cpg/TestUtils.kt | 3 +- docs/docs/CPG/specs/dfg.md | 89 +++++--- docs/docs/CPG/specs/eog.md | 46 +++++ 16 files changed, 496 insertions(+), 50 deletions(-) create mode 100644 cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/TupleDeclaration.kt create mode 100644 cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/TupleDeclarationTest.kt diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/DeclarationBuilder.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/DeclarationBuilder.kt index ad95413862..06a1cd5ee7 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/DeclarationBuilder.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/DeclarationBuilder.kt @@ -170,6 +170,34 @@ fun MetadataProvider.newVariableDeclaration( return node } +/** + * Creates a new [TupleDeclaration]. The [MetadataProvider] receiver will be used to fill different + * meta-data using [Node.applyMetadata]. Calling this extension function outside of Kotlin requires + * an appropriate [MetadataProvider], such as a [LanguageFrontend] as an additional prepended + * argument. + */ +@JvmOverloads +fun LanguageProvider.newTupleDeclaration( + elements: List, + initializer: Expression?, + rawNode: Any? = null +): TupleDeclaration { + val node = TupleDeclaration() + node.applyMetadata(this, null, rawNode, null, true) + + // Tuples always have an auto-type + node.type = autoType() + + // Also all our elements need to have an auto-type + elements.forEach { it.type = autoType() } + node.elements = elements + + node.initializer = initializer + + log(node) + return node +} + /** * Creates a new [TypedefDeclaration]. The [MetadataProvider] receiver will be used to fill * different meta-data using [Node.applyMetadata]. Calling this extension function outside of Kotlin diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/TupleDeclaration.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/TupleDeclaration.kt new file mode 100644 index 0000000000..9d33d90930 --- /dev/null +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/TupleDeclaration.kt @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2023, 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.graph.declarations + +import de.fraunhofer.aisec.cpg.graph.AST +import de.fraunhofer.aisec.cpg.graph.Name +import de.fraunhofer.aisec.cpg.graph.newTupleDeclaration +import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression +import de.fraunhofer.aisec.cpg.graph.types.AutoType +import de.fraunhofer.aisec.cpg.graph.types.TupleType + +/** + * This declaration models a tuple of different [VariableDeclaration] nodes. This is primarily used + * in languages that support multiple assignments in a declaration, such as Go. The tuple is needed + * because the initializer of this declaration is flowing into the tuple (and then split among its + * elements) rather than flowing into the declarations individually. For example the following code + * + * ```go + * var a,b = call() + * ``` + * + * corresponds to: + * - two [VariableDeclaration] nodes `a` and `b`, with an empty [VariableDeclaration.initializer] + * - a [TupleDeclaration], with the auto-generated name `(a,b)` and [TupleDeclaration.elements] `a` + * and `b` + * - an [TupleDeclaration.initializer] that holds a [CallExpression] to `call`. + * + * Implementation Note #1: The [VariableDeclaration.initializer] of the element variables MUST be + * empty; only the [TupleDeclaration.initializer] must be set. Otherwise we are potentially parsing + * the initializer twice. + * + * Implementation Note #2: Currently, we only support [TupleDeclaration] with an initial [AutoType] + * (set in [newTupleDeclaration]); its actual [TupleType] will be inferred by the + * [TupleDeclaration.initializer] (see [VariableDeclaration.typeChanged] for the implementation). + * + * The same applies to the elements in the tuple. They also need to have an [AutoType], and their + * respective type will be based on a registered type observer to their tuple and implemented also + * in [VariableDeclaration.typeChanged] + */ +class TupleDeclaration : VariableDeclaration() { + /** The list of elements in this tuple. */ + @AST + var elements: List = mutableListOf() + set(value) { + field = value + // Make sure we inform our elements about our type changes + value.forEach { registerTypeObserver(it) } + } + + override var name: Name + get() = Name(elements.joinToString(",", "(", ")") { it.name.toString() }) + set(_) {} + + operator fun plusAssign(element: VariableDeclaration) { + this.elements += element + // Make sure we inform the new element about our type changes + registerTypeObserver(element) + } +} diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/ValueDeclaration.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/ValueDeclaration.kt index 1d3a480452..216c175400 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/ValueDeclaration.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/ValueDeclaration.kt @@ -32,6 +32,7 @@ import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.unwrap import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression import de.fraunhofer.aisec.cpg.graph.types.* +import de.fraunhofer.aisec.cpg.helpers.identitySetOf import de.fraunhofer.aisec.cpg.passes.VariableUsageResolver import java.util.stream.Collectors import org.apache.commons.lang3.builder.ToStringBuilder @@ -39,7 +40,7 @@ import org.neo4j.ogm.annotation.Relationship /** A declaration who has a type. */ abstract class ValueDeclaration : Declaration(), HasType { - override val typeObservers = mutableListOf() + override val typeObservers: MutableSet = identitySetOf() /** The type of this declaration. */ override var type: Type = unknownType() diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/VariableDeclaration.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/VariableDeclaration.kt index 825a10dab9..0cd98b7f1b 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/VariableDeclaration.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/VariableDeclaration.kt @@ -31,6 +31,7 @@ import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExp import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression import de.fraunhofer.aisec.cpg.graph.types.AutoType import de.fraunhofer.aisec.cpg.graph.types.HasType +import de.fraunhofer.aisec.cpg.graph.types.TupleType import de.fraunhofer.aisec.cpg.graph.types.Type import org.apache.commons.lang3.builder.ToStringBuilder import org.neo4j.ogm.annotation.Relationship @@ -85,19 +86,34 @@ open class VariableDeclaration : ValueDeclaration(), HasInitializer, HasType.Typ } override fun typeChanged(newType: Type, src: HasType) { - // Only accept type changes from our initializer, if any - if (src != initializer) { + // Only accept type changes from our initializer; or if the source is a tuple + if (src != initializer && src !is TupleDeclaration) { return } - // In the auto-inference case, we want to set the type of our declaration to the - // declared type of the initializer + // If our type is set to "auto", we want to derive our type from the initializer (or the + // tuple source) if (this.type is AutoType) { - type = newType + // If the source is a tuple, we need to check, if we are really part of the source tuple + // and if yes, on which position + if (src is TupleDeclaration && newType is TupleType) { + // We can then derive our appropriate type out of the tuple type based on the + // position in the tuple + val idx = src.elements.indexOf(this) + if (idx != -1) { + type = newType.types.getOrElse(idx) { unknownType() } + } + } else { + // Otherwise, we can just set the type directly. + type = newType + } } else { - // Otherwise, we are at least interested in what the initializer's type is, to see - // whether we can fill our assigned types with that - addAssignedType(newType) + if (src !is TupleDeclaration) { + // If we are not in "auto" mode, we are at least interested in what the + // initializer's type is, to see + // whether we can fill our assigned types with that + addAssignedType(newType) + } } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/Expression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/Expression.kt index db1e6563ab..1e81f54ee7 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/Expression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/Expression.kt @@ -28,6 +28,7 @@ package de.fraunhofer.aisec.cpg.graph.statements.expressions import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.statements.Statement import de.fraunhofer.aisec.cpg.graph.types.* +import de.fraunhofer.aisec.cpg.helpers.identitySetOf import org.apache.commons.lang3.builder.ToStringBuilder import org.neo4j.ogm.annotation.Transient @@ -43,7 +44,7 @@ import org.neo4j.ogm.annotation.Transient *

This is not possible in Java, the aforementioned code example would prompt a compile error. */ abstract class Expression : Statement(), HasType { - @Transient override val typeObservers = mutableListOf() + @Transient override val typeObservers: MutableSet = identitySetOf() override var type: Type = unknownType() set(value) { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/InitializerListExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/InitializerListExpression.kt index c937cc65bb..9a03dd70a3 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/InitializerListExpression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/InitializerListExpression.kt @@ -78,7 +78,7 @@ class InitializerListExpression : Expression(), ArgumentHolder, HasType.TypeObse // entries in generated code), we skip it here. // // So we just have to look what kind of object we are initializing (its type is stored in - // our "type), to see whether we need to propagate something at all. If it has an array + // our "type"), to see whether we need to propagate something at all. If it has an array // type, we need to propagate an array version of the incoming type. If our "target" is a // regular object type, we do NOT propagate anything at all, because in this case we get the // types of individual fields, and we are not interested in those (yet). 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 a3e2a73968..10ec301f86 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 @@ -102,7 +102,7 @@ interface HasType : ContextProvider, LanguageProvider { * A list of [TypeObserver] objects that will be informed about type changes, usually by * [informObservers]. */ - val typeObservers: MutableList + val typeObservers: MutableSet /** * A [TypeObserver] can be used by its implementing class to observe changes to the diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/TupleType.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/TupleType.kt index 027a1e540f..7bd67d6128 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/TupleType.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/TupleType.kt @@ -26,6 +26,7 @@ package de.fraunhofer.aisec.cpg.graph.types import de.fraunhofer.aisec.cpg.graph.Name +import de.fraunhofer.aisec.cpg.graph.unknownType /** * Represents a tuple of types. Primarily used in resolving function calls with multiple return @@ -43,11 +44,11 @@ class TupleType(types: List) : Type() { } override fun reference(pointer: PointerType.PointerOrigin?): Type { - TODO("Not yet implemented") + return unknownType() } override fun dereference(): Type { - TODO("Not yet implemented") + return unknownType() } override fun duplicate(): Type { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt index d39bfc207e..ab0e153547 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt @@ -77,9 +77,21 @@ open class ControlFlowSensitiveDFGPass(ctx: TranslationContext) : TranslationUni ) for ((key, value) in finalState.generalState) { - key.addAllPrevDFG( - value.elements.filterNot { it is VariableDeclaration && key == it } - ) + if (key is TupleDeclaration) { + // We need a little hack for tuple statements to set the index. We have the + // outer part (i.e., the tuple) here but we generate the DFG edges to the + // elements. We have the indices here, so it's amazing. + key.elements.forEachIndexed { i, element -> + element.addAllPrevDFG( + value.elements.filterNot { it is VariableDeclaration && key == it }, + mutableMapOf(Properties.INDEX to i) + ) + } + } else { + key.addAllPrevDFG( + value.elements.filterNot { it is VariableDeclaration && key == it } + ) + } } } } @@ -118,11 +130,22 @@ open class ControlFlowSensitiveDFGPass(ctx: TranslationContext) : TranslationUni val initializer = (currentNode as? VariableDeclaration)?.initializer if (initializer != null) { // A variable declaration with an initializer => The initializer flows to the - // declaration. - // We also wrote something to this variable declaration + // declaration. This also affects tuples. We split it up later. state.push(currentNode, PowersetLattice(setOf(initializer))) - doubleState.pushToDeclarationsState(currentNode, PowersetLattice(setOf(currentNode))) + if (currentNode is TupleDeclaration) { + // For a tuple declaration, we write the elements in this statement. We do not + // really care about the tuple when using the elements subsequently. + currentNode.elements.forEach { + doubleState.pushToDeclarationsState(it, PowersetLattice(setOf(it))) + } + } else { + // We also wrote something to this variable declaration here. + doubleState.pushToDeclarationsState( + currentNode, + PowersetLattice(setOf(currentNode)) + ) + } } else if (isSimpleAssignment(currentNode)) { // It's an assignment which can have one or multiple things on the lhs and on the // rhs. The lhs could be a declaration or a reference (or multiple of these things). diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/DFGPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/DFGPass.kt index 580ebe69ed..af73e32043 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/DFGPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/DFGPass.kt @@ -29,7 +29,9 @@ import de.fraunhofer.aisec.cpg.TranslationContext import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.declarations.FieldDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration +import de.fraunhofer.aisec.cpg.graph.declarations.TupleDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration +import de.fraunhofer.aisec.cpg.graph.edge.Properties import de.fraunhofer.aisec.cpg.graph.statements.* import de.fraunhofer.aisec.cpg.graph.statements.expressions.* import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker.IterativeGraphWalker @@ -86,6 +88,7 @@ class DFGPass(ctx: TranslationContext) : ComponentPass(ctx) { // Declarations is FieldDeclaration -> handleFieldDeclaration(node) is FunctionDeclaration -> handleFunctionDeclaration(node) + is TupleDeclaration -> handleTupleDeclaration(node) is VariableDeclaration -> handleVariableDeclaration(node) } } @@ -128,6 +131,18 @@ class DFGPass(ctx: TranslationContext) : ComponentPass(ctx) { } } + /** + * Adds the DFG edges for a [TupleDeclaration]. The data flows from initializer to the tuple + * elements. + */ + protected fun handleTupleDeclaration(node: TupleDeclaration) { + node.initializer?.let { initializer -> + node.elements.withIndex().forEach { + it.value.addPrevDFG(initializer, mutableMapOf(Properties.INDEX to it.index)) + } + } + } + /** * Adds the DFG edge for a [VariableDeclaration]. The data flows from initializer to the * variable. diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt index a1cf6f69f3..527a64a4b4 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt @@ -102,6 +102,7 @@ open class EvaluationOrderGraphPass(ctx: TranslationContext) : TranslationUnitPa map[FunctionDeclaration::class.java] = { handleFunctionDeclaration(it as FunctionDeclaration) } + map[TupleDeclaration::class.java] = { handleTupleDeclaration(it as TupleDeclaration) } map[VariableDeclaration::class.java] = { handleVariableDeclaration(it as VariableDeclaration) } @@ -241,6 +242,13 @@ open class EvaluationOrderGraphPass(ctx: TranslationContext) : TranslationUnitPa pushToEOG(node) } + protected fun handleTupleDeclaration(node: TupleDeclaration) { + // analyze the initializer + createEOG(node.initializer) + node.elements.forEach { createEOG(it) } + pushToEOG(node) + } + protected fun handleRecordDeclaration(node: RecordDeclaration) { scopeManager.enterScope(node) handleStatementHolder(node) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TypeHierarchyResolver.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TypeHierarchyResolver.kt index 41cdded80b..026e9bbb92 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TypeHierarchyResolver.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TypeHierarchyResolver.kt @@ -103,7 +103,7 @@ open class TypeHierarchyResolver(ctx: TranslationContext) : ComponentPass(ctx) { protected fun findSupertypeRecords(recordDecl: RecordDeclaration): Set { val superTypeDeclarations = - recordDecl.superTypes.mapNotNull { (it as ObjectType).recordDeclaration }.toSet() + recordDecl.superTypes.mapNotNull { (it as? ObjectType)?.recordDeclaration }.toSet() recordDecl.superTypeDeclarations = superTypeDeclarations return superTypeDeclarations } diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/TupleDeclarationTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/TupleDeclarationTest.kt new file mode 100644 index 0000000000..db4d8e8d55 --- /dev/null +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/TupleDeclarationTest.kt @@ -0,0 +1,191 @@ +/* + * Copyright (c) 2023, 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.graph.declarations + +import de.fraunhofer.aisec.cpg.* +import de.fraunhofer.aisec.cpg.TestUtils.assertInvokes +import de.fraunhofer.aisec.cpg.TestUtils.assertRefersTo +import de.fraunhofer.aisec.cpg.frontends.TestLanguageFrontend +import de.fraunhofer.aisec.cpg.graph.* +import de.fraunhofer.aisec.cpg.graph.builder.* +import de.fraunhofer.aisec.cpg.graph.objectType +import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression +import de.fraunhofer.aisec.cpg.graph.types.TupleType +import kotlin.test.Test +import kotlin.test.assertContains +import kotlin.test.assertIs +import kotlin.test.assertNotNull + +class TupleDeclarationTest { + @Test + fun testTopLevelTuple() { + with( + TestLanguageFrontend( + ctx = + TranslationContext( + TranslationConfiguration.builder().defaultPasses().build(), + ScopeManager(), + TypeManager() + ) + ) + ) { + val result = build { + translationResult { + translationUnit { + function( + "func", + returnTypes = listOf(objectType("MyClass"), objectType("error")) + ) + + // I fear this is too complex for the fluent DSL; so we just use the node + // builder here + val tuple = + newTupleDeclaration( + listOf(newVariableDeclaration("a"), newVariableDeclaration("b")), + newCallExpression(newDeclaredReferenceExpression("func")) + ) + scopeManager.addDeclaration(tuple) + tuple.elements.forEach { scopeManager.addDeclaration(it) } + + function("main") { body { call("print") { ref("a") } } } + } + } + } + + val main = result.functions["main"] + assertNotNull(main) + + val tuple = result.variables["(a,b)"] + assertNotNull(tuple) + assertIs(tuple) + assertIs(tuple.type) + + val call = tuple.initializer as? CallExpression + assertNotNull(call) + assertInvokes(call, result.functions["func"]) + + val a = tuple.elements["a"] + assertNotNull(a) + assertLocalName("MyClass", a.type) + assertContains(a.prevDFG, call) + + val b = tuple.elements["b"] + assertNotNull(b) + assertLocalName("error", b.type) + assertContains(b.prevDFG, call) + + val callPrint = main.calls["print"] + assertNotNull(callPrint) + assertIs(callPrint) + + val arg = callPrint.arguments(0) + assertNotNull(arg) + assertRefersTo(arg, a) + assertContains(arg.prevDFG, a) + } + } + + @Test + fun testFunctionLevelTuple() { + with( + TestLanguageFrontend( + ctx = + TranslationContext( + TranslationConfiguration.builder().defaultPasses().build(), + ScopeManager(), + TypeManager() + ) + ) + ) { + val result = build { + translationResult { + translationUnit { + function( + "func", + returnTypes = listOf(objectType("MyClass"), objectType("error")) + ) + + function("main") { + body { + declare { + // I fear this is too complex for the fluent DSL; so we just use + // the node + // builder here + val tuple = + newTupleDeclaration( + listOf( + newVariableDeclaration("a"), + newVariableDeclaration("b") + ), + newCallExpression( + newDeclaredReferenceExpression("func") + ) + ) + this.addToPropertyEdgeDeclaration(tuple) + scopeManager.addDeclaration(tuple) + tuple.elements.forEach { scopeManager.addDeclaration(it) } + } + call("print") { ref("a") } + } + } + } + } + } + + val main = result.functions["main"] + assertNotNull(main) + + val tuple = main.variables["(a,b)"] + assertNotNull(tuple) + assertIs(tuple) + assertIs(tuple.type) + + val call = tuple.initializer as? CallExpression + assertNotNull(call) + assertInvokes(call, result.functions["func"]) + + val a = tuple.elements["a"] + assertNotNull(a) + assertLocalName("MyClass", a.type) + assertContains(a.prevDFG, call) + + val b = tuple.elements["b"] + assertNotNull(b) + assertLocalName("error", b.type) + assertContains(b.prevDFG, call) + + val callPrint = main.calls["print"] + assertNotNull(callPrint) + assertIs(callPrint) + + val arg = callPrint.arguments(0) + assertNotNull(arg) + assertRefersTo(arg, a) + assertContains(arg.prevDFG, a) + } + } +} diff --git a/cpg-core/src/testFixtures/kotlin/de/fraunhofer/aisec/cpg/TestUtils.kt b/cpg-core/src/testFixtures/kotlin/de/fraunhofer/aisec/cpg/TestUtils.kt index 79435e8922..f7f12b5811 100644 --- a/cpg-core/src/testFixtures/kotlin/de/fraunhofer/aisec/cpg/TestUtils.kt +++ b/cpg-core/src/testFixtures/kotlin/de/fraunhofer/aisec/cpg/TestUtils.kt @@ -221,7 +221,8 @@ object TestUtils { * Asserts, that the call expression given in [call] refers to the expected function declaration * [func]. */ - fun assertInvokes(call: CallExpression, func: FunctionDeclaration?) { + fun assertInvokes(call: CallExpression?, func: FunctionDeclaration?) { + assertNotNull(call) assertContains(call.invokes, func) } diff --git a/docs/docs/CPG/specs/dfg.md b/docs/docs/CPG/specs/dfg.md index d1923a1155..ea827917ce 100755 --- a/docs/docs/CPG/specs/dfg.md +++ b/docs/docs/CPG/specs/dfg.md @@ -54,67 +54,80 @@ Scheme: expression -- DFG --> node; ``` -## BinaryOperator -Interesting fields: +## AssignExpression -* `operatorCode: String`: String representation of the operator -* `lhs: Expression`: The left-hand side of the operation -* `rhs: Expression`: The right-hand side of the operation +Interesting fields: -We have to differentiate between the operators. We can group them into three categories: 1) Assignment, 2) Assignment with a Computation and 3) Computation. +* `lhs: List`: All expressions on the left-hand side of the assignment. +* `rhs: List`: All expressions on the right-hand side of the assignment. -### Case 1: Assignment (`operatorCode: =`) +### Case 1: Normal assignment (`operatorCode: =`) The `rhs` flows to `lhs`. In some languages, it is possible to have an assignment in a subexpression (e.g. `a + (b=1)`). For this reason, if the assignment's ast parent is not a `CompoundStatement` (i.e., a block of statements), we also add a DFG edge to the whole operator. +If the `lhs` consists of multiple variables (or a tuple), we try to split up the `rhs` by the index. If we can't do this, the whole `rhs` flows to all variables in `lhs`. Scheme: ```mermaid flowchart LR - node([BinaryOperator]) -.- rhs(rhs); + node([AssignExpression]) -.- rhs(rhs); rhs -- DFG --> lhs; - node([BinaryOperator]) -.- lhs(lhs); - + node([AssignExpression]) -.- lhs(lhs); ``` ```mermaid flowchart LR - node([BinaryOperator]) -.- lhs(lhs); - node([BinaryOperator]) -.- rhs(rhs); + node([AssignExpression]) -.- lhs(lhs); + node([AssignExpression]) -.- rhs(rhs); rhs -- DFG --> lhs; rhs -- DFG --> node; ``` - ```mermaid - flowchart LR - A[binaryOperator.rhs] -- DFG --> binaryOperator.lhs; - subgraph S[If the ast parent is not a CompoundStatement] - direction LR - binaryOperator.rhs -- DFG --> binaryOperator; - end - A --> S; - ``` - +```mermaid +flowchart LR + A[assignment.rhs] -- DFG --> assignment.lhs; + subgraph S[If the ast parent is not a CompoundStatement] + direction LR + assignment.rhs -- DFG --> assignment; + end + A --> S; +``` -### Case 2: Assignment with a Computation (`operatorCode: *=, /=, %=, +=, -=, <<=, >>=, &=, ^=, |=` ) +If size of `lhs` and `rhs` is equal: +```mermaid +flowchart LR + node([AssignExpression]) -.- rhs("rhs[i]"); + rhs -- "for all i: DFG[i]" --> lhs; + node([AssignExpression]) -.- lhs("lhs[i]"); +``` + +### Case 2: Compound assignment (`operatorCode: *=, /=, %=, +=, -=, <<=, >>=, &=, ^=, |=` ) The `lhs` and the `rhs` flow to the binary operator expression, the binary operator flows to the `lhs`. Scheme: - ```mermaid +```mermaid flowchart LR node([BinaryOperator]) -.- lhs(lhs); node([BinaryOperator]) -.- rhs(rhs); lhs -- DFG --> node; rhs -- DFG --> node; node == DFG ==> lhs; - ``` +``` *Dangerous: We have to ensure that the first two operations are performed before the last one* -### Case 3: Computation +## BinaryOperator + +Interesting fields: + +* `operatorCode: String`: String representation of the operator +* `lhs: Expression`: The left-hand side of the operation +* `rhs: Expression`: The right-hand side of the operation + +We have to differentiate between the operators. We can group them into three categories: 1) Assignment, 2) Assignment with a Computation and 3) Computation. The `lhs` and the `rhs` flow to the binary operator expression. @@ -150,11 +163,11 @@ Interesting fields: The `initializer` flows to the whole expression. Scheme: - ```mermaid +```mermaid flowchart LR node([NewExpression]) -.- initializer(initializer) initializer -- DFG --> node - ``` +``` ## ArraySubscriptionExpression @@ -569,6 +582,26 @@ Scheme: R == next read of ==> node; ``` +## TupleDeclaration + +Interesting fields: + +* `initializer: Expression?`: The value which is used to initialize a variable (if applicable). +* `element: List`: The value which is used to initialize a variable (if applicable). + +The value of the initializer flows to the elements of the tuple declaration. The value of the variable declarations flows to all `DeclaredReferenceExpressions` which read the value before the value of the variable is written to through another reference to the variable. + +Scheme: +```mermaid + flowchart LR + initializer -- "for all i: DFG[i]" --> tuple("elements[i]"); + node([VariableDeclaration]) -.- tuple; + initializer -.- node; + tuple -- DFG --> R[/Node/]; + R == next read of ==> tuple; +``` + + ## Assignment Interesting fields: diff --git a/docs/docs/CPG/specs/eog.md b/docs/docs/CPG/specs/eog.md index b8eaa53dcd..ca8c1bd700 100644 --- a/docs/docs/CPG/specs/eog.md +++ b/docs/docs/CPG/specs/eog.md @@ -89,6 +89,28 @@ flowchart LR nblock1--EOG-->nblock2 ``` +## TupleDeclaration +Represents the declaration of a tuple of variables. + +Interesting fields: + +* `initializer: Expression`: The result of evaluation will initialize the variable. +* `elements: List`: The result of evaluation will initialize the variable. + +Scheme: +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; + prev:::outer --EOG--> child + parent(["TupleDeclaration"]) --EOG--> next:::outer + parent -.-> child["initializer"] + child --EOG--> e0["elements[0]"] + e0 --EOG--> e.["..."] + e. --EOG--> ei["elements[i]"] + ei --EOG--> parent + +``` + ## VariableDeclaration Represents the declaration of a local variable. @@ -273,6 +295,30 @@ flowchart LR rhs --EOG--> node ``` +## AssignExpression + +Interesting fields: + +* `lhs: List`: All expressions on the left-hand side of the assignment (i.e., the target) +* `rhs: List`: All expressions on the right-hand side of the assignment (i.e., the value to be assigned) +* `declarations: List`: All expressions on the left-hand side of the assignment (i.e., the target) + +Scheme: +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; + prev:::outer --EOG--> decl0["declarations[0]"] + decl0 --EOG--> decl.["..."] + decl. --EOG--> decli["declarations[i]"] + decli --EOG--> lhs0["lhs[0]"] + lhs0 --EOG--> lhs.["..."] + lhs. --EOG--> lhsi["lhs[i]"] + lhsi--EOG--> rhs0["rhs[0]"] + rhs0 --EOG--> rhs.["..."] + rhs. --EOG--> rhsi["rhs[i]"] + rhsi --EOG--> node +``` + ## CompoundStatement From 4c54110cf2bb1d24230216bc6060fb93282528f6 Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Fri, 25 Aug 2023 20:12:43 +0200 Subject: [PATCH 130/143] Fixed rather intricate bug in property edge list (#1292) Fixed rather stupid bug in property edge list For some reason wrapping of property edges was always assuming that the edges are "outgoing". This of course created major problems if edges were incoming. --- .../de/fraunhofer/aisec/cpg/graph/Node.kt | 31 +++---------------- .../aisec/cpg/graph/StatementHolder.kt | 11 +++++-- .../TranslationUnitDeclaration.kt | 9 ++---- .../aisec/cpg/graph/edge/PropertyEdge.kt | 29 ++++++++++------- .../statements/expressions/CallExpression.kt | 4 +-- .../cpg/graph/types/FunctionPointerType.kt | 6 ++-- .../aisec/cpg/graph/types/ObjectType.kt | 6 ++-- 7 files changed, 41 insertions(+), 55 deletions(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Node.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Node.kt index 62bab149b2..35e12fc560 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Node.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Node.kt @@ -35,12 +35,8 @@ import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.TypedefDeclaration -import de.fraunhofer.aisec.cpg.graph.edge.DependenceType +import de.fraunhofer.aisec.cpg.graph.edge.* import de.fraunhofer.aisec.cpg.graph.edge.Properties -import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge -import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.unwrap -import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdgeDelegate -import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdgeSetDelegate import de.fraunhofer.aisec.cpg.graph.scopes.GlobalScope import de.fraunhofer.aisec.cpg.graph.scopes.RecordScope import de.fraunhofer.aisec.cpg.graph.scopes.Scope @@ -51,7 +47,6 @@ import de.fraunhofer.aisec.cpg.passes.* import de.fraunhofer.aisec.cpg.processing.IVisitable import de.fraunhofer.aisec.cpg.sarif.PhysicalLocation import java.util.* -import kotlin.collections.ArrayList import org.apache.commons.lang3.builder.ToStringBuilder import org.apache.commons.lang3.builder.ToStringStyle import org.neo4j.ogm.annotation.* @@ -164,33 +159,17 @@ open class Node : IVisitable, Persistable, LanguageProvider, ScopeProvider /** Virtual property for accessing [prevEOGEdges] without property edges. */ @PopulatedByPass(EvaluationOrderGraphPass::class) - var prevEOG: List - get() = unwrap(prevEOGEdges, false) - set(value) { - val propertyEdgesEOG: MutableList> = ArrayList() - - for ((idx, prev) in value.withIndex()) { - val propertyEdge = PropertyEdge(prev, this) - propertyEdge.addProperty(Properties.INDEX, idx) - propertyEdgesEOG.add(propertyEdge) - } - - this.prevEOGEdges = propertyEdgesEOG - } + var prevEOG: List by PropertyEdgeDelegate(Node::prevEOGEdges, false) /** Virtual property for accessing [nextEOGEdges] without property edges. */ @PopulatedByPass(EvaluationOrderGraphPass::class) - var nextEOG: List - get() = unwrap(nextEOGEdges) - set(value) { - this.nextEOGEdges = PropertyEdge.transformIntoOutgoingPropertyEdgeList(value, this) - } + var nextEOG: List by PropertyEdgeDelegate(Node::nextEOGEdges) /** Incoming data flow edges */ @Relationship(value = "DFG", direction = Relationship.Direction.INCOMING) @PopulatedByPass(DFGPass::class, ControlFlowSensitiveDFGPass::class) var prevDFGEdges: MutableList> = mutableListOf() - internal set + protected set /** Virtual property for accessing [prevDFGEdges] without property edges. */ @PopulatedByPass(DFGPass::class, ControlFlowSensitiveDFGPass::class) @@ -200,7 +179,7 @@ open class Node : IVisitable, Persistable, LanguageProvider, ScopeProvider @PopulatedByPass(DFGPass::class, ControlFlowSensitiveDFGPass::class) @Relationship(value = "DFG", direction = Relationship.Direction.OUTGOING) var nextDFGEdges: MutableList> = mutableListOf() - internal set + protected set /** Virtual property for accessing [nextDFGEdges] without property edges. */ @PopulatedByPass(DFGPass::class, ControlFlowSensitiveDFGPass::class) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/StatementHolder.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/StatementHolder.kt index 2ad18b1ea7..f863e5502d 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/StatementHolder.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/StatementHolder.kt @@ -27,8 +27,9 @@ package de.fraunhofer.aisec.cpg.graph import de.fraunhofer.aisec.cpg.graph.edge.Properties import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge -import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.transformIntoOutgoingPropertyEdgeList import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.unwrap +import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.wrap +import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdgeDelegate import de.fraunhofer.aisec.cpg.graph.statements.Statement /** @@ -44,13 +45,17 @@ interface StatementHolder : Holder { /** List of statements as property edges. */ var statementEdges: MutableList> - /** Virtual property to access [statementEdges] without property edges. */ + /** + * Virtual property to access [statementEdges] without property edges. + * + * Note: We cannot use [PropertyEdgeDelegate] because delegates are not allowed in interfaces. + */ var statements: List get() { return unwrap(statementEdges) } set(value) { - statementEdges = transformIntoOutgoingPropertyEdgeList(value, this as Node) + statementEdges = wrap(value, this as Node) } /** diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/TranslationUnitDeclaration.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/TranslationUnitDeclaration.kt index ff4d0b6f62..7a7ad3f1b1 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/TranslationUnitDeclaration.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/TranslationUnitDeclaration.kt @@ -27,11 +27,11 @@ package de.fraunhofer.aisec.cpg.graph.declarations import de.fraunhofer.aisec.cpg.graph.AST import de.fraunhofer.aisec.cpg.graph.DeclarationHolder -import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.StatementHolder import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.propertyEqualsList import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.unwrap +import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdgeDelegate import de.fraunhofer.aisec.cpg.graph.statements.Statement import de.fraunhofer.aisec.cpg.passes.PassTarget import java.util.Objects @@ -63,11 +63,8 @@ class TranslationUnitDeclaration : Declaration(), DeclarationHolder, StatementHo override val declarations: List get() = unwrap(declarationEdges) - override var statements: List - get() = unwrap(statementEdges) - set(value) { - statementEdges = PropertyEdge.transformIntoOutgoingPropertyEdgeList(value, this as Node) - } + override var statements: List by + PropertyEdgeDelegate(TranslationUnitDeclaration::statementEdges) val includes: List get() = unwrap(includeEdges) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edge/PropertyEdge.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edge/PropertyEdge.kt index 654d450377..317a146289 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edge/PropertyEdge.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edge/PropertyEdge.kt @@ -36,6 +36,7 @@ import java.util.* import kotlin.reflect.KMutableProperty1 import kotlin.reflect.KProperty import kotlin.reflect.KProperty1 +import kotlin.reflect.jvm.isAccessible import org.neo4j.ogm.annotation.* import org.neo4j.ogm.annotation.typeconversion.Convert import org.slf4j.LoggerFactory @@ -162,15 +163,21 @@ open class PropertyEdge : Persistable { * @return List of PropertyEdges with the targets of the nodes and index property. */ @JvmStatic - fun transformIntoOutgoingPropertyEdgeList( + fun wrap( nodes: List, - commonRelationshipNode: Node + commonRelationshipNode: Node, + outgoing: Boolean = true ): MutableList> { val propertyEdges: MutableList> = ArrayList() for (n in nodes) { - val propertyEdge = PropertyEdge(commonRelationshipNode, n) + val propertyEdge = + if (outgoing) { + PropertyEdge(commonRelationshipNode, n) + } else { + PropertyEdge(n, commonRelationshipNode) + } propertyEdge.addProperty(Properties.INDEX, propertyEdges.size) - propertyEdges.add(propertyEdge) + propertyEdges.add(propertyEdge as PropertyEdge) } return propertyEdges } @@ -366,10 +373,9 @@ class PropertyEdgeDelegate( operator fun setValue(thisRef: S, property: KProperty<*>, value: List) { if (edge is KMutableProperty1) { - edge.setter.call( - thisRef, - PropertyEdge.transformIntoOutgoingPropertyEdgeList(value, thisRef as Node) - ) + val callable = edge.setter + callable.isAccessible = true + edge.setter.call(thisRef, PropertyEdge.wrap(value, thisRef as Node, outgoing)) } } } @@ -386,10 +392,9 @@ class PropertyEdgeSetDelegate( operator fun setValue(thisRef: S, property: KProperty<*>, value: MutableSet) { if (edge is KMutableProperty1) { - edge.setter.call( - thisRef, - PropertyEdge.transformIntoOutgoingPropertyEdgeList(value.toList(), thisRef as Node) - ) + val callable = edge.setter + callable.isAccessible = true + edge.setter.call(thisRef, PropertyEdge.wrap(value.toList(), thisRef as Node, outgoing)) } } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/CallExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/CallExpression.kt index 867348b768..6a39018d72 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/CallExpression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/CallExpression.kt @@ -34,8 +34,8 @@ import de.fraunhofer.aisec.cpg.graph.declarations.TemplateDeclaration.TemplateIn import de.fraunhofer.aisec.cpg.graph.edge.* import de.fraunhofer.aisec.cpg.graph.edge.Properties import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.propertyEqualsList -import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.transformIntoOutgoingPropertyEdgeList import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.unwrap +import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.wrap import de.fraunhofer.aisec.cpg.graph.types.* import de.fraunhofer.aisec.cpg.graph.types.HasSecondaryTypeEdge import de.fraunhofer.aisec.cpg.passes.CallResolver @@ -74,7 +74,7 @@ open class CallExpression : } set(value) { unwrap(invokeEdges).forEach { it.unregisterTypeObserver(this) } - invokeEdges = transformIntoOutgoingPropertyEdgeList(value, this) + invokeEdges = wrap(value, this) value.forEach { it.registerTypeObserver(this) } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/FunctionPointerType.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/FunctionPointerType.kt index a4d0d47119..0b75a37c7d 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/FunctionPointerType.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/FunctionPointerType.kt @@ -28,8 +28,8 @@ package de.fraunhofer.aisec.cpg.graph.types import de.fraunhofer.aisec.cpg.frontends.Language import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.propertyEqualsList -import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.transformIntoOutgoingPropertyEdgeList import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.unwrap +import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.wrap import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdgeDelegate import de.fraunhofer.aisec.cpg.graph.types.PointerType.PointerOrigin import java.util.* @@ -59,7 +59,7 @@ class FunctionPointerType : Type { language: Language<*>? = null, returnType: Type = UnknownType.getUnknownType(language) ) : super(EMPTY_NAME, language) { - parametersPropertyEdge = transformIntoOutgoingPropertyEdgeList(parameters, this) + parametersPropertyEdge = wrap(parameters, this) this.returnType = returnType } @@ -69,7 +69,7 @@ class FunctionPointerType : Type { language: Language<*>? = null, returnType: Type = UnknownType.getUnknownType(language) ) : super(type) { - parametersPropertyEdge = transformIntoOutgoingPropertyEdgeList(parameters, this) + parametersPropertyEdge = wrap(parameters, this) this.returnType = returnType this.language = language } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/ObjectType.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/ObjectType.kt index 76629e485c..1d4d83cc5d 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/ObjectType.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/ObjectType.kt @@ -31,7 +31,7 @@ import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration import de.fraunhofer.aisec.cpg.graph.edge.Properties import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.propertyEqualsList -import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.transformIntoOutgoingPropertyEdgeList +import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.wrap import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdgeDelegate import de.fraunhofer.aisec.cpg.graph.types.PointerType.PointerOrigin import de.fraunhofer.aisec.cpg.graph.unknownType @@ -61,7 +61,7 @@ open class ObjectType : Type, HasSecondaryTypeEdge { primitive: Boolean, language: Language<*>? ) : super(typeName, language) { - this.genericsPropertyEdges = transformIntoOutgoingPropertyEdgeList(generics, this) + this.genericsPropertyEdges = wrap(generics, this) isPrimitive = primitive this.language = language } @@ -73,7 +73,7 @@ open class ObjectType : Type, HasSecondaryTypeEdge { language: Language<*>? ) : super(type) { this.language = language - this.genericsPropertyEdges = transformIntoOutgoingPropertyEdgeList(generics, this) + this.genericsPropertyEdges = wrap(generics, this) isPrimitive = primitive } From a1b2ebe25ed782f4872f0440a55cdd565853d3f8 Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Mon, 28 Aug 2023 11:15:11 +0200 Subject: [PATCH 131/143] More precise types in arithmetic evaluation (#1290) Previously, we just casted most of our numeric types to `Long` when doing arithmetic operations in the `ValueEvalutor`. This PR tries to optimize that, to at least keep the type if both are the same. It is still somewhat inprecise when two different types of different bitwidth are involved. In these cases we mostly just still cast to `Long`. --- .../aisec/cpg/analysis/MultiValueEvaluator.kt | 29 +- .../aisec/cpg/analysis/NumberExtensions.kt | 301 ++++++++++++++++++ .../aisec/cpg/analysis/NumberSet.kt | 20 ++ .../aisec/cpg/analysis/ValueEvaluator.kt | 102 +----- .../cpg/analysis/MultiValueEvaluatorTest.kt | 59 +++- .../aisec/cpg/analysis/ValueEvaluatorTest.kt | 291 +++++++++++++++-- 6 files changed, 654 insertions(+), 148 deletions(-) create mode 100644 cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/NumberExtensions.kt diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluator.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluator.kt index c649aeecb7..f9e44f0eae 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluator.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluator.kt @@ -181,9 +181,8 @@ class MultiValueEvaluator : ValueEvaluator() { return when (expr.operatorCode) { "-" -> { when (val input = evaluateInternal(expr.input, depth + 1)) { - is Collection<*> -> input.map { n -> (n as? Number)?.negate() } - is Number -> input.negate() - else -> cannotEvaluate(expr, this) + is Collection<*> -> input.map { n -> (n as? Number)?.unaryMinus() } + else -> super.handleUnaryOp(expr, depth) } } "--" -> { @@ -191,9 +190,8 @@ class MultiValueEvaluator : ValueEvaluator() { evaluateInternal(expr.input, depth + 1) } else { when (val input = evaluateInternal(expr.input, depth + 1)) { - is Number -> input.decrement() - is Collection<*> -> input.map { n -> (n as? Number)?.decrement() } - else -> cannotEvaluate(expr, this) + is Collection<*> -> input.map { n -> (n as? Number)?.dec() } + else -> super.handleUnaryOp(expr, depth) } } } @@ -202,15 +200,14 @@ class MultiValueEvaluator : ValueEvaluator() { evaluateInternal(expr.input, depth + 1) } else { when (val input = evaluateInternal(expr.input, depth + 1)) { - is Number -> input.increment() - is Collection<*> -> input.map { n -> (n as? Number)?.increment() } - else -> cannotEvaluate(expr, this) + is Collection<*> -> input.map { n -> (n as? Number)?.inc() } + else -> super.handleUnaryOp(expr, depth) } } } "*" -> evaluateInternal(expr.input, depth + 1) "&" -> evaluateInternal(expr.input, depth + 1) - else -> cannotEvaluate(expr, this) + else -> super.handleUnaryOp(expr, depth) } } @@ -418,22 +415,22 @@ class MultiValueEvaluator : ValueEvaluator() { return when (expr.operatorCode) { "-" -> { when (input) { - is Collection<*> -> input.map { n -> (n as? Number)?.negate() } - is Number -> input.negate() + is Collection<*> -> input.map { n -> (n as? Number)?.unaryMinus() } + is Number -> -input else -> cannotEvaluate(expr, this) } } "--" -> { when (input) { - is Number -> input.decrement() - is Collection<*> -> input.map { n -> (n as? Number)?.decrement() } + is Number -> input.dec() + is Collection<*> -> input.map { n -> (n as? Number)?.dec() } else -> cannotEvaluate(expr, this) } } "++" -> { when (input) { - is Number -> input.increment() - is Collection<*> -> input.map { n -> (n as? Number)?.increment() } + is Number -> input.inc() + is Collection<*> -> input.map { n -> (n as? Number)?.inc() } else -> cannotEvaluate(expr, this) } } diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/NumberExtensions.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/NumberExtensions.kt new file mode 100644 index 0000000000..0b5a48ba6b --- /dev/null +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/NumberExtensions.kt @@ -0,0 +1,301 @@ +/* + * Copyright (c) 2023, 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.analysis + +internal operator fun Number.plus(other: Number): Number = + when (this) { + is Int -> this + other + is Byte -> this + other + is Long -> this + other + is Short -> this + other + is Float -> this + other + is Double -> this + other + else -> throw UnsupportedOperationException() + } + +internal operator fun Int.plus(other: Number): Number = + when (other) { + is Int -> this + other + is Byte -> this + other + is Long -> this + other + is Short -> this + other + is Float -> this + other + is Double -> this + other + else -> throw UnsupportedOperationException() + } + +internal operator fun Byte.plus(other: Number): Number = + when (other) { + is Int -> this + other + is Byte -> this + other + is Long -> this + other + is Short -> this + other + is Float -> this + other + is Double -> this + other + else -> throw UnsupportedOperationException() + } + +internal operator fun Long.plus(other: Number): Number = + when (other) { + is Int -> this + other + is Byte -> this + other + is Long -> this + other + is Short -> this + other + is Float -> this + other + is Double -> this + other + else -> throw UnsupportedOperationException() + } + +internal operator fun Short.plus(other: Number): Number = + when (other) { + is Int -> this + other + is Byte -> this + other + is Long -> this + other + is Short -> this + other + is Float -> this + other + is Double -> this + other + else -> throw UnsupportedOperationException() + } + +internal operator fun Float.plus(other: Number): Number = + when (other) { + is Int -> this + other + is Byte -> this + other + is Long -> this + other + is Short -> this + other + is Float -> this + other + is Double -> this + other + else -> throw UnsupportedOperationException() + } + +internal operator fun Double.plus(other: Number): Number = + when (other) { + is Int -> this + other + is Byte -> this + other + is Long -> this + other + is Short -> this + other + is Float -> this + other + is Double -> this + other + else -> throw UnsupportedOperationException() + } + +internal operator fun Number.minus(other: Number): Number = + when (this) { + is Int -> this + -other + is Byte -> this + -other + is Long -> this + -other + is Short -> this + -other + is Float -> this + -other + is Double -> this + -other + else -> throw UnsupportedOperationException() + } + +internal operator fun Number.times(other: Number): Number = + when (this) { + is Int -> this * other + is Byte -> this * other + is Long -> this * other + is Short -> this * other + is Float -> this * other + is Double -> this * other + else -> throw UnsupportedOperationException() + } + +internal operator fun Int.times(other: Number): Number = + when (other) { + is Int -> this * other + is Byte -> this * other + is Long -> this * other + is Short -> this * other + is Float -> this * other + is Double -> this * other + else -> throw UnsupportedOperationException() + } + +internal operator fun Byte.times(other: Number): Number = + when (other) { + is Int -> this * other + is Byte -> this * other + is Long -> this * other + is Short -> this * other + is Float -> this * other + is Double -> this * other + else -> throw UnsupportedOperationException() + } + +internal operator fun Long.times(other: Number): Number = + when (other) { + is Int -> this * other + is Byte -> this * other + is Long -> this * other + is Short -> this * other + is Float -> this * other + is Double -> this * other + else -> throw UnsupportedOperationException() + } + +internal operator fun Short.times(other: Number): Number = + when (other) { + is Int -> this * other + is Byte -> this * other + is Long -> this * other + is Short -> this * other + is Float -> this * other + is Double -> this * other + else -> throw UnsupportedOperationException() + } + +internal operator fun Float.times(other: Number): Number = + when (other) { + is Int -> this * other + is Byte -> this * other + is Long -> this * other + is Short -> this * other + is Float -> this * other + is Double -> this * other + else -> throw UnsupportedOperationException() + } + +internal operator fun Double.times(other: Number): Number = + when (other) { + is Int -> this * other + is Byte -> this * other + is Long -> this * other + is Short -> this * other + is Float -> this * other + is Double -> this * other + else -> throw UnsupportedOperationException() + } + +internal operator fun Number.div(other: Number): Number = + when (this) { + is Int -> this / other + is Byte -> this / other + is Long -> this / other + is Short -> this / other + is Float -> this / other + is Double -> this / other + else -> throw UnsupportedOperationException() + } + +internal operator fun Int.div(other: Number): Number = + when (other) { + is Int -> this / other + is Byte -> this / other + is Long -> this / other + is Short -> this / other + is Float -> this / other + is Double -> this / other + else -> throw UnsupportedOperationException() + } + +internal operator fun Byte.div(other: Number): Number = + when (other) { + is Int -> this / other + is Byte -> this / other + is Long -> this / other + is Short -> this / other + is Float -> this / other + is Double -> this / other + else -> throw UnsupportedOperationException() + } + +internal operator fun Long.div(other: Number): Number = + when (other) { + is Int -> this / other + is Byte -> this / other + is Long -> this / other + is Short -> this / other + is Float -> this / other + is Double -> this / other + else -> throw UnsupportedOperationException() + } + +internal operator fun Short.div(other: Number): Number = + when (other) { + is Int -> this / other + is Byte -> this / other + is Long -> this / other + is Short -> this / other + is Float -> this / other + is Double -> this / other + else -> throw UnsupportedOperationException() + } + +internal operator fun Float.div(other: Number): Number = + when (other) { + is Int -> this / other + is Byte -> this / other + is Long -> this / other + is Short -> this / other + is Float -> this / other + is Double -> this / other + else -> throw UnsupportedOperationException() + } + +internal operator fun Double.div(other: Number): Number = + when (other) { + is Int -> this / other + is Byte -> this / other + is Long -> this / other + is Short -> this / other + is Float -> this / other + is Double -> this / other + else -> throw UnsupportedOperationException() + } + +internal operator fun Number.unaryMinus(): Number = + when (this) { + is Int -> -this + is Byte -> -this + is Long -> -this + is Short -> -this + is Float -> -this + is Double -> -this + else -> throw UnsupportedOperationException() + } + +internal operator fun Number.inc(): Number = + when (this) { + is Int -> this + 1 + is Byte -> this + 1 + is Long -> this + 1 + is Short -> this + 1 + is Float -> this + 1 + is Double -> this + 1 + else -> throw UnsupportedOperationException() + } + +internal operator fun Number.dec(): Number = + when (this) { + is Int -> this - 1 + is Byte -> this - 1 + is Long -> this - 1 + is Short -> this - 1 + is Float -> this - 1 + is Double -> this - 1 + else -> throw UnsupportedOperationException() + } diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/NumberSet.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/NumberSet.kt index 43606cc8a4..388d01d223 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/NumberSet.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/NumberSet.kt @@ -25,6 +25,9 @@ */ package de.fraunhofer.aisec.cpg.analysis +import de.fraunhofer.aisec.cpg.graph.Node +import org.apache.commons.lang3.builder.ToStringBuilder + abstract class NumberSet { abstract fun min(): Long @@ -88,4 +91,21 @@ class ConcreteNumberSet(var values: MutableSet = mutableSetOf()) : NumberS override fun clear() { values.clear() } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as ConcreteNumberSet + + return values == other.values + } + + override fun hashCode(): Int { + return values.hashCode() + } + + override fun toString(): String { + return ToStringBuilder(this, Node.TO_STRING_STYLE).append(values).toString() + } } diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluator.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluator.kt index 5fa5f49537..477927414d 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluator.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluator.kt @@ -175,40 +175,14 @@ open class ValueEvaluator( private fun handlePlus(lhsValue: Any?, rhsValue: Any?, expr: Expression?): Any? { return when { lhsValue is String -> lhsValue + rhsValue - lhsValue is Int && (rhsValue is Double || rhsValue is Float) -> - lhsValue + (rhsValue as Number).toDouble() - lhsValue is Int && rhsValue is Number -> lhsValue + rhsValue.toLong() - lhsValue is Long && (rhsValue is Double || rhsValue is Float) -> - lhsValue + (rhsValue as Number).toDouble() - lhsValue is Long && rhsValue is Number -> lhsValue + rhsValue.toLong() - lhsValue is Short && (rhsValue is Double || rhsValue is Float) -> - lhsValue + (rhsValue as Number).toDouble() - lhsValue is Short && rhsValue is Number -> lhsValue + rhsValue.toLong() - lhsValue is Byte && (rhsValue is Double || rhsValue is Float) -> - lhsValue + (rhsValue as Number).toDouble() - lhsValue is Byte && rhsValue is Number -> lhsValue + rhsValue.toLong() - lhsValue is Double && rhsValue is Number -> lhsValue + rhsValue.toDouble() - lhsValue is Float && rhsValue is Number -> lhsValue + rhsValue.toDouble() + lhsValue is Number && rhsValue is Number -> lhsValue + rhsValue else -> cannotEvaluate(expr, this) } } private fun handleMinus(lhsValue: Any?, rhsValue: Any?, expr: Expression?): Any? { return when { - lhsValue is Int && (rhsValue is Double || rhsValue is Float) -> - lhsValue - (rhsValue as Number).toDouble() - lhsValue is Int && rhsValue is Number -> lhsValue - rhsValue.toLong() - lhsValue is Long && (rhsValue is Double || rhsValue is Float) -> - lhsValue - (rhsValue as Number).toDouble() - lhsValue is Long && rhsValue is Number -> lhsValue - rhsValue.toLong() - lhsValue is Short && (rhsValue is Double || rhsValue is Float) -> - lhsValue - (rhsValue as Number).toDouble() - lhsValue is Short && rhsValue is Number -> lhsValue - rhsValue.toLong() - lhsValue is Byte && (rhsValue is Double || rhsValue is Float) -> - lhsValue - (rhsValue as Number).toDouble() - lhsValue is Byte && rhsValue is Number -> lhsValue - rhsValue.toLong() - lhsValue is Double && rhsValue is Number -> lhsValue - rhsValue.toDouble() - lhsValue is Float && rhsValue is Number -> lhsValue - rhsValue.toDouble() + lhsValue is Number && rhsValue is Number -> lhsValue - rhsValue else -> cannotEvaluate(expr, this) } } @@ -216,40 +190,14 @@ open class ValueEvaluator( private fun handleDiv(lhsValue: Any?, rhsValue: Any?, expr: Expression?): Any? { return when { rhsValue == 0 -> cannotEvaluate(expr, this) - lhsValue is Int && (rhsValue is Double || rhsValue is Float) -> - lhsValue / (rhsValue as Number).toDouble() - lhsValue is Int && rhsValue is Number -> lhsValue / rhsValue.toLong() - lhsValue is Long && (rhsValue is Double || rhsValue is Float) -> - lhsValue / (rhsValue as Number).toDouble() - lhsValue is Long && rhsValue is Number -> lhsValue / rhsValue.toLong() - lhsValue is Short && (rhsValue is Double || rhsValue is Float) -> - lhsValue / (rhsValue as Number).toDouble() - lhsValue is Short && rhsValue is Number -> lhsValue / rhsValue.toLong() - lhsValue is Byte && (rhsValue is Double || rhsValue is Float) -> - lhsValue / (rhsValue as Number).toDouble() - lhsValue is Byte && rhsValue is Number -> lhsValue / rhsValue.toLong() - lhsValue is Double && rhsValue is Number -> lhsValue / rhsValue.toDouble() - lhsValue is Float && rhsValue is Number -> lhsValue / rhsValue.toDouble() + lhsValue is Number && rhsValue is Number -> lhsValue / rhsValue else -> cannotEvaluate(expr, this) } } private fun handleTimes(lhsValue: Any?, rhsValue: Any?, expr: Expression?): Any? { return when { - lhsValue is Int && (rhsValue is Double || rhsValue is Float) -> - lhsValue * (rhsValue as Number).toDouble() - lhsValue is Int && rhsValue is Number -> lhsValue * rhsValue.toLong() - lhsValue is Long && (rhsValue is Double || rhsValue is Float) -> - lhsValue * (rhsValue as Number).toDouble() - lhsValue is Long && rhsValue is Number -> lhsValue * rhsValue.toLong() - lhsValue is Short && (rhsValue is Double || rhsValue is Float) -> - lhsValue * (rhsValue as Number).toDouble() - lhsValue is Short && rhsValue is Number -> lhsValue * rhsValue.toLong() - lhsValue is Byte && (rhsValue is Double || rhsValue is Float) -> - lhsValue * (rhsValue as Number).toDouble() - lhsValue is Byte && rhsValue is Number -> lhsValue * rhsValue.toLong() - lhsValue is Double && rhsValue is Number -> lhsValue * rhsValue.toDouble() - lhsValue is Float && rhsValue is Number -> lhsValue * rhsValue.toDouble() + lhsValue is Number && rhsValue is Number -> lhsValue * rhsValue else -> cannotEvaluate(expr, this) } } @@ -302,19 +250,19 @@ open class ValueEvaluator( return when (expr.operatorCode) { "-" -> { when (val input = evaluateInternal(expr.input, depth + 1)) { - is Number -> input.negate() + is Number -> -input else -> cannotEvaluate(expr, this) } } "--" -> { - when (val input = evaluateInternal(expr.input, depth + 1)) { - is Number -> input.decrement() + return when (val input = evaluateInternal(expr.input, depth + 1)) { + is Number -> input.dec() else -> cannotEvaluate(expr, this) } } "++" -> { when (val input = evaluateInternal(expr.input, depth + 1)) { - is Number -> input.increment() + is Number -> input.inc() else -> cannotEvaluate(expr, this) } } @@ -449,40 +397,6 @@ open class ValueEvaluator( } } -internal fun Number.negate(): Number { - return when (this) { - is Int -> -this - is Long -> -this - is Short -> -this - is Byte -> -this - is Double -> -this - is Float -> -this - else -> throw UnsupportedOperationException() - } -} - -fun Number.increment(): Number { - return when (this) { - is Double -> this + 1 - is Float -> this + 1 - is Int -> this + 1 - is Long -> this + 1 - is Short -> this + 1 - else -> throw UnsupportedOperationException() - } -} - -fun Number.decrement(): Number { - return when (this) { - is Double -> this - 1 - is Float -> this - 1 - is Int -> this - 1 - is Long -> this - 1 - is Short -> this - 1 - else -> throw UnsupportedOperationException() - } -} - /** * This function is a piece of pure magic. It is one of the missing pieces in the Kotlin language * and compares an arbitrary [Number] with another [Number] using the dedicated compareTo functions diff --git a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluatorTest.kt b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluatorTest.kt index 099ca6255c..fb925ed067 100644 --- a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluatorTest.kt +++ b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluatorTest.kt @@ -26,10 +26,10 @@ package de.fraunhofer.aisec.cpg.analysis import de.fraunhofer.aisec.cpg.TestUtils -import de.fraunhofer.aisec.cpg.graph.bodyOrNull -import de.fraunhofer.aisec.cpg.graph.byNameOrNull +import de.fraunhofer.aisec.cpg.frontends.TestHandler +import de.fraunhofer.aisec.cpg.frontends.TestLanguageFrontend +import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration -import de.fraunhofer.aisec.cpg.graph.evaluate import de.fraunhofer.aisec.cpg.graph.statements.CompoundStatement import de.fraunhofer.aisec.cpg.graph.statements.DeclarationStatement import de.fraunhofer.aisec.cpg.graph.statements.ForStatement @@ -63,7 +63,7 @@ class MultiValueEvaluatorTest { assertNotNull(b) var value = b.evaluate() - assertEquals(2L, value) + assertEquals(2, value) val printB = main.bodyOrNull() assertNotNull(printB) @@ -71,7 +71,7 @@ class MultiValueEvaluatorTest { val evaluator = MultiValueEvaluator() value = evaluator.evaluate(printB.arguments.firstOrNull()) as ConcreteNumberSet assertEquals(value.min(), value.max()) - assertEquals(2L, value.min()) + assertEquals(2, value.min()) val path = evaluator.path assertEquals(5, path.size) @@ -87,13 +87,13 @@ class MultiValueEvaluatorTest { assertNotNull(c) value = evaluator.evaluate(c) - assertEquals(3L, value) + assertEquals(3, value) val d = main.bodyOrNull(3)?.singleDeclaration assertNotNull(d) value = evaluator.evaluate(d) - assertEquals(2L, value) + assertEquals(2, value) val e = main.bodyOrNull(4)?.singleDeclaration assertNotNull(e) @@ -103,13 +103,13 @@ class MultiValueEvaluatorTest { val f = main.bodyOrNull(5)?.singleDeclaration assertNotNull(f) value = evaluator.evaluate(f) - assertEquals(10L, value) + assertEquals(10, value) val g = main.bodyOrNull(6)?.singleDeclaration assertNotNull(g) value = evaluator.evaluate(g) as ConcreteNumberSet assertEquals(value.min(), value.max()) - assertEquals(-3L, value.min()) + assertEquals(-3, value.min()) val i = main.bodyOrNull(8)?.singleDeclaration assertNotNull(i) @@ -219,6 +219,47 @@ class MultiValueEvaluatorTest { assertEquals(setOf(0, 1, 2, 3, 4, 5), value.values) } + @Test + fun testHandleUnary() { + val evaluator = MultiValueEvaluator() + + with(TestHandler(TestLanguageFrontend())) { + // Construct a fake DFG flow + val three = newLiteral(3, primitiveType("int")) + val four = newLiteral(4, primitiveType("int")) + + val ref = newDeclaredReferenceExpression("a") + ref.prevDFG = mutableSetOf(three, four) + + val neg = newUnaryOperator("-", false, true) + neg.input = ref + assertEquals(ConcreteNumberSet(mutableSetOf(-3, -4)), evaluator.evaluate(neg)) + + neg.input = newLiteral(3.5, primitiveType("double")) + assertEquals(-3.5, evaluator.evaluate(neg)) + + val plusplus = newUnaryOperator("++", true, false) + plusplus.input = newLiteral(3, primitiveType("int")) + assertEquals(4, evaluator.evaluate(plusplus)) + + plusplus.input = newLiteral(3.5, primitiveType("double")) + assertEquals(4.5, evaluator.evaluate(plusplus)) + + plusplus.input = newLiteral(3.5f, primitiveType("float")) + assertEquals(4.5f, evaluator.evaluate(plusplus)) + + val minusminus = newUnaryOperator("--", true, false) + minusminus.input = newLiteral(3, primitiveType("int")) + assertEquals(2, evaluator.evaluate(minusminus)) + + minusminus.input = newLiteral(3.5, primitiveType("double")) + assertEquals(2.5, evaluator.evaluate(minusminus)) + + minusminus.input = newLiteral(3.5f, primitiveType("float")) + assertEquals(2.5f, evaluator.evaluate(minusminus)) + } + } + @Test fun testInterval() { val interval = Interval() diff --git a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluatorTest.kt b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluatorTest.kt index 8f8655c787..1a994ca749 100644 --- a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluatorTest.kt +++ b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluatorTest.kt @@ -38,6 +38,33 @@ import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertNotNull +import org.junit.jupiter.api.assertThrows + +class NotReallyANumber : Number() { + override fun toByte(): Byte { + TODO("Not yet implemented") + } + + override fun toDouble(): Double { + TODO("Not yet implemented") + } + + override fun toFloat(): Float { + TODO("Not yet implemented") + } + + override fun toInt(): Int { + TODO("Not yet implemented") + } + + override fun toLong(): Long { + TODO("Not yet implemented") + } + + override fun toShort(): Short { + TODO("Not yet implemented") + } +} class ValueEvaluatorTest { @@ -60,14 +87,14 @@ class ValueEvaluatorTest { assertNotNull(b) var value = b.evaluate() - assertEquals(2L, value) + assertEquals(2, value) val printB = main.bodyOrNull() assertNotNull(printB) val evaluator = ValueEvaluator() value = evaluator.evaluate(printB.arguments.firstOrNull()) - assertEquals(2L, value) + assertEquals(2, value) val path = evaluator.path assertEquals(5, path.size) @@ -82,13 +109,13 @@ class ValueEvaluatorTest { assertNotNull(c) value = c.evaluate() - assertEquals(3L, value) + assertEquals(3, value) val d = main.bodyOrNull(3)?.singleDeclaration assertNotNull(d) value = d.evaluate() - assertEquals(2L, value) + assertEquals(2, value) val e = main.bodyOrNull(4)?.singleDeclaration assertNotNull(e) @@ -98,7 +125,7 @@ class ValueEvaluatorTest { val f = main.bodyOrNull(5)?.singleDeclaration assertNotNull(f) value = f.evaluate() - assertEquals(10L, value) + assertEquals(10, value) val printHelloWorld = main.bodyOrNull(2) assertNotNull(printHelloWorld) @@ -109,7 +136,7 @@ class ValueEvaluatorTest { val g = main.bodyOrNull(6)?.singleDeclaration assertNotNull(g) value = g.evaluate() - assertEquals(-3L, value) + assertEquals(-3, value) val h = main.bodyOrNull(7)?.singleDeclaration assertNotNull(h) @@ -184,54 +211,157 @@ class ValueEvaluatorTest { fun testHandlePlus() { with(TestHandler(TestLanguageFrontend())) { val binOp = newBinaryOperator("+") + // Int.plus binOp.lhs = newLiteral(3, primitiveType("int")) binOp.rhs = newLiteral(2, primitiveType("int")) + assertEquals(5, ValueEvaluator().evaluate(binOp)) + + binOp.rhs = newLiteral(2.toByte(), primitiveType("byte")) + assertEquals(5, ValueEvaluator().evaluate(binOp)) + binOp.rhs = newLiteral(2L, primitiveType("long")) assertEquals(5L, ValueEvaluator().evaluate(binOp)) + binOp.rhs = newLiteral(2.toShort(), primitiveType("short")) + assertEquals(5, ValueEvaluator().evaluate(binOp)) + + binOp.rhs = newLiteral(2.4f, primitiveType("float")) + assertEquals(5.4f, ValueEvaluator().evaluate(binOp)) + binOp.rhs = newLiteral(2.4, primitiveType("double")) assertEquals(5.4, ValueEvaluator().evaluate(binOp)) - binOp.lhs = newLiteral(3L, primitiveType("long")) + assertThrows { + binOp.rhs = newLiteral(NotReallyANumber(), objectType("fake")) + ValueEvaluator().evaluate(binOp) + } + + // Byte.plus + binOp.lhs = newLiteral(3.toByte(), primitiveType("byte")) binOp.rhs = newLiteral(2, primitiveType("int")) + assertEquals(5, ValueEvaluator().evaluate(binOp)) + + binOp.rhs = newLiteral(2.toByte(), primitiveType("byte")) + assertEquals(5, ValueEvaluator().evaluate(binOp)) + binOp.rhs = newLiteral(2L, primitiveType("long")) assertEquals(5L, ValueEvaluator().evaluate(binOp)) + binOp.rhs = newLiteral(2.toShort(), primitiveType("short")) + assertEquals(5, ValueEvaluator().evaluate(binOp)) + + binOp.rhs = newLiteral(2.4f, primitiveType("float")) + assertEquals(5.4f, ValueEvaluator().evaluate(binOp)) + binOp.rhs = newLiteral(2.4, primitiveType("double")) assertEquals(5.4, ValueEvaluator().evaluate(binOp)) - binOp.lhs = newLiteral((3).toShort(), primitiveType("short")) + assertThrows { + binOp.rhs = newLiteral(NotReallyANumber(), objectType("fake")) + ValueEvaluator().evaluate(binOp) + } + + // Long.plus + binOp.lhs = newLiteral(3L, primitiveType("long")) binOp.rhs = newLiteral(2, primitiveType("int")) + assertEquals(5L, ValueEvaluator().evaluate(binOp)) + binOp.rhs = newLiteral(2.toByte(), primitiveType("byte")) assertEquals(5L, ValueEvaluator().evaluate(binOp)) + binOp.rhs = newLiteral(2L, primitiveType("long")) + assertEquals(5L, ValueEvaluator().evaluate(binOp)) + + binOp.rhs = newLiteral(2.toShort(), primitiveType("short")) + assertEquals(5L, ValueEvaluator().evaluate(binOp)) + + binOp.rhs = newLiteral(2.4f, primitiveType("float")) + assertEquals(5.4f, ValueEvaluator().evaluate(binOp)) + binOp.rhs = newLiteral(2.4, primitiveType("double")) assertEquals(5.4, ValueEvaluator().evaluate(binOp)) - binOp.lhs = newLiteral((3).toByte(), primitiveType("byte")) + assertThrows { + binOp.rhs = newLiteral(NotReallyANumber(), objectType("fake")) + ValueEvaluator().evaluate(binOp) + } + + // Short.plus + binOp.lhs = newLiteral(3.toShort(), primitiveType("short")) binOp.rhs = newLiteral(2, primitiveType("int")) + assertEquals(5, ValueEvaluator().evaluate(binOp)) + binOp.rhs = newLiteral(2.toByte(), primitiveType("byte")) + assertEquals(5, ValueEvaluator().evaluate(binOp)) + + binOp.rhs = newLiteral(2L, primitiveType("long")) assertEquals(5L, ValueEvaluator().evaluate(binOp)) + binOp.rhs = newLiteral(2.toShort(), primitiveType("short")) + assertEquals(5, ValueEvaluator().evaluate(binOp)) + + binOp.rhs = newLiteral(2.4f, primitiveType("float")) + assertEquals(5.4f, ValueEvaluator().evaluate(binOp)) + binOp.rhs = newLiteral(2.4, primitiveType("double")) assertEquals(5.4, ValueEvaluator().evaluate(binOp)) - binOp.lhs = newLiteral(3.0, primitiveType("double")) + assertThrows { + binOp.rhs = newLiteral(NotReallyANumber(), objectType("fake")) + ValueEvaluator().evaluate(binOp) + } + + // Float.plus + binOp.lhs = newLiteral(3.0f, primitiveType("float")) binOp.rhs = newLiteral(2, primitiveType("int")) + assertEquals(5.0f, ValueEvaluator().evaluate(binOp)) - assertEquals(5.0, ValueEvaluator().evaluate(binOp)) + binOp.rhs = newLiteral(2.toByte(), primitiveType("byte")) + assertEquals(5.0f, ValueEvaluator().evaluate(binOp)) + + binOp.rhs = newLiteral(2L, primitiveType("long")) + assertEquals(5.0f, ValueEvaluator().evaluate(binOp)) + + binOp.rhs = newLiteral(2.toShort(), primitiveType("short")) + assertEquals(5.0f, ValueEvaluator().evaluate(binOp)) + + binOp.rhs = newLiteral(2.4f, primitiveType("float")) + assertEquals(5.4f, ValueEvaluator().evaluate(binOp)) binOp.rhs = newLiteral(2.4, primitiveType("double")) assertEquals(5.4, ValueEvaluator().evaluate(binOp)) - binOp.lhs = newLiteral(3.0f, primitiveType("float")) + assertThrows { + binOp.rhs = newLiteral(NotReallyANumber(), objectType("fake")) + ValueEvaluator().evaluate(binOp) + } + + // Double.plus + binOp.lhs = newLiteral(3.0, primitiveType("double")) binOp.rhs = newLiteral(2, primitiveType("int")) + assertEquals(5.0, ValueEvaluator().evaluate(binOp)) + + binOp.rhs = newLiteral(2.toByte(), primitiveType("byte")) + assertEquals(5.0, ValueEvaluator().evaluate(binOp)) + binOp.rhs = newLiteral(2L, primitiveType("long")) assertEquals(5.0, ValueEvaluator().evaluate(binOp)) + binOp.rhs = newLiteral(2.toShort(), primitiveType("short")) + assertEquals(5.0, ValueEvaluator().evaluate(binOp)) + + binOp.rhs = newLiteral(2.4f, primitiveType("float")) + assertEquals((3.0 + 2.4f), ValueEvaluator().evaluate(binOp)) + binOp.rhs = newLiteral(2.4, primitiveType("double")) assertEquals(5.4, ValueEvaluator().evaluate(binOp)) + assertThrows { + binOp.rhs = newLiteral(NotReallyANumber(), objectType("fake")) + ValueEvaluator().evaluate(binOp) + } + + // String.plus binOp.lhs = newLiteral("Hello", primitiveType("string")) binOp.rhs = newLiteral(" world", primitiveType("string")) assertEquals("Hello world", ValueEvaluator().evaluate(binOp)) @@ -248,7 +378,7 @@ class ValueEvaluatorTest { binOp.lhs = newLiteral(3, primitiveType("int")) binOp.rhs = newLiteral(2, primitiveType("int")) - assertEquals(1L, ValueEvaluator().evaluate(binOp)) + assertEquals(1, ValueEvaluator().evaluate(binOp)) binOp.rhs = newLiteral(2.4, primitiveType("double")) assertEquals(3 - 2.4, ValueEvaluator().evaluate(binOp)) @@ -264,7 +394,7 @@ class ValueEvaluatorTest { binOp.lhs = newLiteral((3).toShort(), primitiveType("short")) binOp.rhs = newLiteral(2, primitiveType("int")) - assertEquals(1L, ValueEvaluator().evaluate(binOp)) + assertEquals(1, ValueEvaluator().evaluate(binOp)) binOp.rhs = newLiteral(2.4, primitiveType("double")) assertEquals(3 - 2.4, ValueEvaluator().evaluate(binOp)) @@ -272,7 +402,7 @@ class ValueEvaluatorTest { binOp.lhs = newLiteral((3).toByte(), primitiveType("byte")) binOp.rhs = newLiteral(2, primitiveType("int")) - assertEquals(1L, ValueEvaluator().evaluate(binOp)) + assertEquals(1, ValueEvaluator().evaluate(binOp)) binOp.rhs = newLiteral(2.4, primitiveType("double")) assertEquals(3 - 2.4, ValueEvaluator().evaluate(binOp)) @@ -288,7 +418,7 @@ class ValueEvaluatorTest { binOp.lhs = newLiteral(3.0f, primitiveType("float")) binOp.rhs = newLiteral(2, primitiveType("int")) - assertEquals(1.0, ValueEvaluator().evaluate(binOp)) + assertEquals(1.0f, ValueEvaluator().evaluate(binOp)) binOp.rhs = newLiteral(2.4, primitiveType("double")) assertEquals(3 - 2.4, ValueEvaluator().evaluate(binOp)) @@ -302,55 +432,158 @@ class ValueEvaluatorTest { @Test fun testHandleTimes() { with(TestHandler(TestLanguageFrontend())) { + // Int.times val binOp = newBinaryOperator("*") binOp.lhs = newLiteral(3, primitiveType("int")) binOp.rhs = newLiteral(2, primitiveType("int")) + assertEquals(6, ValueEvaluator().evaluate(binOp)) + binOp.rhs = newLiteral(2.toByte(), primitiveType("byte")) + assertEquals(6, ValueEvaluator().evaluate(binOp)) + + binOp.rhs = newLiteral(2L, primitiveType("long")) assertEquals(6L, ValueEvaluator().evaluate(binOp)) + binOp.rhs = newLiteral(2.toShort(), primitiveType("short")) + assertEquals(6, ValueEvaluator().evaluate(binOp)) + + binOp.rhs = newLiteral(2.4f, primitiveType("float")) + assertEquals(3 * 2.4f, ValueEvaluator().evaluate(binOp)) + binOp.rhs = newLiteral(2.4, primitiveType("double")) assertEquals(3 * 2.4, ValueEvaluator().evaluate(binOp)) - binOp.lhs = newLiteral(3L, primitiveType("long")) + assertThrows { + binOp.rhs = newLiteral(NotReallyANumber(), objectType("fake")) + ValueEvaluator().evaluate(binOp) + } + + // Byte.times + binOp.lhs = newLiteral(3.toByte(), primitiveType("byte")) binOp.rhs = newLiteral(2, primitiveType("int")) + assertEquals(6, ValueEvaluator().evaluate(binOp)) + binOp.rhs = newLiteral(2.toByte(), primitiveType("byte")) + assertEquals(6, ValueEvaluator().evaluate(binOp)) + + binOp.rhs = newLiteral(2L, primitiveType("long")) assertEquals(6L, ValueEvaluator().evaluate(binOp)) + binOp.rhs = newLiteral(2.toShort(), primitiveType("short")) + assertEquals(6, ValueEvaluator().evaluate(binOp)) + + binOp.rhs = newLiteral(2.4f, primitiveType("float")) + assertEquals(3 * 2.4f, ValueEvaluator().evaluate(binOp)) + binOp.rhs = newLiteral(2.4, primitiveType("double")) assertEquals(3 * 2.4, ValueEvaluator().evaluate(binOp)) - binOp.lhs = newLiteral((3).toShort(), primitiveType("short")) + assertThrows { + binOp.rhs = newLiteral(NotReallyANumber(), objectType("fake")) + ValueEvaluator().evaluate(binOp) + } + + // Short.times + binOp.lhs = newLiteral(3.toShort(), primitiveType("short")) binOp.rhs = newLiteral(2, primitiveType("int")) + assertEquals(6, ValueEvaluator().evaluate(binOp)) + + binOp.rhs = newLiteral(2.toShort(), primitiveType("byte")) + assertEquals(6, ValueEvaluator().evaluate(binOp)) + binOp.rhs = newLiteral(2L, primitiveType("long")) assertEquals(6L, ValueEvaluator().evaluate(binOp)) + binOp.rhs = newLiteral(2.toShort(), primitiveType("short")) + assertEquals(6, ValueEvaluator().evaluate(binOp)) + + binOp.rhs = newLiteral(2.4f, primitiveType("float")) + assertEquals(3 * 2.4f, ValueEvaluator().evaluate(binOp)) + binOp.rhs = newLiteral(2.4, primitiveType("double")) assertEquals(3 * 2.4, ValueEvaluator().evaluate(binOp)) - binOp.lhs = newLiteral((3).toByte(), primitiveType("byte")) + assertThrows { + binOp.rhs = newLiteral(NotReallyANumber(), objectType("fake")) + ValueEvaluator().evaluate(binOp) + } + + // Long.times + binOp.lhs = newLiteral(3L, primitiveType("long")) binOp.rhs = newLiteral(2, primitiveType("int")) + assertEquals(6L, ValueEvaluator().evaluate(binOp)) + + binOp.rhs = newLiteral(2L, primitiveType("byte")) + assertEquals(6L, ValueEvaluator().evaluate(binOp)) + + binOp.rhs = newLiteral(2L, primitiveType("long")) + assertEquals(6L, ValueEvaluator().evaluate(binOp)) + binOp.rhs = newLiteral(2L, primitiveType("short")) assertEquals(6L, ValueEvaluator().evaluate(binOp)) + binOp.rhs = newLiteral(2.4f, primitiveType("float")) + assertEquals(3L * 2.4f, ValueEvaluator().evaluate(binOp)) + binOp.rhs = newLiteral(2.4, primitiveType("double")) - assertEquals(3 * 2.4, ValueEvaluator().evaluate(binOp)) + assertEquals(3L * 2.4, ValueEvaluator().evaluate(binOp)) - binOp.lhs = newLiteral(3.0, primitiveType("double")) + assertThrows { + binOp.rhs = newLiteral(NotReallyANumber(), objectType("fake")) + ValueEvaluator().evaluate(binOp) + } + + // Float.times + binOp.lhs = newLiteral(3.0f, primitiveType("float")) binOp.rhs = newLiteral(2, primitiveType("int")) + assertEquals(6.0f, ValueEvaluator().evaluate(binOp)) - assertEquals(6.0, ValueEvaluator().evaluate(binOp)) + binOp.rhs = newLiteral(2L, primitiveType("byte")) + assertEquals(6.0f, ValueEvaluator().evaluate(binOp)) + + binOp.rhs = newLiteral(2L, primitiveType("long")) + assertEquals(6.0f, ValueEvaluator().evaluate(binOp)) + + binOp.rhs = newLiteral(2L, primitiveType("short")) + assertEquals(6.0f, ValueEvaluator().evaluate(binOp)) + + binOp.rhs = newLiteral(2.4f, primitiveType("float")) + assertEquals(3.0f * 2.4f, ValueEvaluator().evaluate(binOp)) binOp.rhs = newLiteral(2.4, primitiveType("double")) - assertEquals(3 * 2.4, ValueEvaluator().evaluate(binOp)) + assertEquals(3.0f * 2.4, ValueEvaluator().evaluate(binOp)) - binOp.lhs = newLiteral(3.0f, primitiveType("float")) + assertThrows { + binOp.rhs = newLiteral(NotReallyANumber(), objectType("fake")) + ValueEvaluator().evaluate(binOp) + } + + // Double.times + binOp.lhs = newLiteral(3.0, primitiveType("double")) binOp.rhs = newLiteral(2, primitiveType("int")) + assertEquals(6.0, ValueEvaluator().evaluate(binOp)) + + binOp.rhs = newLiteral(2L, primitiveType("byte")) + assertEquals(6.0, ValueEvaluator().evaluate(binOp)) + binOp.rhs = newLiteral(2L, primitiveType("long")) assertEquals(6.0, ValueEvaluator().evaluate(binOp)) + binOp.rhs = newLiteral(2L, primitiveType("short")) + assertEquals(6.0, ValueEvaluator().evaluate(binOp)) + + binOp.rhs = newLiteral(2.4f, primitiveType("float")) + assertEquals(3.0 * 2.4f, ValueEvaluator().evaluate(binOp)) + binOp.rhs = newLiteral(2.4, primitiveType("double")) - assertEquals(3 * 2.4, ValueEvaluator().evaluate(binOp)) + assertEquals(3.0 * 2.4, ValueEvaluator().evaluate(binOp)) + + assertThrows { + binOp.rhs = newLiteral(NotReallyANumber(), objectType("fake")) + ValueEvaluator().evaluate(binOp) + } + // String.times binOp.lhs = newLiteral("Hello", primitiveType("string")) binOp.rhs = newLiteral(" world", primitiveType("string")) assertEquals("{*}", ValueEvaluator().evaluate(binOp)) @@ -369,7 +602,7 @@ class ValueEvaluatorTest { binOp.lhs = newLiteral(3, primitiveType("int")) binOp.rhs = newLiteral(2, primitiveType("int")) - assertEquals(1L, ValueEvaluator().evaluate(binOp)) + assertEquals(1, ValueEvaluator().evaluate(binOp)) binOp.rhs = newLiteral(2.4, primitiveType("double")) assertEquals(3 / 2.4, ValueEvaluator().evaluate(binOp)) @@ -385,7 +618,7 @@ class ValueEvaluatorTest { binOp.lhs = newLiteral((3).toShort(), primitiveType("short")) binOp.rhs = newLiteral(2, primitiveType("int")) - assertEquals(1L, ValueEvaluator().evaluate(binOp)) + assertEquals(1, ValueEvaluator().evaluate(binOp)) binOp.rhs = newLiteral(2.4, primitiveType("double")) assertEquals(3 / 2.4, ValueEvaluator().evaluate(binOp)) @@ -393,7 +626,7 @@ class ValueEvaluatorTest { binOp.lhs = newLiteral((3).toByte(), primitiveType("byte")) binOp.rhs = newLiteral(2, primitiveType("int")) - assertEquals(1L, ValueEvaluator().evaluate(binOp)) + assertEquals(1, ValueEvaluator().evaluate(binOp)) binOp.rhs = newLiteral(2.4, primitiveType("double")) assertEquals(3 / 2.4, ValueEvaluator().evaluate(binOp)) @@ -409,7 +642,7 @@ class ValueEvaluatorTest { binOp.lhs = newLiteral(3.0f, primitiveType("float")) binOp.rhs = newLiteral(2, primitiveType("int")) - assertEquals(1.5, ValueEvaluator().evaluate(binOp)) + assertEquals(1.5f, ValueEvaluator().evaluate(binOp)) binOp.rhs = newLiteral(2.4, primitiveType("double")) assertEquals(3 / 2.4, ValueEvaluator().evaluate(binOp)) From b06ebc2f5193718cfcd27fb33fbbd97efa8d4cf6 Mon Sep 17 00:00:00 2001 From: KuechA <31155350+KuechA@users.noreply.github.com> Date: Mon, 28 Aug 2023 13:42:39 +0200 Subject: [PATCH 132/143] Add more documentation of value evaluation (#1302) --- docs/docs/GettingStarted/query.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/docs/GettingStarted/query.md b/docs/docs/GettingStarted/query.md index 88c735129f..6fb8a49444 100755 --- a/docs/docs/GettingStarted/query.md +++ b/docs/docs/GettingStarted/query.md @@ -112,6 +112,11 @@ set of analyses and functions to use them. These are: - **min(n: Node)**: Minimal value of a node - **max(n: Node)**: Maximal value of a node +- **evaluate(evaluator: ValueEvaluator)**: Evaluates the value of a node. You + can use different evaluators which can affect the possible results. In general, + it makes sense to check if the evaluation succeeded and/or transfer the types. + E.g., the default value evaluator could return different numbers (transferring + them e.g. with `toLong()` or `toFloat()` could make sense), a string, or an error. - **sizeof(n: Node)**: The length of an array or string - **dataFlow(from: Node, to: Node)**: Checks if a data flow is possible between the nodes `from` as a source and `to` as sink. From 463ac6094bcb3217972db1f4bee576daf0905f7b Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Tue, 29 Aug 2023 12:24:35 +0200 Subject: [PATCH 133/143] Only store one single unique `Type` nodes for each type (#1304) Instead of gathering individual type node objects and then squashing them back together, we just make sure that `registerType` only returns the single unique type object. To reduce the number of `Type` nodes needed as input to `registerType`, the `objectType` function does a pre-check now, if a `ObjectType` with the given name already exists, so no new `Type` object is even created at all. This also removes the "secondary type edge" interface, which was only needed to merge extra usages of types in nodes. Now all type nodes are always referring to the single instance. --- .../de/fraunhofer/aisec/cpg/TypeManager.kt | 97 ++++---- .../de/fraunhofer/aisec/cpg/graph/Name.kt | 5 + .../fraunhofer/aisec/cpg/graph/TypeBuilder.kt | 31 ++- .../aisec/cpg/graph/builder/Fluent.kt | 9 +- .../graph/declarations/RecordDeclaration.kt | 19 +- .../declarations/TypeParamDeclaration.kt | 21 +- .../statements/expressions/CallExpression.kt | 34 +-- .../aisec/cpg/graph/types/AutoType.kt | 4 - .../aisec/cpg/graph/types/BooleanType.kt | 7 +- .../cpg/graph/types/FloatingPointType.kt | 7 +- .../cpg/graph/types/FunctionPointerType.kt | 6 - .../aisec/cpg/graph/types/FunctionType.kt | 8 +- .../aisec/cpg/graph/types/HasType.kt | 6 - .../aisec/cpg/graph/types/IncompleteType.kt | 4 - .../aisec/cpg/graph/types/IntegerType.kt | 7 +- .../aisec/cpg/graph/types/NumericType.kt | 5 - .../aisec/cpg/graph/types/ObjectType.kt | 40 +--- .../cpg/graph/types/ParameterizedType.kt | 8 +- .../aisec/cpg/graph/types/PointerType.kt | 4 - .../aisec/cpg/graph/types/ProblemType.kt | 4 - .../aisec/cpg/graph/types/ReferenceType.kt | 8 - .../aisec/cpg/graph/types/StringType.kt | 7 +- .../aisec/cpg/graph/types/TupleType.kt | 4 - .../fraunhofer/aisec/cpg/graph/types/Type.kt | 3 - .../aisec/cpg/graph/types/UnknownType.kt | 5 - .../cpg/passes/TemplateCallResolverHelper.kt | 76 ++++--- .../aisec/cpg/passes/TypeResolver.kt | 207 ++---------------- .../aisec/cpg/passes/UnresolvedDFGPassTest.kt | 8 +- .../cpg/frontends/cxx/DeclarationHandler.kt | 36 +-- .../templates/ClassTemplateTest.kt | 5 +- 30 files changed, 178 insertions(+), 507 deletions(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TypeManager.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TypeManager.kt index 14f846c4f2..72bc8aa510 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TypeManager.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TypeManager.kt @@ -36,8 +36,15 @@ import de.fraunhofer.aisec.cpg.graph.scopes.Scope import de.fraunhofer.aisec.cpg.graph.scopes.TemplateScope import de.fraunhofer.aisec.cpg.graph.types.* import java.util.* +import java.util.concurrent.ConcurrentHashMap +import org.slf4j.Logger +import org.slf4j.LoggerFactory class TypeManager { + companion object { + val log: Logger = LoggerFactory.getLogger(TypeManager::class.java) + } + /** * Stores the relationship between parameterized RecordDeclarations (e.g. Classes using * Generics) to the ParameterizedType to be able to resolve the Type of the fields, since @@ -50,8 +57,8 @@ class TypeManager { mutableMapOf>() ) - val firstOrderTypes: MutableSet = Collections.synchronizedSet(HashSet()) - val secondOrderTypes: MutableSet = Collections.synchronizedSet(HashSet()) + val firstOrderTypes: MutableSet = ConcurrentHashMap.newKeySet() + val secondOrderTypes: MutableSet = ConcurrentHashMap.newKeySet() /** * @param recordDeclaration that is instantiated by a template containing parameterizedtypes @@ -187,13 +194,35 @@ class TypeManager { return parameterizedType } - fun registerType(t: T): T { + inline fun registerType(t: T): T { + // Skip as they should be unique to each class and not globally unique + if (t is ParameterizedType) { + return t + } + if (t.isFirstOrderType) { - firstOrderTypes.add(t) - } else { - secondOrderTypes.add(t) - registerType((t as SecondOrderType).elementType) + // Make sure we only ever return one unique object per type + if (!firstOrderTypes.add(t)) { + return firstOrderTypes.first { it == t && it is T } as T + } else { + log.trace( + "Registering unique first order type {}{}", + t.name, + if (t is ObjectType && t.generics.isNotEmpty()) { + " with generics [${t.generics.joinToString(",") { it.name.toString() }}]" + } else { + "" + } + ) + } + } else if (t is SecondOrderType) { + if (!secondOrderTypes.add(t)) { + return secondOrderTypes.first { it == t && it is T } as T + } else { + log.trace("Registering unique second order type {}", t.name) + } } + return t } @@ -217,17 +246,7 @@ class TypeManager { target: Type, alias: Type, ): Declaration { - var currTarget = target - var currAlias = alias - if (alias is SecondOrderType) { - // TODO: I have NO clue what the following lines do and why they are necessary - val chain = alias.duplicate() - chain.root = currTarget - currTarget = chain - currTarget.refreshNames() - currAlias = alias.root - } - val typedef = frontend.newTypedefDeclaration(currTarget, currAlias, rawCode) + val typedef = frontend.newTypedefDeclaration(target, alias, rawCode) frontend.scopeManager.addTypedef(typedef) return typedef } @@ -238,11 +257,7 @@ class TypeManager { scopeManager.currentTypedefs .firstOrNull { t: TypedefDeclaration -> t.alias.root == finalToCheck } ?.type - return if (applicable == null) { - alias - } else { - alias.changeRoot(applicable) - } + return applicable ?: alias } } @@ -405,39 +420,3 @@ fun Type.wrap(wrapState: WrapState): Type { return type } - -/** - * Reconstructs the type chain when the root node is modified e.g. when swapping with alias - * (typedef) - * - * @param newRoot root the chain is swapped with - * @return the type but root replaced with newRoot - */ -private fun Type.changeRoot(newRoot: Type): Type { - if (this.isFirstOrderType) { - newRoot.typeOrigin = this.typeOrigin - } - if (!newRoot.isFirstOrderType) { - return newRoot - } - return when { - this is ObjectType && newRoot is ObjectType -> { - newRoot.generics = this.generics - newRoot - } - this is ReferenceType -> { - val reference = this.elementType.changeRoot(newRoot) - val newChain = this.duplicate() as ReferenceType - newChain.elementType = reference - newChain.refreshName() - newChain - } - this is PointerType -> { - val newChain = this.duplicate() as PointerType - newChain.root = this.root.changeRoot(newRoot) - newChain.refreshNames() - newChain - } - else -> newRoot - } -} diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Name.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Name.kt index 4cb0a1f75c..1cf9d66218 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Name.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Name.kt @@ -131,6 +131,11 @@ fun LanguageProvider?.parseName(fqn: CharSequence) = /** Tries to parse the given fully qualified name using the specified [delimiter] into a [Name]. */ internal fun parseName(fqn: CharSequence, delimiter: String, vararg splitDelimiters: String): Name { + // We can take a shortcut, if this is already a name + if (fqn is Name) { + return fqn + } + val parts = fqn.split(delimiter, *splitDelimiters) var name: Name? = null diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/TypeBuilder.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/TypeBuilder.kt index 2e3a70e36a..05a9c75f62 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/TypeBuilder.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/TypeBuilder.kt @@ -25,7 +25,6 @@ */ package de.fraunhofer.aisec.cpg.graph -import de.fraunhofer.aisec.cpg.TypeManager import de.fraunhofer.aisec.cpg.frontends.Language import de.fraunhofer.aisec.cpg.frontends.TranslationException import de.fraunhofer.aisec.cpg.graph.types.* @@ -92,10 +91,9 @@ fun Type.ref(): Type { } /** - * This function creates a new [Type] with the given [name]. In order to avoid unnecessary - * allocation of simple types, we do a pre-check within this function, whether a built-in type exist - * with the particular name. If it not exists, a new [ObjectType] is created and registered with the - * [TypeManager]. + * This function returns an [ObjectType] with the given [name]. If a respective [Type] does not yet + * exist, it will be created In order to avoid unnecessary allocation of simple types, we do a + * pre-check within this function, whether a built-in type exist with the particular name. */ @JvmOverloads fun LanguageProvider.objectType(name: CharSequence, generics: List = listOf()): Type { @@ -112,9 +110,28 @@ fun LanguageProvider.objectType(name: CharSequence, generics: List = listO ?: throw TranslationException( "Could not create type: translation context not available" ) - val type = ObjectType(name, generics, false, language) - return c.typeManager.registerType(type) + synchronized(c.typeManager.firstOrderTypes) { + // We can try to look up the type by its name and return it, if it already exists. + var type = + c.typeManager.firstOrderTypes.firstOrNull { + it is ObjectType && + it.name == name && + it.generics == generics && + it.language == language + } + if (type != null) { + return type + } + + // Otherwise, we either need to create the type because of the generics or because we do not + // know the type yet. + type = ObjectType(name, generics, false, language) + + // Piping it through register type will ensure that in any case we return the one unique + // type object for it. + return c.typeManager.registerType(type) + } } /** diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt index 05ce234380..7efefcf1f2 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt @@ -1175,13 +1175,8 @@ infix fun Expression.assignAsExpr(rhs: AssignExpression.() -> Unit): AssignExpre } /** Creates a new [Type] with the given [name] in the Fluent Node DSL. */ -fun LanguageFrontend<*, *>.t(name: CharSequence, init: (Type.() -> Unit)? = null): Type { - val type = objectType(name) - if (init != null) { - init(type) - } - return type -} +fun LanguageFrontend<*, *>.t(name: CharSequence, generics: List = listOf()) = + objectType(name, generics) /** * Internally used to enter a new scope if [needsScope] is true before invoking [init] and leaving diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/RecordDeclaration.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/RecordDeclaration.kt index a759f55c03..3e1afe69ca 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/RecordDeclaration.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/RecordDeclaration.kt @@ -30,7 +30,6 @@ import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.propertyEqualsList import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdgeDelegate import de.fraunhofer.aisec.cpg.graph.statements.Statement -import de.fraunhofer.aisec.cpg.graph.types.HasSecondaryTypeEdge import de.fraunhofer.aisec.cpg.graph.types.ObjectType import de.fraunhofer.aisec.cpg.graph.types.Type import org.apache.commons.lang3.builder.ToStringBuilder @@ -38,7 +37,7 @@ import org.neo4j.ogm.annotation.Relationship import org.neo4j.ogm.annotation.Transient /** Represents a C++ union/struct/class or Java class */ -class RecordDeclaration : Declaration(), DeclarationHolder, StatementHolder, HasSecondaryTypeEdge { +class RecordDeclaration : Declaration(), DeclarationHolder, StatementHolder { /** The kind, i.e. struct, class, union or enum. */ var kind: String? = null @@ -173,22 +172,6 @@ class RecordDeclaration : Declaration(), DeclarationHolder, StatementHolder, Has .toString() } - override fun updateType(typeState: Collection) { - // Replace occurrences of the super classes and interfaces with the one combined type - replaceType(superClasses, typeState) - replaceType(implementedInterfaces, typeState) - } - - private fun replaceType(list: MutableList, typeState: Collection) { - for ((idx, t) in list.withIndex()) { - for (newType in typeState) { - if (newType == t) { - list[idx] = newType - } - } - } - } - override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is RecordDeclaration) return false diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/TypeParamDeclaration.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/TypeParamDeclaration.kt index e3e1940342..cde9143f37 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/TypeParamDeclaration.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/TypeParamDeclaration.kt @@ -27,19 +27,13 @@ package de.fraunhofer.aisec.cpg.graph.declarations import de.fraunhofer.aisec.cpg.graph.AST import de.fraunhofer.aisec.cpg.graph.HasDefault -import de.fraunhofer.aisec.cpg.graph.types.HasSecondaryTypeEdge import de.fraunhofer.aisec.cpg.graph.types.Type import java.util.* import org.neo4j.ogm.annotation.Relationship /** A declaration of a type template parameter */ -class TypeParamDeclaration : ValueDeclaration(), HasSecondaryTypeEdge, HasDefault { - /** - * TemplateParameters can define a default for the type parameter Since the primary type edge - * points to the ParameterizedType, the default edge is a secondary type edge. Therefore, the - * TypeResolver requires to implement the [HasSecondaryTypeEdge] to be aware of the edge to be - * able to merge the type nodes. - */ +class TypeParamDeclaration : ValueDeclaration(), HasDefault { + /** TemplateParameters can define a default for the type parameter. */ @Relationship(value = "DEFAULT", direction = Relationship.Direction.OUTGOING) @AST override var default: Type? = null @@ -52,15 +46,4 @@ class TypeParamDeclaration : ValueDeclaration(), HasSecondaryTypeEdge, HasDefaul } override fun hashCode() = Objects.hash(super.hashCode(), default) - - override fun updateType(typeState: Collection) { - val oldType = default - if (oldType != null) { - for (t in typeState) { - if (t == oldType) { - default = t - } - } - } - } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/CallExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/CallExpression.kt index 6a39018d72..7b38f9cc39 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/CallExpression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/CallExpression.kt @@ -37,7 +37,6 @@ import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.propertyEqualsL import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.unwrap import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.wrap import de.fraunhofer.aisec.cpg.graph.types.* -import de.fraunhofer.aisec.cpg.graph.types.HasSecondaryTypeEdge import de.fraunhofer.aisec.cpg.passes.CallResolver import de.fraunhofer.aisec.cpg.passes.VariableUsageResolver import java.util.* @@ -48,8 +47,7 @@ import org.neo4j.ogm.annotation.Relationship * An expression, which calls another function. It has a list of arguments (list of [Expression]s) * and is connected via the INVOKES edge to its [FunctionDeclaration]. */ -open class CallExpression : - Expression(), HasType.TypeObserver, HasSecondaryTypeEdge, ArgumentHolder { +open class CallExpression : Expression(), HasType.TypeObserver, ArgumentHolder { /** * Connection to its [FunctionDeclaration]. This will be populated by the [CallResolver]. This * will have an effect on the [type] @@ -189,26 +187,6 @@ open class CallExpression : template = value != null } - private val typeTemplateParameters: List - get() { - val types: MutableList = ArrayList() - for (n in templateParameters) { - if (n is Type) { - types.add(n) - } - } - return types - } - - private fun replaceTypeTemplateParameter(oldType: Type?, newType: Type) { - for (i in templateParameterEdges?.indices ?: listOf()) { - val propertyEdge = templateParameterEdges?.get(i) - if (propertyEdge?.end == oldType) { - propertyEdge?.end = newType - } - } - } - /** * Adds a template parameter to this call expression. A parameter can either be an [Expression] * (usually a [Literal]) or a [Type]. @@ -317,14 +295,4 @@ open class CallExpression : // TODO: Not sure if we can add the template, templateParameters, templateInstantiation fields // here override fun hashCode() = Objects.hash(super.hashCode(), arguments) - - override fun updateType(typeState: Collection) { - for (t in typeTemplateParameters) { - for (t2 in typeState) { - if (t2 == t) { - replaceTypeTemplateParameter(t, t2) - } - } - } - } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/AutoType.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/AutoType.kt index 5d98db1138..681790d96d 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/AutoType.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/AutoType.kt @@ -43,8 +43,4 @@ class AutoType(override var language: Language<*>?) : Type("auto", language) { override fun dereference(): Type { return unknownType() } - - override fun duplicate(): Type { - return AutoType(this.language) - } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/BooleanType.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/BooleanType.kt index e3b951dcd0..2acab30cfc 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/BooleanType.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/BooleanType.kt @@ -33,9 +33,4 @@ class BooleanType( bitWidth: Int? = 1, language: Language<*>? = null, modifier: Modifier = Modifier.NOT_APPLICABLE -) : NumericType(typeName, bitWidth, language, modifier) { - - override fun duplicate(): Type { - return BooleanType(this.name, bitWidth, language, modifier) - } -} +) : NumericType(typeName, bitWidth, language, modifier) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/FloatingPointType.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/FloatingPointType.kt index 9a33cfafda..542cf881d5 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/FloatingPointType.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/FloatingPointType.kt @@ -33,9 +33,4 @@ class FloatingPointType( bitWidth: Int? = null, language: Language<*>? = null, modifier: Modifier = Modifier.SIGNED -) : NumericType(typeName, bitWidth, language, modifier) { - - override fun duplicate(): Type { - return FloatingPointType(name, bitWidth, language, modifier) - } -} +) : NumericType(typeName, bitWidth, language, modifier) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/FunctionPointerType.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/FunctionPointerType.kt index 0b75a37c7d..92c1aa4304 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/FunctionPointerType.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/FunctionPointerType.kt @@ -28,7 +28,6 @@ package de.fraunhofer.aisec.cpg.graph.types import de.fraunhofer.aisec.cpg.frontends.Language import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.propertyEqualsList -import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.unwrap import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.wrap import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdgeDelegate import de.fraunhofer.aisec.cpg.graph.types.PointerType.PointerOrigin @@ -82,11 +81,6 @@ class FunctionPointerType : Type { return this } - override fun duplicate(): Type { - val copiedParameters: List = ArrayList(unwrap(parametersPropertyEdge)) - return FunctionPointerType(this, copiedParameters, language, returnType) - } - override fun isSimilar(t: Type?): Boolean { return if (t is FunctionPointerType) { parametersPropertyEdge == t.parametersPropertyEdge && returnType == t.returnType diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/FunctionType.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/FunctionType.kt index d228edc6c6..55550fc462 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/FunctionType.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/FunctionType.kt @@ -26,6 +26,7 @@ package de.fraunhofer.aisec.cpg.graph.types import de.fraunhofer.aisec.cpg.frontends.Language +import de.fraunhofer.aisec.cpg.frontends.TranslationException import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration import de.fraunhofer.aisec.cpg.graph.unknownType @@ -58,10 +59,6 @@ constructor( return unknownType() } - override fun duplicate(): Type { - return FunctionType(typeName, parameters.toList(), returnTypes.toList(), language) - } - companion object { /** * This helper function computes a [FunctionType] out of an existing [FunctionDeclaration]. @@ -78,7 +75,8 @@ constructor( func.language ) - return func.registerType(type) + val c = func.ctx ?: throw TranslationException("context not available") + return c.typeManager.registerType(type) } } } 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 10ec301f86..d5e2b6c78b 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 @@ -25,7 +25,6 @@ */ package de.fraunhofer.aisec.cpg.graph.types -import de.fraunhofer.aisec.cpg.frontends.TranslationException import de.fraunhofer.aisec.cpg.graph.ContextProvider import de.fraunhofer.aisec.cpg.graph.LanguageProvider import de.fraunhofer.aisec.cpg.graph.Node @@ -184,8 +183,3 @@ interface HasType : ContextProvider, LanguageProvider { typeObservers -= typeObserver } } - -fun Node.registerType(type: T): T { - val c = ctx ?: throw TranslationException("context not available") - return c.typeManager.registerType(type) -} diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/IncompleteType.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/IncompleteType.kt index 59b440e376..b16dd5ee02 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/IncompleteType.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/IncompleteType.kt @@ -51,10 +51,6 @@ class IncompleteType : Type { return this } - override fun duplicate(): Type { - return IncompleteType(this) - } - override fun equals(other: Any?): Boolean { return other is IncompleteType } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/IntegerType.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/IntegerType.kt index c788bc970f..f63d600f4a 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/IntegerType.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/IntegerType.kt @@ -33,9 +33,4 @@ class IntegerType( bitWidth: Int? = null, language: Language<*>? = null, modifier: Modifier = Modifier.SIGNED -) : NumericType(typeName, bitWidth, language, modifier) { - - override fun duplicate(): Type { - return IntegerType(this.name, bitWidth, language, modifier) - } -} +) : NumericType(typeName, bitWidth, language, modifier) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/NumericType.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/NumericType.kt index 498fd2a252..0b0d4a046e 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/NumericType.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/NumericType.kt @@ -35,11 +35,6 @@ open class NumericType( language: Language<*>? = null, val modifier: Modifier = Modifier.SIGNED ) : ObjectType(typeName, listOf(), true, language) { - - override fun duplicate(): Type { - return NumericType(this.name, bitWidth, language, modifier) - } - /** * NumericTypes can have a modifier. The default is signed. Some types (e.g. char in C) may be * neither of the signed/unsigned option. diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/ObjectType.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/ObjectType.kt index 1d4d83cc5d..85571b78b1 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/ObjectType.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/ObjectType.kt @@ -28,7 +28,6 @@ package de.fraunhofer.aisec.cpg.graph.types import de.fraunhofer.aisec.cpg.PopulatedByPass import de.fraunhofer.aisec.cpg.frontends.Language import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration -import de.fraunhofer.aisec.cpg.graph.edge.Properties import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.propertyEqualsList import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.wrap @@ -43,7 +42,7 @@ import org.neo4j.ogm.annotation.Relationship * This is the main type in the Type system. ObjectTypes describe objects, as instances of a class. * This also includes primitive data types. */ -open class ObjectType : Type, HasSecondaryTypeEdge { +open class ObjectType : Type { /** * Reference from the [ObjectType] to its class ([RecordDeclaration]), only if the class is * available. This is set by the [TypeResolver]. @@ -52,8 +51,10 @@ open class ObjectType : Type, HasSecondaryTypeEdge { @Relationship(value = "GENERICS", direction = Relationship.Direction.OUTGOING) var genericsPropertyEdges: MutableList> = mutableListOf() + private set var generics by PropertyEdgeDelegate(ObjectType::genericsPropertyEdges) + private set constructor( typeName: CharSequence, @@ -83,25 +84,6 @@ open class ObjectType : Type, HasSecondaryTypeEdge { isPrimitive = false } - override fun updateType(typeState: Collection) { - for (t in generics) { - for (t2 in typeState) { - if (t2 == t) { - replaceGenerics(t, t2) - } - } - } - } - - fun replaceGenerics(oldType: Type?, newType: Type) { - for (i in genericsPropertyEdges.indices) { - val propertyEdge = genericsPropertyEdges[i] - if (propertyEdge.end == oldType) { - propertyEdge.end = newType - } - } - } - /** @return PointerType to a ObjectType, e.g. int* */ override fun reference(pointer: PointerOrigin?): PointerType { return PointerType(this, pointer) @@ -119,22 +101,6 @@ open class ObjectType : Type, HasSecondaryTypeEdge { return unknownType() } - override fun duplicate(): Type { - return ObjectType(this, generics, isPrimitive, language) - } - - fun addGeneric(generic: Type) { - val propertyEdge = PropertyEdge(this, generic) - propertyEdge.addProperty(Properties.INDEX, genericsPropertyEdges.size) - genericsPropertyEdges.add(propertyEdge) - } - - fun addGenerics(generics: List) { - for (generic in generics) { - addGeneric(generic) - } - } - override fun isSimilar(t: Type?): Boolean { return t is ObjectType && generics == t.generics && super.isSimilar(t) } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/ParameterizedType.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/ParameterizedType.kt index 2ced2d0d51..12a8ebc2ce 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/ParameterizedType.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/ParameterizedType.kt @@ -29,8 +29,8 @@ import de.fraunhofer.aisec.cpg.frontends.Language import de.fraunhofer.aisec.cpg.graph.types.PointerType.PointerOrigin /** - * ParameterizedTypes describe types, that are passed as Paramters to Classes E.g. uninitialized - * generics in the graph are represented as ParameterizedTypes + * ParameterizedTypes describe types, that are passed as parameters to classes, e.g. uninitialized + * generics in the graph are represented as [ParameterizedType] nodes. */ class ParameterizedType : Type { constructor(type: Type) : super(type) { @@ -48,8 +48,4 @@ class ParameterizedType : Type { override fun dereference(): Type { return this } - - override fun duplicate(): Type { - return ParameterizedType(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 39af389997..945925d50c 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 @@ -103,10 +103,6 @@ class PointerType : Type, SecondOrderType { name = fullTypeName } - override fun duplicate(): Type { - return PointerType(this, elementType.duplicate(), pointerOrigin) - } - val isArray: Boolean get() = pointerOrigin == PointerOrigin.ARRAY diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/ProblemType.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/ProblemType.kt index 7ca10808cc..bb193d35f8 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/ProblemType.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/ProblemType.kt @@ -33,8 +33,4 @@ class ProblemType : Type() { override fun dereference(): Type { return this } - - override fun duplicate(): Type { - return this - } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/ReferenceType.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/ReferenceType.kt index 7c46b9ffbe..b4096c7020 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/ReferenceType.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/ReferenceType.kt @@ -68,18 +68,10 @@ class ReferenceType : Type, SecondOrderType { return elementType.dereference() } - override fun duplicate(): Type { - return ReferenceType(this, elementType) - } - override fun isSimilar(t: Type?): Boolean { return t is ReferenceType && t.elementType == this && super.isSimilar(t) } - fun refreshName() { - name = elementType.name.append("&") - } - override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is ReferenceType) return false diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/StringType.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/StringType.kt index 0be5e3eb34..1b2fda27ce 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/StringType.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/StringType.kt @@ -31,9 +31,4 @@ class StringType( typeName: CharSequence = "", language: Language<*>? = null, generics: List = listOf() -) : ObjectType(typeName, generics, false, language) { - - override fun duplicate(): Type { - return StringType(this.name, language, generics) - } -} +) : ObjectType(typeName, generics, false, language) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/TupleType.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/TupleType.kt index 7bd67d6128..cb4eee9708 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/TupleType.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/TupleType.kt @@ -50,8 +50,4 @@ class TupleType(types: List) : Type() { override fun dereference(): Type { return unknownType() } - - override fun duplicate(): Type { - return TupleType(types.map { it.duplicate() }) - } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/Type.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/Type.kt index 8324271847..055cc0ef37 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/Type.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/Type.kt @@ -139,9 +139,6 @@ abstract class Type : Node { } } - /** @return Creates an exact copy of the current type (chain) */ - abstract fun duplicate(): Type - val typeName: String get() = name.toString() diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/UnknownType.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/UnknownType.kt index 57b2c678fc..7b849dc96d 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/UnknownType.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/UnknownType.kt @@ -54,11 +54,6 @@ class UnknownType private constructor() : Type() { return this } - override fun duplicate(): Type { - // We don't duplicate because we cannot change any properties. - return this - } - override fun hashCode() = Objects.hash(super.hashCode()) override fun equals(other: Any?): Boolean { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TemplateCallResolverHelper.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TemplateCallResolverHelper.kt index e22174aaf5..ad5b11dacd 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TemplateCallResolverHelper.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TemplateCallResolverHelper.kt @@ -30,6 +30,7 @@ import de.fraunhofer.aisec.cpg.graph.declarations.ClassTemplateDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.ParamVariableDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.TemplateDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.TypeParamDeclaration +import de.fraunhofer.aisec.cpg.graph.objectType import de.fraunhofer.aisec.cpg.graph.statements.expressions.ConstructExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.TypeExpression @@ -113,39 +114,50 @@ fun applyMissingParams( templateParametersExplicitInitialization: Map, templateParameterRealDefaultInitialization: Map ) { - val missingParams: List = - template.parameterDefaults.subList( - constructExpression.templateParameters.size, - template.parameterDefaults.size - ) - for (m in missingParams) { - var missingParam = m - if (missingParam is DeclaredReferenceExpression) { - missingParam = missingParam.refersTo - } - if (missingParam in templateParametersExplicitInitialization) { - // If default is a previously defined template argument that has been explicitly - // passed - templateParametersExplicitInitialization[missingParam]?.let { - constructExpression.addTemplateParameter( - it, - TemplateDeclaration.TemplateInitialization.DEFAULT - ) - } - // If template argument is a type add it as a generic to the type as well - (templateParametersExplicitInitialization[missingParam] as? TypeExpression)?.type?.let { - (constructExpression.type as ObjectType).addGeneric(it) - } - } else if (missingParam in templateParameterRealDefaultInitialization) { - // Add default of template parameter to construct declaration - templateParameterRealDefaultInitialization[missingParam]?.let { - constructExpression.addTemplateParameter( - it, - TemplateDeclaration.TemplateInitialization.DEFAULT - ) + with(constructExpression) { + val missingParams: List = + template.parameterDefaults.subList( + constructExpression.templateParameters.size, + template.parameterDefaults.size + ) + for (m in missingParams) { + var missingParam = m + if (missingParam is DeclaredReferenceExpression) { + missingParam = missingParam.refersTo } - (templateParametersExplicitInitialization[missingParam] as? TypeExpression)?.type?.let { - (constructExpression.type as ObjectType).addGeneric(it) + if (missingParam in templateParametersExplicitInitialization) { + // If default is a previously defined template argument that has been explicitly + // passed + templateParametersExplicitInitialization[missingParam]?.let { + constructExpression.addTemplateParameter( + it, + TemplateDeclaration.TemplateInitialization.DEFAULT + ) + } + // If template argument is a type add it as a generic to the type as well + (templateParametersExplicitInitialization[missingParam] as? TypeExpression) + ?.type + ?.let { + val type = constructExpression.type + if (type is ObjectType) { + constructExpression.type = + objectType(type.name, listOf(it, *type.generics.toTypedArray())) + } + } + } else if (missingParam in templateParameterRealDefaultInitialization) { + // Add default of template parameter to construct declaration + templateParameterRealDefaultInitialization[missingParam]?.let { + constructExpression.addTemplateParameter( + it, + TemplateDeclaration.TemplateInitialization.DEFAULT + ) + } + (templateParametersExplicitInitialization[missingParam] as? TypeExpression) + ?.type + ?.let { + constructExpression.type = + objectType(constructExpression.type.name, listOf(it)) + } } } } 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 ef82aaf708..4891b229c0 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 @@ -30,200 +30,35 @@ import de.fraunhofer.aisec.cpg.graph.Component import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration import de.fraunhofer.aisec.cpg.graph.types.* -import de.fraunhofer.aisec.cpg.graph.types.HasSecondaryTypeEdge -import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker.IterativeGraphWalker +import de.fraunhofer.aisec.cpg.processing.IVisitor +import de.fraunhofer.aisec.cpg.processing.strategy.Strategy +/** + * The purpose of this [Pass] is to establish a relationship between [Type] nodes (more specifically + * [ObjectType]s) and their [RecordDeclaration]. + */ open class TypeResolver(ctx: TranslationContext) : ComponentPass(ctx) { - protected val firstOrderTypes = mutableSetOf() - protected val typeState = mutableMapOf>() - - /** - * Reduce the SecondOrderTypes to store only the unique SecondOrderTypes - * - * @param type SecondOrderType that is to be eliminated if an equal is already in typeState or - * is added if not - */ - protected fun processSecondOrderTypes(type: Type) { - val state = typeState.computeIfAbsent(type.root, ::mutableListOf) - if (state.contains(type)) return - - state.add(type) - - val element = ((type as? SecondOrderType)?.elementType as? SecondOrderType) ?: return - - val newElement = state.find { it == element } - if (newElement != null) { - (type as SecondOrderType).elementType = newElement - } else { - processSecondOrderTypes(element as Type) - } - } - - /** - * Ensures that two different Types that are created at different Points are still the same - * object in order to only store one node into the database - * - * @param type newly created Type - * @return If the same type was already stored in the typeState Map the stored one is returned. - * In the other case the parameter type is stored into the map and the parameter type is - * returned - */ - protected fun obtainType(type: Type): Type { - return if (type.root == type && type in typeState) { - typeState.keys.first { it == type } - } else { - addType(type) - type - } - } - - /** - * Responsible for storing new types into typeState - * - * @param type new type - */ - protected fun addType(type: Type) { - if (type.root == type && type !in typeState) { - // This is a rootType and is included in the map as key with empty references - typeState[type] = mutableListOf() - return - } - - // ReferencesTypes - if (type.root in typeState) { - if (type !in (typeState[type.root] ?: listOf())) { - typeState[type.root]?.add(type) - addType((type as SecondOrderType).elementType) - } - } else { - addType(type.root) - addType(type) - } - } - - protected fun removeDuplicateTypes() { - // Remove duplicate firstOrderTypes - firstOrderTypes.addAll(typeManager.firstOrderTypes) - - // Propagate new firstOrderTypes into secondOrderTypes - val secondOrderTypes = typeManager.secondOrderTypes - for (t in secondOrderTypes) { - t.root = firstOrderTypes.firstOrNull { it == t.root } ?: t.root - } - - // Build Map from firstOrderTypes to list of secondOderTypes - for (t in firstOrderTypes) { - typeState[t] = mutableListOf() - } - - // Remove duplicate secondOrderTypes - secondOrderTypes.forEach { processSecondOrderTypes(it) } - - // Remove duplicates from fields - secondOrderTypes.forEach { removeDuplicatesInFields(it) } - } - - /** - * Visits all FirstOrderTypes and replace all the fields like returnVal or parameters for - * FunctionPointertype or Generics for ObjectType - * - * @param t FirstOrderType - */ - protected fun removeDuplicatesInFields(t: Type) { - // Remove duplicates from fields - if (t is FunctionPointerType) { - t.returnType = obtainType(t.returnType) - t.parameters = t.parameters.map(::obtainType) - } else if (t is ObjectType) { - t.generics = t.generics.map(::obtainType) - } - } - - /** - * Pass on the TypeSystem: Sets RecordDeclaration Relationship from ObjectType to - * RecordDeclaration - * - * @param component - */ override fun accept(component: Component) { - removeDuplicateTypes() - val walker = IterativeGraphWalker() - walker.registerOnNodeVisit(::ensureUniqueType) - walker.registerOnNodeVisit(::handle) - walker.registerOnNodeVisit(::ensureUniqueSecondaryTypeEdge) - - for (tu in component.translationUnits) { - walker.iterate(tu) - } - } - - protected fun ensureUniqueSubTypes(subTypes: Collection): List { - val uniqueTypes = mutableListOf() - for (subType in subTypes) { - val trackedTypes = - if (subType.isFirstOrderType) { - typeState.keys - } else { - typeState.computeIfAbsent(subType.root, ::mutableListOf) - } - val unique = trackedTypes.firstOrNull { it == subType } - // TODO Why do we only take the first one even if we don't add it? - if (unique != null && unique !in uniqueTypes) uniqueTypes.add(unique) - } - return uniqueTypes - } - - protected fun ensureUniqueType(node: Node) { - // Avoid handling of ParameterizedType as they should be unique to each class and not - // globally unique - if (node is HasType && node.type !is ParameterizedType) { - val type = node.type - val newType = - if (type.isFirstOrderType) { - typeState.keys - } else { - typeState.computeIfAbsent(type.root, ::mutableListOf) + component.accept( + Strategy::AST_FORWARD, + object : IVisitor() { + /** + * Creates the [ObjectType.recordDeclaration] relationship between [ObjectType]s and + * [RecordDeclaration] with the same [Node.name]. + */ + fun visit(record: RecordDeclaration) { + for (t in typeManager.firstOrderTypes) { + if (t.name == record.name && t is ObjectType) { + // The node is the class of the type t + t.recordDeclaration = record + } } - .firstOrNull { it == type } - if (newType != null) { - node.type = newType - } - } - } - - /** - * ensures that the if a nodes contains secondary type edges, those types are also merged and no - * duplicate is left - * - * @param node implementing [HasSecondaryTypeEdge] - */ - protected fun ensureUniqueSecondaryTypeEdge(node: Node) { - if (node is HasSecondaryTypeEdge) { - node.updateType(typeState.keys) - } else if (node is HasType && node.type is HasSecondaryTypeEdge) { - (node.type as HasSecondaryTypeEdge).updateType(typeState.keys) - } - } - - /** - * Creates the recordDeclaration relationship between ObjectTypes and RecordDeclaration (from - * the Type to the Class) - * - * @param node - */ - fun handle(node: Node) { - if (node is RecordDeclaration) { - for (t in typeState.keys) { - if (t.name == node.name && t is ObjectType) { - // The node is the class of the type t - t.recordDeclaration = node } } - } + ) } override fun cleanup() { - firstOrderTypes.clear() - typeState.clear() + // Nothing to do } } diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/UnresolvedDFGPassTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/UnresolvedDFGPassTest.kt index f5ecc5d7e7..ec3d9d661c 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/UnresolvedDFGPassTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/UnresolvedDFGPassTest.kt @@ -35,7 +35,6 @@ import de.fraunhofer.aisec.cpg.graph.builder.* import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.Literal -import de.fraunhofer.aisec.cpg.graph.types.ObjectType import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertTrue @@ -133,12 +132,7 @@ class UnresolvedDFGPassTest { param("args", t("String[]")) body { declare { - variable( - "os", - t("Optional") { - (this as ObjectType).generics = listOf(t("String")) - } - ) { + variable("os", t("Optional", listOf(t("String")))) { memberCall("getOptionalString", ref("RandomClass")) { isStatic = true } diff --git a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/DeclarationHandler.kt b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/DeclarationHandler.kt index a70d6c933f..0c7dc12bdb 100644 --- a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/DeclarationHandler.kt +++ b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/DeclarationHandler.kt @@ -377,18 +377,26 @@ class DeclarationHandler(lang: CXXLanguageFrontend) : // Loop through all the methods and adjust their receiver types for (method in (innerDeclaration as? RecordDeclaration)?.methods ?: listOf()) { // Add ParameterizedTypes to type - method.receiver?.let { addParameterizedTypesToType(it.type, parameterizedTypes) } + method.receiver?.let { + it.type = addParameterizedTypesToType(it.type, parameterizedTypes) + } } // Add parameterizedTypes to ConstructorDeclaration type and adjust their receiver types for (constructor in innerDeclaration.constructors) { - constructor.receiver?.let { addParameterizedTypesToType(it.type, parameterizedTypes) } - - // We need to add the type to (first) return type as well. The function type is somehow - // magically updated then as well. - constructor.returnTypes.firstOrNull()?.let { - addParameterizedTypesToType(it, parameterizedTypes) + constructor.receiver?.let { + it.type = addParameterizedTypesToType(it.type, parameterizedTypes) } + + // We need to add the type to (first) return type as well as the function type + constructor.returnTypes = + constructor.returnTypes.map { addParameterizedTypesToType(it, parameterizedTypes) } + constructor.type = + FunctionType( + constructor.type.typeName, + (constructor.type as? FunctionType)?.parameters ?: listOf(), + constructor.returnTypes, + ) } } @@ -399,16 +407,18 @@ class DeclarationHandler(lang: CXXLanguageFrontend) : * @param parameterizedTypes */ private fun addParameterizedTypesToType( - type: Type?, + type: Type, parameterizedTypes: List - ) { + ): Type { if (type is ObjectType) { - for (parameterizedType in parameterizedTypes) { - type.addGeneric(parameterizedType) - } + // Because we cannot mutate the existing type (otherwise this will affect ALL usages of + // it), we need to create a new type with the correct generics + return objectType(type.name, parameterizedTypes) } else if (type is PointerType) { - addParameterizedTypesToType(type.elementType, parameterizedTypes) + return addParameterizedTypesToType(type.elementType, parameterizedTypes).pointer() } + + return type } private fun handleSimpleDeclaration(ctx: IASTSimpleDeclaration): Declaration { diff --git a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/templates/ClassTemplateTest.kt b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/templates/ClassTemplateTest.kt index 47a9d2f850..20c00f0f24 100644 --- a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/templates/ClassTemplateTest.kt +++ b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/templates/ClassTemplateTest.kt @@ -155,7 +155,10 @@ internal class ClassTemplateTest : BaseTest() { val type2 = findByUniqueName(result.allChildren(), "class Type2") val first = findByUniqueName(result.fields, "first") val second = findByUniqueName(result.fields, "second") - val receiver = pair.byNameOrNull("Pair")?.receiver + val constructor = pair.constructors["Pair"] + assertNotNull(constructor) + + val receiver = constructor.receiver assertNotNull(receiver) val pairConstructorDeclaration = From 68eb0574b593dc4d1f3efaf94f5723b1508336c9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 30 Aug 2023 07:15:42 +0200 Subject: [PATCH 134/143] Update spotless to v6.21.0 (#1307) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 18a1cb8d8e..cc95191d6b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,7 +3,7 @@ kotlin = "1.9.0" neo4j = "4.0.6" log4j = "2.20.0" sonarqube = "4.2.0.3129" -spotless = "6.20.0" +spotless = "6.21.0" nexus-publish = "1.3.0" [libraries] From 430861636db3e2563ffad841e9cf7320ee3fdd00 Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Wed, 30 Aug 2023 07:47:03 +0200 Subject: [PATCH 135/143] Smaller fixes in logging and utils (#1288) This PR fixes smaller things such as proper display of the log source by inlining the log functions. It also fixes other smaller issues. Co-authored-by: Konrad Weiss --- .../aisec/cpg/TranslationManager.kt | 37 +++++----- .../fraunhofer/aisec/cpg/frontends/Handler.kt | 8 ++- .../de/fraunhofer/aisec/cpg/helpers/Util.kt | 68 +++++++------------ .../aisec/cpg/passes/inference/Inference.kt | 17 +++-- 4 files changed, 61 insertions(+), 69 deletions(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationManager.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationManager.kt index 55f3ee5469..1144637f35 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationManager.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationManager.kt @@ -32,20 +32,16 @@ import de.fraunhofer.aisec.cpg.frontends.TranslationException import de.fraunhofer.aisec.cpg.graph.Component import de.fraunhofer.aisec.cpg.graph.Name import de.fraunhofer.aisec.cpg.helpers.Benchmark -import de.fraunhofer.aisec.cpg.helpers.Util import de.fraunhofer.aisec.cpg.passes.* import java.io.File import java.io.PrintWriter import java.lang.reflect.InvocationTargetException import java.nio.file.Files -import java.nio.file.Path -import java.nio.file.attribute.BasicFileAttributes import java.util.* import java.util.concurrent.CompletableFuture import java.util.concurrent.CompletionException import java.util.concurrent.ExecutionException import java.util.concurrent.atomic.AtomicBoolean -import java.util.stream.Collectors import kotlin.reflect.full.findAnnotation import org.slf4j.LoggerFactory @@ -147,15 +143,13 @@ private constructor( val list = sourceLocations.flatMap { file -> if (file.isDirectory) { - Files.find( - file.toPath(), - 999, - { _: Path?, fileAttr: BasicFileAttributes -> - fileAttr.isRegularFile - } - ) - .map { it.toFile() } - .collect(Collectors.toList()) + val files = + file + .walkTopDown() + .onEnter { !it.name.startsWith(".") } + .filter { it.isFile && !it.name.startsWith(".") } + .toList() + files } else { val frontendClass = file.language?.frontend val supportsParallelParsing = @@ -182,8 +176,8 @@ private constructor( PrintWriter(tmpFile).use { writer -> list.forEach { - val cxxExtensions = listOf(".c", ".cpp", ".cc", ".cxx") - if (cxxExtensions.contains(Util.getExtension(it))) { + val cxxExtensions = listOf("c", "cpp", "cc", "cxx") + if (cxxExtensions.contains(it.extension)) { if (ctx.config.topLevel != null) { val topLevel = ctx.config.topLevel.toPath() writer.write( @@ -276,7 +270,12 @@ private constructor( Thread.currentThread().interrupt() } catch (e: ExecutionException) { log.error("Error parsing ${futureToFile[future]}", e) - Thread.currentThread().interrupt() + // We previously called Thread.currentThread().interrupt here, however + // it is unsure, why. Therefore, instead of just removing this line, we + // "disabled" it and left this comment here for future generations. If + // we see that it is really not needed we can remove it completely at some + // point. + // Thread.currentThread().interrupt() } } @@ -298,9 +297,7 @@ private constructor( val usedFrontends = mutableSetOf>() for (sourceLocation in sourceLocations) { - log.info("Parsing {}", sourceLocation.absolutePath) - - var f = parse(component, ctx, sourceLocation) + val f = parse(component, ctx, sourceLocation) if (f != null) { handleCompletion(result, usedFrontends, sourceLocation, f) } @@ -331,6 +328,8 @@ private constructor( ctx: TranslationContext, sourceLocation: File, ): LanguageFrontend<*, *>? { + log.info("Parsing {}", sourceLocation.absolutePath) + var frontend: LanguageFrontend<*, *>? = null try { frontend = getFrontend(sourceLocation, ctx) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/Handler.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/Handler.kt index eebd21d093..189f191f1c 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/Handler.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/Handler.kt @@ -44,7 +44,7 @@ import org.slf4j.LoggerFactory * @param the raw ast node specific to the parser * @param the language frontend */ -abstract class Handler>( +abstract class Handler>( protected val configConstructor: Supplier, /** Returns the frontend which used this handler. */ val frontend: L @@ -140,8 +140,10 @@ abstract class Handler - val buffer = ByteArray(1024) - var length: Int - while (inputStream.read(buffer).also { length = it } != -1) { - result.write(buffer, 0, length) - } - return result.toString(StandardCharsets.UTF_8) - } - } - - @JvmStatic - fun distinctBy(by: Function): Predicate { - val seen = mutableSetOf() - return Predicate { t: T -> seen.add(by.apply(t)) } - } - - fun getExtension(file: File): String { - val pos = file.name.lastIndexOf('.') - return if (pos > 0) { - file.name.substring(pos).lowercase(Locale.getDefault()) - } else { - "" - } - } - - @JvmStatic - fun warnWithFileLocation( + inline fun warnWithFileLocation( lang: LanguageFrontend, astNode: AstNode, log: Logger, @@ -194,8 +159,7 @@ object Util { ) } - @JvmStatic - fun errorWithFileLocation( + inline fun errorWithFileLocation( lang: LanguageFrontend, astNode: AstNode, log: Logger, @@ -212,22 +176,42 @@ object Util { ) } - @JvmStatic - fun warnWithFileLocation(node: Node, log: Logger, format: String?, vararg arguments: Any?) { + inline fun warnWithFileLocation( + node: Node, + log: Logger, + format: String?, + vararg arguments: Any? + ) { log.warn( String.format("%s: %s", PhysicalLocation.locationLink(node.location), format), *arguments ) } - @JvmStatic - fun errorWithFileLocation(node: Node, log: Logger, format: String?, vararg arguments: Any?) { + inline fun errorWithFileLocation( + node: Node, + log: Logger, + format: String?, + vararg arguments: Any? + ) { log.error( String.format("%s: %s", PhysicalLocation.locationLink(node.location), format), *arguments ) } + inline fun debugWithFileLocation( + node: Node?, + log: Logger, + format: String?, + vararg arguments: Any? + ) { + log.debug( + String.format("%s: %s", PhysicalLocation.locationLink(node?.location), format), + *arguments + ) + } + /** * Split a String into multiple parts by using one or more delimiter characters. Any delimiters * that are surrounded by matching opening and closing brackets are skipped. E.g. "a,(b,c)" will diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/Inference.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/Inference.kt index 512a94f4e8..eb40b5606e 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/Inference.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/Inference.kt @@ -35,6 +35,7 @@ import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression import de.fraunhofer.aisec.cpg.graph.statements.expressions.TypeExpression import de.fraunhofer.aisec.cpg.graph.types.* +import de.fraunhofer.aisec.cpg.helpers.Util.debugWithFileLocation import java.util.* import org.slf4j.Logger import org.slf4j.LoggerFactory @@ -70,6 +71,7 @@ class Inference(val start: Node, override val ctx: TranslationContext) : isStatic: Boolean, signature: List, returnType: Type?, + hint: CallExpression? = null ): FunctionDeclaration { // We assume that the start is either a record, a namespace or the translation unit val record = start as? RecordDeclaration @@ -91,7 +93,9 @@ class Inference(val start: Node, override val ctx: TranslationContext) : newFunctionDeclaration(name ?: "", code) } - Companion.log.debug( + debugWithFileLocation( + hint, + log, "Inferred a new {} declaration {} with parameter types {}", if (inferred is MethodDeclaration) "method" else "function", inferred.name, @@ -361,7 +365,7 @@ class Inference(val start: Node, override val ctx: TranslationContext) : /** * This class implements a [HasType.TypeObserver] and uses the observed type to set the - * [ValueDeclaration.declaredType] of a [ValueDeclaration], based on the types we see. It can be + * [ValueDeclaration.type] of a [ValueDeclaration], based on the types we see. It can be * registered on objects that are used to "start" an inference, for example a * [MemberExpression], which infers a [FieldDeclaration]. Once the type of the member expression * becomes known, we can use this information to set the type of the field. @@ -429,7 +433,8 @@ fun TranslationUnitDeclaration.inferFunction( isStatic, call.signature, // TODO: Is the call's type the return value's type? - call.type + call.type, + call ) } @@ -446,7 +451,8 @@ fun NamespaceDeclaration.inferFunction( isStatic, call.signature, // TODO: Is the call's type the return value's type? - call.type + call.type, + call ) } @@ -463,6 +469,7 @@ fun RecordDeclaration.inferMethod( isStatic, call.signature, // TODO: Is the call's type the return value's type? - call.type + call.type, + call ) as MethodDeclaration } From 2ec07591fc079e8698d26de5af393b0ba5130e71 Mon Sep 17 00:00:00 2001 From: KuechA <31155350+KuechA@users.noreply.github.com> Date: Wed, 30 Aug 2023 11:16:24 +0200 Subject: [PATCH 136/143] Copy objects less frequently (#1279) --- .../cpg/graph/statements/LabelStatement.kt | 10 +++++- .../aisec/cpg/helpers/EOGWorklist.kt | 36 +++++++++++++++---- 2 files changed, 39 insertions(+), 7 deletions(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/LabelStatement.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/LabelStatement.kt index b6f57d0056..9a36428482 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/LabelStatement.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/LabelStatement.kt @@ -26,6 +26,8 @@ package de.fraunhofer.aisec.cpg.graph.statements import de.fraunhofer.aisec.cpg.graph.AST +import de.fraunhofer.aisec.cpg.graph.StatementHolder +import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge import java.util.Objects import org.apache.commons.lang3.builder.ToStringBuilder @@ -33,7 +35,7 @@ import org.apache.commons.lang3.builder.ToStringBuilder * A label attached to a statement that is used to change control flow by labeled continue and * breaks (Java) or goto(C++). */ -class LabelStatement : Statement() { +class LabelStatement : Statement(), StatementHolder { /** Statement that the label is attached to. Can be a simple or compound statement. */ @AST var subStatement: Statement? = null @@ -48,6 +50,12 @@ class LabelStatement : Statement() { .toString() } + override var statementEdges: MutableList> + get() = subStatement?.let { PropertyEdge.wrap(listOf(it), this) } ?: mutableListOf() + set(value) { + subStatement = PropertyEdge.unwrap(value).firstOrNull() + } + override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is LabelStatement) return false diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/EOGWorklist.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/EOGWorklist.kt index d7238dc397..1c5d322f75 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/EOGWorklist.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/EOGWorklist.kt @@ -57,7 +57,7 @@ abstract class LatticeElement(open val elements: T) : Comparable) : LatticeElement>(elements) { override fun lub(other: LatticeElement>) = - PowersetLattice((other.elements).union(this.elements)) + PowersetLattice(other.elements.union(this.elements)) override fun duplicate() = PowersetLattice(this.elements.toSet()) @@ -248,9 +248,21 @@ inline fun iterateEOG( while (worklist.isNotEmpty()) { val (nextNode, state) = worklist.pop() - val newState = transformation(nextNode, state.duplicate(), worklist) + // This should check if we're not near the beginning/end of a basic block (i.e., there are + // no merge points or branches of the EOG nearby). If that's the case, we just parse the + // whole basic block and do not want to duplicate the state. Near the beginning/end, we do + // want to copy the state to avoid terminating the iteration too early by messing up with + // the state-changing checks. + val insideBB = + (nextNode.nextEOG.size == 1 && nextNode.prevEOG.singleOrNull()?.nextEOG?.size == 1) + val newState = + transformation(nextNode, if (insideBB) state else state.duplicate(), worklist) if (worklist.update(nextNode, newState)) { - nextNode.nextEOG.forEach { if (it is K) worklist.push(it, newState.duplicate()) } + nextNode.nextEOG.forEach { + if (it is K) { + worklist.push(it, newState) + } + } } } return worklist.mop() @@ -271,10 +283,22 @@ inline fun , N : Any, V> iterateEOG( while (worklist.isNotEmpty()) { val (nextEdge, state) = worklist.pop() - val newState = transformation(nextEdge, state.duplicate(), worklist) - if (worklist.update(nextEdge, newState)) { + // This should check if we're not near the beginning/end of a basic block (i.e., there are + // no merge points or branches of the EOG nearby). If that's the case, we just parse the + // whole basic block and do not want to duplicate the state. Near the beginning/end, we do + // want to copy the state to avoid terminating the iteration too early by messing up with + // the state-changing checks. + val insideBB = + (nextEdge.end.nextEOG.size == 1 && + nextEdge.end.prevEOG.size == 1 && + nextEdge.start.nextEOG.size == 1) + val newState = + transformation(nextEdge, if (insideBB) state else state.duplicate(), worklist) + if (insideBB || worklist.update(nextEdge, newState)) { nextEdge.end.nextEOGEdges.forEach { - if (it is K) worklist.push(it, newState.duplicate()) + if (it is K) { + worklist.push(it, newState) + } } } } From 2ff78e10916eee5a4754925e3875d85eba313c8b Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Wed, 30 Aug 2023 19:22:24 +0200 Subject: [PATCH 137/143] Improved scope handling in `ScopedWalker` (#1308) This PR simplifies and improves the scope handling in the `ScopedWalker`. Previously, the walker tried to enter/leave scopes with a certain heuristic when nodes were iterated or their parent changed. While this worked for most cases, it could diverge from the actual scoping that was established during a frontend run. One such example was when method declarations were declared outside the AST of a class (a common use-case in Go or C++). In this case, the walker left the record scope open after existing the function, since it did not know of its existence (only of the existence of the function scope). I therefore changed the behaviour in a way that the walker "jumps" directly to the scope of the node, which is recorded in its `scope` variable. I suspect, that at the time of wiriting of the old behaviour, we did not have the `scope` variable yet. This now guarantees that exactly the scope that the frontend intended for the given variable is now "replayed" in the walker --- .../de/fraunhofer/aisec/cpg/ScopeManager.kt | 2 +- .../aisec/cpg/helpers/SubgraphWalker.kt | 107 +++--------------- .../aisec/cpg/passes/CallResolver.kt | 1 - .../aisec/cpg/passes/VariableUsageResolver.kt | 1 - .../cpg/passes/FunctionPointerCallResolver.kt | 4 - 5 files changed, 17 insertions(+), 98 deletions(-) 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 7ab83557f5..4e42d509f3 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 @@ -681,7 +681,7 @@ class ScopeManager : ScopeProvider { * Handle with care, here be dragons. Should not be exposed outside of the cpg-core module. */ @PleaseBeCareful - private fun jumpTo(scope: Scope?): Scope? { + internal fun jumpTo(scope: Scope?): Scope? { val oldScope = currentScope currentScope = scope return oldScope 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 38d3a66fa1..49644cfa7d 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 @@ -29,14 +29,10 @@ import de.fraunhofer.aisec.cpg.ScopeManager import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend import de.fraunhofer.aisec.cpg.graph.AST import de.fraunhofer.aisec.cpg.graph.Node -import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration -import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration -import de.fraunhofer.aisec.cpg.graph.declarations.ValueDeclaration import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.checkForPropertyEdge import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.unwrap -import de.fraunhofer.aisec.cpg.graph.statements.CompoundStatement import de.fraunhofer.aisec.cpg.processing.strategy.Strategy import java.lang.annotation.AnnotationFormatError import java.lang.reflect.Field @@ -45,7 +41,6 @@ import java.util.function.BiConsumer import java.util.function.Consumer import java.util.function.Predicate import java.util.stream.Collectors -import org.apache.commons.lang3.tuple.MutablePair import org.neo4j.ogm.annotation.Relationship import org.slf4j.LoggerFactory @@ -289,11 +284,9 @@ object SubgraphWalker { * Once "parent" has been visited, we continue descending into its children. First into * "child1", followed by "subchild". Once we are done there, we return to "child1". At this * point, the exit handler notifies the user that "subchild" is being exited. Afterwards we - * exit "child1", and after "child2" is done, "parent" is exited. This callback is important - * for tracking declaration scopes, as e.g. anything declared in "child1" is also visible to - * "subchild", but not to "child2". + * exit "child1", and after "child2" is done, "parent" is exited. */ - private val onScopeExit: MutableList> = ArrayList() + private val onNodeExit: MutableList> = ArrayList() /** * The core iterative AST traversal algorithm: In a depth-first way we descend into the @@ -313,7 +306,7 @@ object SubgraphWalker { (backlog as ArrayDeque).peek() == current ) { val exiting = (backlog as ArrayDeque).pop() - onScopeExit.forEach(Consumer { c: Consumer -> c.accept(exiting) }) + onNodeExit.forEach(Consumer { c: Consumer -> c.accept(exiting) }) } else { // re-place the current node as a marker for the above check to find out when we // need to exit a scope @@ -344,13 +337,13 @@ object SubgraphWalker { onNodeVisit2.add(callback) } - fun registerOnScopeExit(callback: Consumer) { - onScopeExit.add(callback) + fun registerOnNodeExit(callback: Consumer) { + onNodeExit.add(callback) } fun clearCallbacks() { onNodeVisit.clear() - onScopeExit.clear() + onNodeExit.clear() } fun getTodo(): Deque { @@ -359,21 +352,13 @@ object SubgraphWalker { } /** - * Handles declaration scope monitoring for iterative traversals. If this is not required, use - * [IterativeGraphWalker] for less overhead. - * - * Declaration scopes are similar to [de.fraunhofer.aisec.cpg.ScopeManager] scopes: - * [ValueDeclaration]s located inside a scope (i.e. are children of the scope root) are visible - * to any children of the scope root. Scopes can be layered, where declarations from parent - * scopes are visible to the children but not the other way around. + * This class traverses the graph in a similar way as the [IterativeGraphWalker], but with the + * added feature, that a [ScopeManager] is populated with the scope information of the current + * node. This way, we can call functions on the supplied [scopeManager] and emulate that we are + * currently in the scope of the "consumed" node in the callback. This can be useful for + * resolving declarations or other scope-related tasks. */ class ScopedWalker { - // declarationScope -> (parentScope, declarations) - private val nodeToParentBlockAndContainedValueDeclarations: - MutableMap< - Node, org.apache.commons.lang3.tuple.Pair> - > = - IdentityHashMap() private var walker: IterativeGraphWalker? = null private val scopeManager: ScopeManager @@ -415,7 +400,6 @@ object SubgraphWalker { fun iterate(root: Node) { walker = IterativeGraphWalker() handlers.forEach { h -> walker?.registerOnNodeVisit { n -> handleNode(n, h) } } - walker?.registerOnScopeExit { exiting: Node -> leaveScope(exiting) } walker?.iterate(root) } @@ -423,73 +407,14 @@ object SubgraphWalker { current: Node, handler: TriConsumer ) { - scopeManager.enterScopeIfExists(current) + // Jump to the node's scope, if it is different from ours. + if (scopeManager.currentScope != current.scope) { + scopeManager.jumpTo(current.scope) + } + val parent = walker?.backlog?.peek() - // TODO: actually we should not handle this in handleNode but have something similar to - // onScopeEnter because the method declaration already correctly sets the scope handler.accept(scopeManager.currentRecord, parent, current) } - - private fun leaveScope(exiting: Node) { - scopeManager.leaveScope(exiting) - } - - fun collectDeclarations(current: Node?) { - if (current == null) return - - var parentBlock: Node? = null - - // get containing Record or Compound - for (node in walker?.backlog ?: listOf()) { - if ( - node is RecordDeclaration || - node is CompoundStatement || - node is FunctionDeclaration || - node is - TranslationUnitDeclaration // can also be a translation unit for global - // (C) functions - ) { - parentBlock = node - break - } - } - nodeToParentBlockAndContainedValueDeclarations[current] = - MutablePair(parentBlock, ArrayList()) - if (current is ValueDeclaration) { - LOGGER.trace("Adding variable {}", current.code) - if (parentBlock == null) { - LOGGER.warn("Parent block is empty during subgraph run") - } else { - nodeToParentBlockAndContainedValueDeclarations[parentBlock]?.right?.add(current) - } - } - } - - /** - * @param scope - * @param predicate - * @return - */ - @Deprecated("""The scope manager should be used instead. - """) - fun getDeclarationForScope( - scope: Node, - predicate: Predicate - ): Optional { - var currentScope = scope - - // iterate all declarations from the current scope and all its parent scopes - while (nodeToParentBlockAndContainedValueDeclarations.containsKey(scope)) { - val entry = nodeToParentBlockAndContainedValueDeclarations[currentScope] - for (`val` in entry?.right ?: listOf()) { - if (predicate.test(`val`)) { - return Optional.of(`val`) - } - } - entry?.left?.let { currentScope = it } - } - return Optional.empty() - } } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolver.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolver.kt index 5bd0a46fa2..c951b2b510 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolver.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolver.kt @@ -74,7 +74,6 @@ open class CallResolver(ctx: TranslationContext) : SymbolResolverPass(ctx) { override fun accept(component: Component) { walker = ScopedWalker(scopeManager) - walker.registerHandler { _, _, currNode -> walker.collectDeclarations(currNode) } walker.registerHandler { node, _ -> findRecords(node) } walker.registerHandler { node, _ -> findTemplates(node) } walker.registerHandler { currentClass, _, currentNode -> diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/VariableUsageResolver.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/VariableUsageResolver.kt index d37ba927fd..d28e6c1e49 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/VariableUsageResolver.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/VariableUsageResolver.kt @@ -66,7 +66,6 @@ open class VariableUsageResolver(ctx: TranslationContext) : SymbolResolverPass(c for (tu in component.translationUnits) { currentTU = tu walker.clearCallbacks() - walker.registerHandler { _, _, currNode -> walker.collectDeclarations(currNode) } walker.registerHandler { node, _ -> findRecords(node) } walker.registerHandler { node, _ -> findEnums(node) } walker.iterate(currentTU) diff --git a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/FunctionPointerCallResolver.kt b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/FunctionPointerCallResolver.kt index 6f5279192e..76c565bf26 100644 --- a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/FunctionPointerCallResolver.kt +++ b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/FunctionPointerCallResolver.kt @@ -30,7 +30,6 @@ import de.fraunhofer.aisec.cpg.frontends.cxx.CXXLanguageFrontend import de.fraunhofer.aisec.cpg.graph.Component import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration -import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration import de.fraunhofer.aisec.cpg.graph.pointer import de.fraunhofer.aisec.cpg.graph.statements.expressions.* @@ -63,9 +62,6 @@ class FunctionPointerCallResolver(ctx: TranslationContext) : ComponentPass(ctx) override fun accept(component: Component) { inferDfgForUnresolvedCalls = config.inferenceConfiguration.inferDfgForUnresolvedSymbols walker = ScopedWalker(scopeManager) - walker.registerHandler { _: RecordDeclaration?, _: Node?, currNode: Node? -> - walker.collectDeclarations(currNode) - } walker.registerHandler { node, _ -> resolve(node) } for (tu in component.translationUnits) { From ccdc65a01fd21d8105cbe886c594819f00a52588 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 1 Sep 2023 07:00:38 +0200 Subject: [PATCH 138/143] Update org.jetbrains.dokka to v1.9.0 (#1310) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index cc95191d6b..81c52c1792 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -41,8 +41,8 @@ mockito = { module = "org.mockito:mockito-core", version = "5.5.0"} # plugins needed for build.gradle.kts in buildSrc kotlin-gradle = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } -dokka-gradle = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version = "1.8.10" } # the dokka plugin is slightly behind the main Kotlin release cycle -dokka-versioning= {module = "org.jetbrains.dokka:versioning-plugin", version = "1.8.10"} +dokka-gradle = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version = "1.9.0" } # the dokka plugin is slightly behind the main Kotlin release cycle +dokka-versioning= {module = "org.jetbrains.dokka:versioning-plugin", version = "1.9.0"} sonarqube-gradle = { module = "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin", version.ref = "sonarqube" } spotless-gradle = { module = "com.diffplug.spotless:spotless-plugin-gradle", version.ref = "spotless" } nexus-publish-gradle = { module = "io.github.gradle-nexus:publish-plugin", version.ref = "nexus-publish" } From 223084c0a17385c9065c894d33cdd5610278268f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 1 Sep 2023 05:05:53 +0000 Subject: [PATCH 139/143] Update dependency org.jetbrains.dokka:versioning-plugin to v1.9.0 (#1309) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 4284914adc..83cc1752e5 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -49,7 +49,7 @@ allprojects { val dokkaPlugin by configurations dependencies { - dokkaPlugin("org.jetbrains.dokka:versioning-plugin:1.8.10") + dokkaPlugin("org.jetbrains.dokka:versioning-plugin:1.9.0") } } From e7c3efbe14825fc75338ae67d65517e829970eaa Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 2 Sep 2023 11:17:46 +0200 Subject: [PATCH 140/143] Update sonarqube to v4.3.1.3277 (#1312) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 81c52c1792..46ee7ccde6 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,7 +2,7 @@ kotlin = "1.9.0" neo4j = "4.0.6" log4j = "2.20.0" -sonarqube = "4.2.0.3129" +sonarqube = "4.3.1.3277" spotless = "6.21.0" nexus-publish = "1.3.0" From 001f56485c5319f1e8d6e5f3ed8a47955c20090e Mon Sep 17 00:00:00 2001 From: KuechA <31155350+KuechA@users.noreply.github.com> Date: Tue, 5 Sep 2023 15:21:18 +0200 Subject: [PATCH 141/143] Remove unused file (#1314) --- .../src/test/resources/openssl/client.cpp | 150 ------------------ 1 file changed, 150 deletions(-) delete mode 100644 cpg-core/src/test/resources/openssl/client.cpp diff --git a/cpg-core/src/test/resources/openssl/client.cpp b/cpg-core/src/test/resources/openssl/client.cpp deleted file mode 100644 index 9ec4194b85..0000000000 --- a/cpg-core/src/test/resources/openssl/client.cpp +++ /dev/null @@ -1,150 +0,0 @@ -#include -#include -#include -#include -#include -#include - -SSL *ssl; -// std::string bad_ciphers = "MD5"; -// for now, we intentially use a C string here to make our LLVM analysis a little bit easier -const char *bad_ciphers = "MD5"; - -int callMeBack(int preverify_ok, X509_STORE_CTX *x509_ctx); - -int connectTo(std::string ip, int test) -{ - int s = socket(AF_INET, SOCK_STREAM, 0); - - if (!s) - { - printf("Error creating socket.\n"); - return -1; - } - - std::cerr << "Connecting to " << ip << "..." << std::endl; - - struct sockaddr_in sa; - memset(&sa, 0, sizeof(sa)); - sa.sin_family = AF_INET; - sa.sin_addr.s_addr = inet_addr(ip.c_str()); - sa.sin_port = htons(443); - socklen_t socklen = sizeof(sa); - - if (connect(s, (struct sockaddr *)&sa, sizeof(sa))) - { - std::cerr << "Error connecting to server." << std::endl; - return -1; - } - - return s; -} - -void failDisableVerification(SSL_CTX *ctx) -{ - SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, callMeBack); -} - -void failSetInsecureCiphers(SSL_CTX *ctx) -{ - char ciphers[] = "ALL:!ADH"; - - SSL_CTX_set_cipher_list(ctx, ciphers); -} - -void failSetInsecureCiphersLiteral(SSL_CTX *ctx) -{ - SSL_CTX_set_cipher_list(ctx, "ALL:!ADH"); -} - -void failSetInsecureCiphersSTL(SSL_CTX *ctx) -{ - std::string ciphers = "ALL:!ADH"; - - SSL_CTX_set_cipher_list(ctx, ciphers.c_str()); -} - -void failSetInsecureCiphersGlobal(SSL_CTX *ctx) -{ - SSL_CTX_set_cipher_list(ctx, bad_ciphers); -} - -void failDisableVerificationLambda(SSL_CTX *ctx) -{ - // lambdas do not work yet - /*SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, [](int preverify_ok, X509_STORE_CTX *x509_ctx) { - return 1; - });*/ -} - -SSL_CTX *initTLSContext() -{ - SSL_library_init(); - SSL_load_error_strings(); - SSL_CTX *ctx = SSL_CTX_new(TLSv1_2_client_method()); - - // set insecure cipher - failSetInsecureCiphers(ctx); - failSetInsecureCiphersLiteral(ctx); - failSetInsecureCiphersSTL(ctx); - failSetInsecureCiphersGlobal(ctx); - - // enable verification - SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, nullptr); - - // disable verification - failDisableVerification(ctx); - - return ctx; -} - -int main(int argc, char **argv) -{ - int s = connectTo("172.217.18.99", 2); - if (s < 0) - { - return -1; - } - - SSL_CTX *ctx = initTLSContext(); - - ssl = SSL_new(ctx); - - if (!ssl) - { - std::cerr << "Error creating SSL." << std::endl; - return -1; - } - - SSL_set_fd(ssl, s); - - int err = SSL_connect(ssl); - // this one confuses neo4j ogm - if (err <= 0) - { - int sslerr = ERR_get_error(); - - std::cerr << "Error creating SSL connection. Error Code: " << ERR_error_string(sslerr, nullptr) << std::endl; - return -1; - } - - if (err <= 0) - { - std::cerr << "Error creating SSL connection. Error Code: " << ERR_error_string(ERR_get_error(), nullptr) << std::endl; - return -1; - } - - if (SSL_get_verify_result(ssl) == X509_V_OK) - { - std::cout << "Call to SSL_get_verify_result is ok" << std::endl; - } - - std::cout << "SSL communication established using " << SSL_get_cipher(ssl) << std::endl; - - return 0; -} - -int callMeBack(int preverify_ok, X509_STORE_CTX *x509_ctx) -{ - return 1; -} \ No newline at end of file From 3fc111768775ab68a5aee7571db331d4624dacc8 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 8 Sep 2023 09:56:38 +0200 Subject: [PATCH 142/143] Update dependency rollup to v3.29.0 (#1316) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- cpg-language-typescript/src/main/nodejs/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cpg-language-typescript/src/main/nodejs/package-lock.json b/cpg-language-typescript/src/main/nodejs/package-lock.json index 38c5a539c2..9be1043148 100644 --- a/cpg-language-typescript/src/main/nodejs/package-lock.json +++ b/cpg-language-typescript/src/main/nodejs/package-lock.json @@ -370,9 +370,9 @@ } }, "node_modules/rollup": { - "version": "3.28.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.28.0.tgz", - "integrity": "sha512-d7zhvo1OUY2SXSM6pfNjgD5+d0Nz87CUp4mt8l/GgVP3oBsPwzNvSzyu1me6BSG9JIgWNTVcafIXBIyM8yQ3yw==", + "version": "3.29.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.0.tgz", + "integrity": "sha512-nszM8DINnx1vSS+TpbWKMkxem0CDWk3cSit/WWCBVs9/JZ1I/XLwOsiUglYuYReaeWWSsW9kge5zE5NZtf/a4w==", "dev": true, "bin": { "rollup": "dist/bin/rollup" From 15ca3aac9337d379987cbe6266eeb6d42639e267 Mon Sep 17 00:00:00 2001 From: Konrad Weiss Date: Tue, 12 Sep 2023 12:03:28 +0200 Subject: [PATCH 143/143] Renaming nodes to be shorter and more consistent (#1303) * Renaming nodes to be shorter and more consistent * Reverting Stmt back to Statement etc. * Fix naming in python * Finish renaming nodes * Replacing outdated names in docs * Updating names and related to node renaming, unifying BlockStatement and BlockStatementExpression * Spotless * Update cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/StatementBuilder.kt Co-authored-by: KuechA <31155350+KuechA@users.noreply.github.com> * Fix comments and naming * Adding Doc and fixing spotless * Co-authored-by: Maximilian Kaul --- CONTRIBUTING.md | 2 +- .../aisec/cpg/analysis/MultiValueEvaluator.kt | 57 ++--- .../aisec/cpg/analysis/SizeEvaluator.kt | 6 +- .../aisec/cpg/analysis/ValueEvaluator.kt | 30 +-- .../cpg/analysis/fsm/DFAOrderEvaluator.kt | 22 +- .../aisec/cpg/graph/EvaluateExtensions.kt | 4 +- .../de/fraunhofer/aisec/cpg/query/Query.kt | 12 +- .../de/fraunhofer/aisec/cpg/query/README.md | 2 +- .../cpg/analysis/MultiValueEvaluatorTest.kt | 7 +- .../aisec/cpg/analysis/SizeEvaluatorTest.kt | 8 +- .../fsm/ComplexDFAOrderEvaluationTest.kt | 199 +++++++++--------- .../fsm/SimpleDFAOrderEvaluationTest.kt | 40 ++-- .../cpg/passes/UnreachableEOGPassTest.kt | 4 +- .../fraunhofer/aisec/cpg/query/QueryTest.kt | 55 ++--- .../aisec/cpg/analysis/NullPointerCheck.kt | 2 +- .../aisec/cpg/analysis/OutOfBoundsCheck.kt | 13 +- .../aisec/cpg/console/Extensions.kt | 7 +- .../de/fraunhofer/aisec/cpg/ScopeManager.kt | 16 +- .../aisec/cpg/graph/DeclarationBuilder.kt | 34 +-- .../aisec/cpg/graph/ExpressionBuilder.kt | 58 +++-- .../fraunhofer/aisec/cpg/graph/Extensions.kt | 27 ++- .../de/fraunhofer/aisec/cpg/graph/Holder.kt | 6 +- .../aisec/cpg/graph/StatementBuilder.kt | 18 -- .../aisec/cpg/graph/builder/Fluent.kt | 122 +++++------ .../graph/declarations/FieldDeclaration.kt | 1 - .../graph/declarations/FunctionDeclaration.kt | 26 +-- .../FunctionTemplateDeclaration.kt | 2 +- .../graph/declarations/MethodDeclaration.kt | 25 ++- ...Declaration.kt => ParameterDeclaration.kt} | 4 +- ...ration.kt => RecordTemplateDeclaration.kt} | 10 +- .../graph/declarations/TemplateDeclaration.kt | 16 +- ...aration.kt => TypeParameterDeclaration.kt} | 4 +- ...{UsingDirective.kt => UsingDeclaration.kt} | 6 +- .../graph/declarations/ValueDeclaration.kt | 14 +- .../graph/declarations/VariableDeclaration.kt | 4 +- .../aisec/cpg/graph/scopes/BlockScope.kt | 9 +- .../cpg/graph/scopes/ValueDeclarationScope.kt | 2 +- .../statements/ASMDeclarationStatement.kt | 2 +- .../aisec/cpg/graph/statements/CatchClause.kt | 3 +- .../aisec/cpg/graph/statements/DoStatement.kt | 2 +- .../cpg/graph/statements/ForEachStatement.kt | 17 +- .../aisec/cpg/graph/statements/IfStatement.kt | 8 +- .../graph/statements/SynchronizedStatement.kt | 9 +- .../cpg/graph/statements/TryStatement.kt | 5 +- .../cpg/graph/statements/WhileStatement.kt | 2 +- .../expressions/AssignExpression.kt | 16 +- .../statements/expressions/BinaryOperator.kt | 5 +- .../Block.kt} | 12 +- .../statements/expressions/CallExpression.kt | 5 +- .../CompoundStatementExpression.kt | 47 ----- .../expressions/ConditionalExpression.kt | 25 +-- .../expressions/ConstructExpression.kt | 1 + ...cation.kt => ConstructorCallExpression.kt} | 5 +- .../DesignatedInitializerExpression.kt | 1 + .../expressions/InitializerListExpression.kt | 1 + .../expressions/MemberExpression.kt | 2 +- ...ionExpression.kt => NewArrayExpression.kt} | 7 +- .../statements/expressions/RangeExpression.kt | 2 +- ...redReferenceExpression.kt => Reference.kt} | 13 +- ...onExpression.kt => SubscriptExpression.kt} | 13 +- .../statements/expressions/UnaryOperator.kt | 4 +- .../cpg/graph/types/HasSecondaryTypeEdge.kt | 7 +- .../aisec/cpg/graph/types/HasType.kt | 5 +- .../de/fraunhofer/aisec/cpg/helpers/Util.kt | 8 +- .../aisec/cpg/passes/CXXCallResolverHelper.kt | 34 +-- .../aisec/cpg/passes/CallResolver.kt | 36 ++-- .../cpg/passes/ControlDependenceGraphPass.kt | 24 ++- .../cpg/passes/ControlFlowSensitiveDFGPass.kt | 54 +++-- .../de/fraunhofer/aisec/cpg/passes/DFGPass.kt | 32 ++- .../cpg/passes/EvaluationOrderGraphPass.kt | 42 ++-- .../aisec/cpg/passes/ImportResolver.kt | 6 +- .../aisec/cpg/passes/SymbolResolverPass.kt | 4 +- .../cpg/passes/TemplateCallResolverHelper.kt | 35 ++- .../aisec/cpg/passes/TypeHierarchyResolver.kt | 10 +- .../aisec/cpg/passes/VariableUsageResolver.kt | 44 ++-- .../aisec/cpg/passes/inference/Inference.kt | 10 +- .../cpg/reflect-config.json | 14 +- .../fraunhofer/aisec/cpg/graph/FluentTest.kt | 7 +- .../aisec/cpg/graph/ShortcutsTest.kt | 67 +++--- .../fraunhofer/aisec/cpg/graph/WalkerTest.kt | 4 +- .../declarations/TupleDeclarationTest.kt | 12 +- .../expressions/AssignExpressionTest.kt | 15 +- .../cpg/graph/types/TypePropagationTest.kt | 14 +- .../passes/ControlDependenceGraphPassTest.kt | 6 +- .../de/fraunhofer/aisec/cpg/passes/DFGTest.kt | 18 +- .../aisec/cpg/passes/UnresolvedDFGPassTest.kt | 12 +- .../aisec/cpg/passes/VariableResolverTest.kt | 6 +- .../cpg/passes/scopes/ScopeManagerTest.kt | 7 +- .../aisec/cpg/processing/VisitorTest.kt | 23 +- .../de/fraunhofer/aisec/cpg/TestUtils.kt | 24 +-- .../cpg/frontends/cxx/CXXLanguageFrontend.kt | 2 +- .../cpg/frontends/cxx/DeclarationHandler.kt | 26 +-- .../cpg/frontends/cxx/DeclaratorHandler.kt | 10 +- .../cpg/frontends/cxx/ExpressionHandler.kt | 38 ++-- .../cxx/ParameterDeclarationHandler.kt | 15 +- .../cpg/frontends/cxx/StatementHandler.kt | 38 ++-- .../aisec/cpg/enhancements/EOGTest.kt | 27 +-- .../templates/ClassTemplateTest.kt | 136 ++++++------ .../templates/FunctionTemplateTest.kt | 24 +-- .../VariableResolverCppTest.kt | 8 +- .../aisec/cpg/frontends/cxx/CXXIncludeTest.kt | 4 +- .../frontends/cxx/CXXLanguageFrontendTest.kt | 114 +++++----- .../cxx/CXXSymbolConfigurationTest.kt | 4 +- .../aisec/cpg/passes/CallResolverTest.kt | 4 +- cpg-language-go/src/main/golang/go.sum | 23 ++ .../frontends/golang/DeclarationHandler.kt | 6 +- .../cpg/frontends/golang/ExpressionHandler.kt | 27 +-- .../cpg/frontends/golang/StatementHandler.kt | 11 +- .../aisec/cpg/passes/GoExtraPass.kt | 6 +- .../cpg/frontends/golang/DeclarationTest.kt | 4 +- .../cpg/frontends/golang/ExpressionTest.kt | 10 +- .../golang/GoLanguageFrontendTest.kt | 42 ++-- .../cpg/frontends/golang/StatementTest.kt | 4 +- .../cpg/frontends/java/DeclarationHandler.kt | 24 ++- .../cpg/frontends/java/ExpressionHandler.kt | 101 +++++---- .../cpg/frontends/java/StatementHandler.kt | 121 +++++++---- .../cpg/passes/JavaCallResolverHelper.kt | 12 +- .../aisec/cpg/enhancements/EOGTest.kt | 4 +- .../cpg/enhancements/calls/SuperCallTest.kt | 6 +- .../aisec/cpg/frontends/FrontendHelperTest.kt | 14 +- .../java/JavaLanguageFrontendTest.kt | 23 +- .../aisec/cpg/frontends/java/LambdaTest.kt | 23 +- .../cpg/frontends/java/StaticImportsTest.kt | 6 +- .../aisec/cpg/helpers/CommentMatcherTest.kt | 10 +- .../ExplicitConstructorInvocationStmt.java | 4 +- .../cpg/frontends/llvm/DeclarationHandler.kt | 12 +- .../cpg/frontends/llvm/ExpressionHandler.kt | 32 ++- .../frontends/llvm/LLVMIRLanguageFrontend.kt | 7 +- .../cpg/frontends/llvm/StatementHandler.kt | 85 ++++---- .../aisec/cpg/passes/CompressLLVMPass.kt | 20 +- .../llvm/LLVMIRLanguageFrontendTest.kt | 149 ++++++------- .../src/main/python/CPGPython/__init__.py | 2 +- .../src/main/python/CPGPython/_expressions.py | 8 +- .../src/main/python/CPGPython/_misc.py | 2 +- .../src/main/python/CPGPython/_statements.py | 34 +-- .../frontends/python/PythonFrontendTest.kt | 133 ++++++------ .../typescript/DeclarationHandler.kt | 2 +- .../frontends/typescript/ExpressionHandler.kt | 11 +- .../frontends/typescript/StatementHandler.kt | 8 +- .../TypescriptLanguageFrontendTest.kt | 16 +- docs/docs/CPG/specs/dfg.md | 50 ++--- docs/docs/CPG/specs/eog.md | 38 ++-- docs/docs/CPG/specs/graph.md | 118 +++++------ docs/docs/CPG/specs/schema.md | 118 +++++------ docs/docs/Contributing/index.md | 2 +- docs/docs/GettingStarted/query.md | 2 +- docs/docs/GettingStarted/shortcuts.md | 2 +- tutorial.md | 44 ++-- 148 files changed, 1651 insertions(+), 1836 deletions(-) rename cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/{ParamVariableDeclaration.kt => ParameterDeclaration.kt} (92%) rename cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/{ClassTemplateDeclaration.kt => RecordTemplateDeclaration.kt} (90%) rename cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/{TypeParamDeclaration.kt => TypeParameterDeclaration.kt} (93%) rename cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/{UsingDirective.kt => UsingDeclaration.kt} (88%) rename cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/{CompoundStatement.kt => expressions/Block.kt} (88%) delete mode 100644 cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/CompoundStatementExpression.kt rename cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/{ExplicitConstructorInvocation.kt => ConstructorCallExpression.kt} (92%) rename cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/{ArrayCreationExpression.kt => NewArrayExpression.kt} (92%) rename cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/{DeclaredReferenceExpression.kt => Reference.kt} (92%) rename cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/{ArraySubscriptionExpression.kt => SubscriptExpression.kt} (92%) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 13e89b56f7..d26b60c13d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -79,7 +79,7 @@ On some edges, we want to store additional information (e.g., if a `EOG` node is /** The list of function parameters. */ @Relationship(value = "PARAMETERS", direction = Relationship.Direction.OUTGOING) @field:SubGraph("AST") -var parameterEdges = mutableListOf>() +var parameterEdges = mutableListOf>() /** Virtual property for accessing [parameterEdges] without property edges. */ var parameters by PropertyEdgeDelegate(FunctionDeclaration::parameterEdges) diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluator.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluator.kt index f9e44f0eae..ef9560d4e4 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluator.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluator.kt @@ -73,17 +73,17 @@ class MultiValueEvaluator : ValueEvaluator() { is FieldDeclaration -> { return evaluateInternal(node.initializer, depth + 1) } - is ArrayCreationExpression -> return evaluateInternal(node.initializer, depth + 1) + is NewArrayExpression -> return evaluateInternal(node.initializer, depth + 1) is VariableDeclaration -> return evaluateInternal(node.initializer, depth + 1) // For a literal, we can just take its value, and we are finished is Literal<*> -> return node.value - is DeclaredReferenceExpression -> return handleDeclaredReferenceExpression(node, depth) + is Reference -> return handleReference(node, depth) is UnaryOperator -> return handleUnaryOp(node, depth) is AssignExpression -> return handleAssignExpression(node, depth) is BinaryOperator -> return handleBinaryOperator(node, depth) // Casts are just a wrapper in this case, we are interested in the inner expression is CastExpression -> return this.evaluateInternal(node.expression, depth + 1) - is ArraySubscriptionExpression -> return handleArraySubscriptionExpression(node, depth) + is SubscriptExpression -> return handleSubscriptExpression(node, depth) // While we are not handling different paths of variables with If statements, we can // easily be partly path-sensitive in a conditional expression is ConditionalExpression -> return handleConditionalExpression(node, depth) @@ -170,8 +170,8 @@ class MultiValueEvaluator : ValueEvaluator() { override fun handleConditionalExpression(expr: ConditionalExpression, depth: Int): Any { val result = mutableSetOf() - val elseResult = evaluateInternal(expr.elseExpr, depth + 1) - val thenResult = evaluateInternal(expr.thenExpr, depth + 1) + val elseResult = evaluateInternal(expr.elseExpression, depth + 1) + val thenResult = evaluateInternal(expr.thenExpression, depth + 1) if (thenResult is Collection<*>) result.addAll(thenResult) else result.add(thenResult) if (elseResult is Collection<*>) result.addAll(elseResult) else result.add(elseResult) return result @@ -217,10 +217,7 @@ class MultiValueEvaluator : ValueEvaluator() { * In contrast to the implementation of [ValueEvaluator], this one can handle more than one * value. */ - override fun handleDeclaredReferenceExpression( - expr: DeclaredReferenceExpression, - depth: Int - ): Collection { + override fun handleReference(expr: Reference, depth: Int): Collection { // For a reference, we are interested in its last assignment into the reference // denoted by the previous DFG edge. We need to filter out any self-references for READWRITE // references. @@ -282,10 +279,7 @@ class MultiValueEvaluator : ValueEvaluator() { forStatement.iterationStatement == node.astParent } - private fun handleSimpleLoopVariable( - expr: DeclaredReferenceExpression, - depth: Int - ): Collection { + private fun handleSimpleLoopVariable(expr: Reference, depth: Int): Collection { val loop = expr.prevDFG.firstOrNull { e -> e.astParent is ForStatement }?.astParent as? ForStatement @@ -298,13 +292,13 @@ class MultiValueEvaluator : ValueEvaluator() { val cond = loop.condition as BinaryOperator val result = mutableSetOf() var lhs = - if ((cond.lhs as? DeclaredReferenceExpression)?.refersTo == expr.refersTo) { + if ((cond.lhs as? Reference)?.refersTo == expr.refersTo) { loopVar } else { evaluateInternal(cond.lhs, depth + 1) } var rhs = - if ((cond.rhs as? DeclaredReferenceExpression)?.refersTo == expr.refersTo) { + if ((cond.rhs as? Reference)?.refersTo == expr.refersTo) { loopVar } else { evaluateInternal(cond.rhs, depth + 1) @@ -323,17 +317,16 @@ class MultiValueEvaluator : ValueEvaluator() { is AssignExpression -> { if ( loopOp.operatorCode == "=" && - (loopOp.lhs.singleOrNull() as? DeclaredReferenceExpression) - ?.refersTo == expr.refersTo && + (loopOp.lhs.singleOrNull() as? Reference)?.refersTo == + expr.refersTo && loopOp.rhs.singleOrNull() is BinaryOperator ) { // Assignment to the variable, take the rhs and see if it's also a // binary operator val opLhs = if ( - ((loopOp.rhs())?.lhs - as? DeclaredReferenceExpression) - ?.refersTo == expr.refersTo + ((loopOp.rhs())?.lhs as? Reference)?.refersTo == + expr.refersTo ) { loopVar } else { @@ -341,9 +334,8 @@ class MultiValueEvaluator : ValueEvaluator() { } val opRhs = if ( - ((loopOp.rhs())?.rhs - as? DeclaredReferenceExpression) - ?.refersTo == expr.refersTo + ((loopOp.rhs())?.rhs as? Reference)?.refersTo == + expr.refersTo ) { loopVar } else { @@ -359,19 +351,13 @@ class MultiValueEvaluator : ValueEvaluator() { // No idea what this is but it's a binary op... val opLhs = - if ( - (loopOp.lhs as? DeclaredReferenceExpression)?.refersTo == - expr.refersTo - ) { + if ((loopOp.lhs as? Reference)?.refersTo == expr.refersTo) { loopVar } else { loopOp.lhs } val opRhs = - if ( - (loopOp.rhs as? DeclaredReferenceExpression)?.refersTo == - expr.refersTo - ) { + if ((loopOp.rhs as? Reference)?.refersTo == expr.refersTo) { loopVar } else { loopOp.rhs @@ -380,10 +366,7 @@ class MultiValueEvaluator : ValueEvaluator() { } is UnaryOperator -> { computeUnaryOpEffect( - if ( - (loopOp.input as? DeclaredReferenceExpression)?.refersTo == - expr.refersTo - ) { + if ((loopOp.input as? Reference)?.refersTo == expr.refersTo) { loopVar } else { loopOp.input @@ -400,10 +383,10 @@ class MultiValueEvaluator : ValueEvaluator() { return result } - if ((cond.lhs as? DeclaredReferenceExpression)?.refersTo == expr.refersTo) { + if ((cond.lhs as? Reference)?.refersTo == expr.refersTo) { lhs = loopVar } - if ((cond.rhs as? DeclaredReferenceExpression)?.refersTo == expr.refersTo) { + if ((cond.rhs as? Reference)?.refersTo == expr.refersTo) { rhs = loopVar } comparisonResult = computeBinaryOpEffect(lhs, rhs, cond) diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/SizeEvaluator.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/SizeEvaluator.kt index b64fef2989..2b802201ec 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/SizeEvaluator.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/SizeEvaluator.kt @@ -51,17 +51,17 @@ class SizeEvaluator : ValueEvaluator() { node?.let { this.path += it } return when (node) { - is ArrayCreationExpression -> + is NewArrayExpression -> if (node.initializer != null) { evaluateInternal(node.initializer, depth + 1) } else { evaluateInternal(node.dimensions.firstOrNull(), depth + 1) } is VariableDeclaration -> evaluateInternal(node.initializer, depth + 1) - is DeclaredReferenceExpression -> evaluateInternal(node.refersTo, depth + 1) + is Reference -> evaluateInternal(node.refersTo, depth + 1) // For a literal, we can just take its value, and we are finished is Literal<*> -> if (node.value is String) (node.value as String).length else node.value - is ArraySubscriptionExpression -> evaluate(node.arrayExpression) + is SubscriptExpression -> evaluate(node.arrayExpression) else -> cannotEvaluate(node, this) } } diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluator.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluator.kt index 477927414d..04ddcdd46c 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluator.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluator.kt @@ -87,16 +87,16 @@ open class ValueEvaluator( node?.let { this.path += it } when (node) { - is ArrayCreationExpression -> return evaluateInternal(node.initializer, depth + 1) + is NewArrayExpression -> return evaluateInternal(node.initializer, depth + 1) is VariableDeclaration -> return evaluateInternal(node.initializer, depth + 1) // For a literal, we can just take its value, and we are finished is Literal<*> -> return node.value - is DeclaredReferenceExpression -> return handleDeclaredReferenceExpression(node, depth) + is Reference -> return handleReference(node, depth) is UnaryOperator -> return handleUnaryOp(node, depth) is BinaryOperator -> return handleBinaryOperator(node, depth) // Casts are just a wrapper in this case, we are interested in the inner expression is CastExpression -> return this.evaluateInternal(node.expression, depth + 1) - is ArraySubscriptionExpression -> return handleArraySubscriptionExpression(node, depth) + is SubscriptExpression -> return handleSubscriptExpression(node, depth) // While we are not handling different paths of variables with If statements, we can // easily be partly path-sensitive in a conditional expression is ConditionalExpression -> return handleConditionalExpression(node, depth) @@ -277,12 +277,8 @@ open class ValueEvaluator( * basically the case if the base of the subscript expression is a list of [KeyValueExpression] * s. */ - protected fun handleArraySubscriptionExpression( - expr: ArraySubscriptionExpression, - depth: Int - ): Any? { - val array = - (expr.arrayExpression as? DeclaredReferenceExpression)?.refersTo as? VariableDeclaration + protected fun handleSubscriptExpression(expr: SubscriptExpression, depth: Int): Any? { + val array = (expr.arrayExpression as? Reference)?.refersTo as? VariableDeclaration val ile = array?.initializer as? InitializerListExpression ile?.let { @@ -301,7 +297,7 @@ open class ValueEvaluator( return (array.initializer as Literal<*>).value } - if (expr.arrayExpression is ArraySubscriptionExpression) { + if (expr.arrayExpression is SubscriptExpression) { return evaluateInternal(expr.arrayExpression, depth + 1) } @@ -315,9 +311,9 @@ open class ValueEvaluator( val rhs = evaluateInternal((expr.condition as? BinaryOperator)?.rhs, depth) return if (lhs == rhs) { - evaluateInternal(expr.thenExpr, depth + 1) + evaluateInternal(expr.thenExpression, depth + 1) } else { - evaluateInternal(expr.elseExpr, depth + 1) + evaluateInternal(expr.elseExpression, depth + 1) } } @@ -328,10 +324,7 @@ open class ValueEvaluator( * Tries to compute the constant value of a reference. It therefore checks the incoming data * flow edges. */ - protected open fun handleDeclaredReferenceExpression( - expr: DeclaredReferenceExpression, - depth: Int - ): Any? { + protected open fun handleReference(expr: Reference, depth: Int): Any? { // For a reference, we are interested into its last assignment into the reference // denoted by the previous DFG edge. We need to filter out any self-references for READWRITE // references. @@ -358,10 +351,7 @@ open class ValueEvaluator( * If a reference has READWRITE access, ignore any "self-references", e.g. from a * plus/minus/div/times-assign or a plusplus/minusminus, etc. */ - protected fun filterSelfReferences( - ref: DeclaredReferenceExpression, - inDFG: List - ): List { + protected fun filterSelfReferences(ref: Reference, inDFG: List): List { var list = inDFG // The ops +=, -=, ... and ++, -- have in common that we see the ref twice: Once to reach diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/fsm/DFAOrderEvaluator.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/fsm/DFAOrderEvaluator.kt index 60e0f9d065..1845eee579 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/fsm/DFAOrderEvaluator.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/fsm/DFAOrderEvaluator.kt @@ -27,15 +27,15 @@ package de.fraunhofer.aisec.cpg.analysis.fsm import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.declarations.Declaration -import de.fraunhofer.aisec.cpg.graph.declarations.ParamVariableDeclaration +import de.fraunhofer.aisec.cpg.graph.declarations.ParameterDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration import de.fraunhofer.aisec.cpg.graph.edge.Properties import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge import de.fraunhofer.aisec.cpg.graph.statements.ReturnStatement import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.ConstructExpression -import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberCallExpression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Reference import de.fraunhofer.aisec.cpg.passes.astParent import org.slf4j.Logger import org.slf4j.LoggerFactory @@ -285,17 +285,15 @@ open class DFAOrderEvaluator( private fun callUsesInterestingBase(node: CallExpression, eogPath: String): List { val allUsedBases = node.arguments - .map { arg -> (arg as? DeclaredReferenceExpression)?.refersTo } + .map { arg -> (arg as? Reference)?.refersTo } .filter { arg -> arg != null && consideredBases.contains(arg) } .toMutableList() if ( node is MemberCallExpression && - node.base is DeclaredReferenceExpression && - consideredBases.contains( - (node.base as DeclaredReferenceExpression).refersTo as Declaration - ) + node.base is Reference && + consideredBases.contains((node.base as Reference).refersTo as Declaration) ) { - allUsedBases.add((node.base as DeclaredReferenceExpression).refersTo) + allUsedBases.add((node.base as Reference).refersTo) } return allUsedBases.map { "$eogPath|${it?.name}.$it" } @@ -346,7 +344,7 @@ open class DFAOrderEvaluator( // the end. var base = getBaseOfNode(node) - if (base is DeclaredReferenceExpression && base.refersTo != null) { + if (base is Reference && base.refersTo != null) { base = base.refersTo } @@ -355,7 +353,7 @@ open class DFAOrderEvaluator( // the different paths of execution which both can use the same base. val prefixedBase = "$eogPath|${base.name}.$base" - if (base is ParamVariableDeclaration) { + if (base is ParameterDeclaration) { // The base was the parameter of the function? We have an inter-procedural flow! interproceduralFlows[prefixedBase] = true } @@ -393,7 +391,7 @@ open class DFAOrderEvaluator( var node: Node = list.first() // if the node refers to another node, return the node it refers to - (node as? DeclaredReferenceExpression)?.refersTo?.let { node = it } + (node as? Reference)?.refersTo?.let { node = it } return node } @@ -407,7 +405,7 @@ open class DFAOrderEvaluator( private fun Node.getSuitableDFGTarget(): Node? { return this.nextDFG .filter { - it is DeclaredReferenceExpression || + it is Reference || it is ReturnStatement || it is ConstructExpression || it is VariableDeclaration diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/EvaluateExtensions.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/EvaluateExtensions.kt index 6487c872fb..ee820c6496 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/EvaluateExtensions.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/EvaluateExtensions.kt @@ -27,8 +27,8 @@ package de.fraunhofer.aisec.cpg.graph import de.fraunhofer.aisec.cpg.analysis.ValueEvaluator import de.fraunhofer.aisec.cpg.graph.declarations.Declaration -import de.fraunhofer.aisec.cpg.graph.statements.expressions.ArrayCreationExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.NewArrayExpression fun Expression.evaluate(evaluator: ValueEvaluator = ValueEvaluator()): Any? { return evaluator.evaluate(this) @@ -38,7 +38,7 @@ fun Declaration.evaluate(evaluator: ValueEvaluator = ValueEvaluator()): Any? { return evaluator.evaluate(this) } -val ArrayCreationExpression.capacity: Int +val NewArrayExpression.capacity: Int get() { return dimensions.first().evaluate() as Int } diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt index 2b89f23d97..89aa74fa0b 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt @@ -359,12 +359,14 @@ fun allNonLiteralsFromFlowTo(from: Node, to: Node, allPaths: List>): it.any { it2 -> if (it2 is AssignmentHolder) { it2.assignments.any { assign -> - val prevMemberFrom = (from as? MemberExpression)?.prevDFG - val nextMemberTo = (assign.target as? MemberExpression)?.nextDFG + val prevMemberFromExpr = (from as? MemberExpression)?.prevDFG + val nextMemberToExpr = (assign.target as? MemberExpression)?.nextDFG assign.target == from || - prevMemberFrom != null && - nextMemberTo != null && - prevMemberFrom.any { it3 -> nextMemberTo.contains(it3) } + prevMemberFromExpr != null && + nextMemberToExpr != null && + prevMemberFromExpr.any { it3 -> + nextMemberToExpr.contains(it3) + } } } else { false diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/query/README.md b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/query/README.md index 9c25867dd7..e1a13f9a09 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/query/README.md +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/query/README.md @@ -21,7 +21,7 @@ all (==> false) -------- Starting at CallExpression[name=memcpy,location=vulnerable.cpp(3:5-3:38),type=UNKNOWN,base=]: 5 > 11 (==> false) ------------------------ - sizeof(DeclaredReferenceExpression[DeclaredReferenceExpression[name=array,location=vulnerable.cpp(3:12-3:17),type=PointerType[name=char[]]],refersTo=VariableDeclaration[name=array,location=vulnerable.cpp(2:10-2:28),initializer=Literal[location=vulnerable.cpp(2:21-2:28),type=PointerType[name=char[]],value=hello]]]) (==> 5) + sizeof(Reference[Reference[name=array,location=vulnerable.cpp(3:12-3:17),type=PointerType[name=char[]]],refersTo=VariableDeclaration[name=array,location=vulnerable.cpp(2:10-2:28),initializer=Literal[location=vulnerable.cpp(2:21-2:28),type=PointerType[name=char[]],value=hello]]]) (==> 5) ---------------------------------------- ------------------------ sizeof(Literal[location=vulnerable.cpp(3:19-3:32),type=PointerType[name=char[]],value=Hello world]) (==> 11) diff --git a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluatorTest.kt b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluatorTest.kt index fb925ed067..ac3ac136c0 100644 --- a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluatorTest.kt +++ b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluatorTest.kt @@ -30,10 +30,10 @@ import de.fraunhofer.aisec.cpg.frontends.TestHandler import de.fraunhofer.aisec.cpg.frontends.TestLanguageFrontend import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration -import de.fraunhofer.aisec.cpg.graph.statements.CompoundStatement import de.fraunhofer.aisec.cpg.graph.statements.DeclarationStatement import de.fraunhofer.aisec.cpg.graph.statements.ForStatement import de.fraunhofer.aisec.cpg.graph.statements.expressions.AssignExpression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Block import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression import de.fraunhofer.aisec.cpg.passes.EdgeCachePass import java.nio.file.Path @@ -211,8 +211,7 @@ class MultiValueEvaluatorTest { assertNotNull(forLoop) val evaluator = MultiValueEvaluator() - val iVarList = - ((forLoop.statement as CompoundStatement).statements[0] as AssignExpression).rhs + val iVarList = ((forLoop.statement as Block).statements[0] as AssignExpression).rhs assertEquals(1, iVarList.size) val iVar = iVarList.first() val value = evaluator.evaluate(iVar) as ConcreteNumberSet @@ -228,7 +227,7 @@ class MultiValueEvaluatorTest { val three = newLiteral(3, primitiveType("int")) val four = newLiteral(4, primitiveType("int")) - val ref = newDeclaredReferenceExpression("a") + val ref = newReference("a") ref.prevDFG = mutableSetOf(three, four) val neg = newUnaryOperator("-", false, true) diff --git a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/SizeEvaluatorTest.kt b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/SizeEvaluatorTest.kt index 2f470c3695..0c2601be82 100644 --- a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/SizeEvaluatorTest.kt +++ b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/SizeEvaluatorTest.kt @@ -32,12 +32,12 @@ import de.fraunhofer.aisec.cpg.graph.byNameOrNull import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration import de.fraunhofer.aisec.cpg.graph.invoke -import de.fraunhofer.aisec.cpg.graph.statements.CompoundStatement import de.fraunhofer.aisec.cpg.graph.statements.DeclarationStatement import de.fraunhofer.aisec.cpg.graph.statements.ForStatement -import de.fraunhofer.aisec.cpg.graph.statements.expressions.ArraySubscriptionExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.AssignExpression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Block import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.SubscriptExpression import java.nio.file.Path import kotlin.test.Test import kotlin.test.assertEquals @@ -107,8 +107,8 @@ class SizeEvaluatorTest { assertNotNull(forLoop) val subscriptExpr = - ((forLoop.statement as CompoundStatement).statements[0] as AssignExpression).lhs< - ArraySubscriptionExpression + ((forLoop.statement as Block).statements[0] as AssignExpression).lhs< + SubscriptExpression >() value = evaluator.evaluate(subscriptExpr) as Int diff --git a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/fsm/ComplexDFAOrderEvaluationTest.kt b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/fsm/ComplexDFAOrderEvaluationTest.kt index d53b8ffca0..90aa4825a5 100644 --- a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/fsm/ComplexDFAOrderEvaluationTest.kt +++ b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/fsm/ComplexDFAOrderEvaluationTest.kt @@ -36,8 +36,9 @@ import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration import de.fraunhofer.aisec.cpg.graph.followNextEOG import de.fraunhofer.aisec.cpg.graph.statements.* +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Block import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression -import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Reference import de.fraunhofer.aisec.cpg.passes.EdgeCachePass import de.fraunhofer.aisec.cpg.passes.UnreachableEOGPass import java.nio.file.Path @@ -103,10 +104,10 @@ class ComplexDFAOrderEvaluationTest { val consideredDecl = mutableSetOf(p1Decl.declarations[0]) val nodesToOp = mutableMapOf>() - nodesToOp[(functionOk.body as CompoundStatement).statements[1]] = setOf("create()") - nodesToOp[(functionOk.body as CompoundStatement).statements[2]] = setOf("init()") - nodesToOp[(functionOk.body as CompoundStatement).statements[3]] = setOf("start()") - nodesToOp[(functionOk.body as CompoundStatement).statements[4]] = setOf("finish()") + nodesToOp[(functionOk.body as Block).statements[1]] = setOf("create()") + nodesToOp[(functionOk.body as Block).statements[2]] = setOf("init()") + nodesToOp[(functionOk.body as Block).statements[3]] = setOf("start()") + nodesToOp[(functionOk.body as Block).statements[4]] = setOf("finish()") val orderEvaluator = DFAOrderEvaluator(dfa, consideredDecl, nodesToOp) val everythingOk = orderEvaluator.evaluateOrder(p1Decl) @@ -126,11 +127,11 @@ class ComplexDFAOrderEvaluationTest { val consideredDecl = mutableSetOf(p1Decl.declarations[0]) val nodesToOp = mutableMapOf>() - nodesToOp[(functionOk.body as CompoundStatement).statements[1]] = setOf("create()") - nodesToOp[(functionOk.body as CompoundStatement).statements[2]] = setOf("init()") - nodesToOp[(functionOk.body as CompoundStatement).statements[3]] = setOf("start()") - nodesToOp[(functionOk.body as CompoundStatement).statements[4]] = setOf("process()") - nodesToOp[(functionOk.body as CompoundStatement).statements[5]] = setOf("finish()") + nodesToOp[(functionOk.body as Block).statements[1]] = setOf("create()") + nodesToOp[(functionOk.body as Block).statements[2]] = setOf("init()") + nodesToOp[(functionOk.body as Block).statements[3]] = setOf("start()") + nodesToOp[(functionOk.body as Block).statements[4]] = setOf("process()") + nodesToOp[(functionOk.body as Block).statements[5]] = setOf("finish()") val orderEvaluator = DFAOrderEvaluator(dfa, consideredDecl, nodesToOp) val everythingOk = orderEvaluator.evaluateOrder(p1Decl) @@ -150,12 +151,12 @@ class ComplexDFAOrderEvaluationTest { val consideredDecl = mutableSetOf(p1Decl.declarations[0]) val nodesToOp = mutableMapOf>() - nodesToOp[(functionOk.body as CompoundStatement).statements[1]] = setOf("create()") - nodesToOp[(functionOk.body as CompoundStatement).statements[2]] = setOf("init()") - nodesToOp[(functionOk.body as CompoundStatement).statements[3]] = setOf("start()") - nodesToOp[(functionOk.body as CompoundStatement).statements[4]] = setOf("process()") - nodesToOp[(functionOk.body as CompoundStatement).statements[5]] = setOf("finish()") - nodesToOp[(functionOk.body as CompoundStatement).statements[6]] = setOf("reset()") + nodesToOp[(functionOk.body as Block).statements[1]] = setOf("create()") + nodesToOp[(functionOk.body as Block).statements[2]] = setOf("init()") + nodesToOp[(functionOk.body as Block).statements[3]] = setOf("start()") + nodesToOp[(functionOk.body as Block).statements[4]] = setOf("process()") + nodesToOp[(functionOk.body as Block).statements[5]] = setOf("finish()") + nodesToOp[(functionOk.body as Block).statements[6]] = setOf("reset()") val orderEvaluator = DFAOrderEvaluator(dfa, consideredDecl, nodesToOp) val everythingOk = orderEvaluator.evaluateOrder(p1Decl) @@ -175,14 +176,14 @@ class ComplexDFAOrderEvaluationTest { val consideredDecl = mutableSetOf(p2Decl.declarations[0]) val nodesToOp = mutableMapOf>() - nodesToOp[(functionOk.body as CompoundStatement).statements[1]] = setOf("create()") - nodesToOp[(functionOk.body as CompoundStatement).statements[2]] = setOf("init()") - nodesToOp[(functionOk.body as CompoundStatement).statements[3]] = setOf("start()") - nodesToOp[(functionOk.body as CompoundStatement).statements[4]] = setOf("process()") - nodesToOp[(functionOk.body as CompoundStatement).statements[5]] = setOf("process()") - nodesToOp[(functionOk.body as CompoundStatement).statements[6]] = setOf("process()") - nodesToOp[(functionOk.body as CompoundStatement).statements[7]] = setOf("process()") - nodesToOp[(functionOk.body as CompoundStatement).statements[8]] = setOf("finish()") + nodesToOp[(functionOk.body as Block).statements[1]] = setOf("create()") + nodesToOp[(functionOk.body as Block).statements[2]] = setOf("init()") + nodesToOp[(functionOk.body as Block).statements[3]] = setOf("start()") + nodesToOp[(functionOk.body as Block).statements[4]] = setOf("process()") + nodesToOp[(functionOk.body as Block).statements[5]] = setOf("process()") + nodesToOp[(functionOk.body as Block).statements[6]] = setOf("process()") + nodesToOp[(functionOk.body as Block).statements[7]] = setOf("process()") + nodesToOp[(functionOk.body as Block).statements[8]] = setOf("finish()") val orderEvaluator = DFAOrderEvaluator(dfa, consideredDecl, nodesToOp) val everythingOk = orderEvaluator.evaluateOrder(p2Decl) @@ -202,14 +203,14 @@ class ComplexDFAOrderEvaluationTest { val consideredDecl = mutableSetOf(p3Decl.declarations[0]) val nodesToOp = mutableMapOf>() - nodesToOp[(functionOk.body as CompoundStatement).statements[1]] = setOf("create()") - nodesToOp[(functionOk.body as CompoundStatement).statements[2]] = setOf("init()") - nodesToOp[(functionOk.body as CompoundStatement).statements[3]] = setOf("start()") - nodesToOp[(functionOk.body as CompoundStatement).statements[4]] = setOf("process()") - nodesToOp[(functionOk.body as CompoundStatement).statements[5]] = setOf("finish()") - nodesToOp[(functionOk.body as CompoundStatement).statements[6]] = setOf("start()") - nodesToOp[(functionOk.body as CompoundStatement).statements[7]] = setOf("process()") - nodesToOp[(functionOk.body as CompoundStatement).statements[8]] = setOf("finish()") + nodesToOp[(functionOk.body as Block).statements[1]] = setOf("create()") + nodesToOp[(functionOk.body as Block).statements[2]] = setOf("init()") + nodesToOp[(functionOk.body as Block).statements[3]] = setOf("start()") + nodesToOp[(functionOk.body as Block).statements[4]] = setOf("process()") + nodesToOp[(functionOk.body as Block).statements[5]] = setOf("finish()") + nodesToOp[(functionOk.body as Block).statements[6]] = setOf("start()") + nodesToOp[(functionOk.body as Block).statements[7]] = setOf("process()") + nodesToOp[(functionOk.body as Block).statements[8]] = setOf("finish()") val orderEvaluator = DFAOrderEvaluator(dfa, consideredDecl, nodesToOp) val everythingOk = orderEvaluator.evaluateOrder(p3Decl) @@ -229,15 +230,15 @@ class ComplexDFAOrderEvaluationTest { val consideredDecl = mutableSetOf(p3Decl.declarations[0]) val nodes = mutableMapOf>() - nodes[(functionOk.body as CompoundStatement).statements[1]] = setOf("create()") - nodes[(functionOk.body as CompoundStatement).statements[2]] = setOf("init()") - nodes[(functionOk.body as CompoundStatement).statements[3]] = setOf("start()") - nodes[(functionOk.body as CompoundStatement).statements[4]] = setOf("process()") - nodes[(functionOk.body as CompoundStatement).statements[5]] = setOf("finish()") - nodes[(functionOk.body as CompoundStatement).statements[6]] = setOf("start()") - nodes[(functionOk.body as CompoundStatement).statements[7]] = setOf("process()") - nodes[(functionOk.body as CompoundStatement).statements[8]] = setOf("finish()") - nodes[(functionOk.body as CompoundStatement).statements[9]] = setOf("reset()") + nodes[(functionOk.body as Block).statements[1]] = setOf("create()") + nodes[(functionOk.body as Block).statements[2]] = setOf("init()") + nodes[(functionOk.body as Block).statements[3]] = setOf("start()") + nodes[(functionOk.body as Block).statements[4]] = setOf("process()") + nodes[(functionOk.body as Block).statements[5]] = setOf("finish()") + nodes[(functionOk.body as Block).statements[6]] = setOf("start()") + nodes[(functionOk.body as Block).statements[7]] = setOf("process()") + nodes[(functionOk.body as Block).statements[8]] = setOf("finish()") + nodes[(functionOk.body as Block).statements[9]] = setOf("reset()") val orderEvaluator = DFAOrderEvaluator(dfa, consideredDecl, nodes) val everythingOk = orderEvaluator.evaluateOrder(p3Decl) @@ -257,10 +258,10 @@ class ComplexDFAOrderEvaluationTest { val consideredDecl = mutableSetOf(p5Decl.declarations[0]) val nodesToOp = mutableMapOf>() - nodesToOp[(functionOk.body as CompoundStatement).statements[1]] = setOf("init()") - nodesToOp[(functionOk.body as CompoundStatement).statements[2]] = setOf("start()") - nodesToOp[(functionOk.body as CompoundStatement).statements[3]] = setOf("process()") - nodesToOp[(functionOk.body as CompoundStatement).statements[4]] = setOf("finish()") + nodesToOp[(functionOk.body as Block).statements[1]] = setOf("init()") + nodesToOp[(functionOk.body as Block).statements[2]] = setOf("start()") + nodesToOp[(functionOk.body as Block).statements[3]] = setOf("process()") + nodesToOp[(functionOk.body as Block).statements[4]] = setOf("finish()") val orderEvaluator = DFAOrderEvaluator(dfa, consideredDecl, nodesToOp) val everythingOk = orderEvaluator.evaluateOrder(p5Decl) @@ -280,18 +281,17 @@ class ComplexDFAOrderEvaluationTest { val consideredDecl = mutableSetOf(p6Decl.declarations[0]) val nodesToOp = mutableMapOf>() - nodesToOp[(functionOk.body as CompoundStatement).statements[1]] = setOf("create()") - nodesToOp[(functionOk.body as CompoundStatement).statements[2]] = setOf("init()") + nodesToOp[(functionOk.body as Block).statements[1]] = setOf("create()") + nodesToOp[(functionOk.body as Block).statements[2]] = setOf("init()") val thenBranch = - ((functionOk.body as CompoundStatement).statements[3] as? IfStatement)?.thenStatement - as? CompoundStatement + ((functionOk.body as Block).statements[3] as? IfStatement)?.thenStatement as? Block assertNotNull(thenBranch) nodesToOp[thenBranch.statements[0]] = setOf("start()") nodesToOp[thenBranch.statements[1]] = setOf("process()") nodesToOp[thenBranch.statements[2]] = setOf("finish()") - nodesToOp[(functionOk.body as CompoundStatement).statements[4]] = setOf("reset()") + nodesToOp[(functionOk.body as Block).statements[4]] = setOf("reset()") val orderEvaluator = DFAOrderEvaluator(dfa, consideredDecl, nodesToOp) val everythingOk = orderEvaluator.evaluateOrder(p6Decl) @@ -313,8 +313,7 @@ class ComplexDFAOrderEvaluationTest { val nodesToOp = mutableMapOf>() val loopBody = - ((functionOk.body as CompoundStatement).statements[1] as? WhileStatement)?.statement - as? CompoundStatement + ((functionOk.body as Block).statements[1] as? WhileStatement)?.statement as? Block assertNotNull(loopBody) nodesToOp[loopBody.statements[0]] = setOf("create()") nodesToOp[loopBody.statements[1]] = setOf("init()") @@ -322,7 +321,7 @@ class ComplexDFAOrderEvaluationTest { nodesToOp[loopBody.statements[3]] = setOf("process()") nodesToOp[loopBody.statements[4]] = setOf("finish()") - nodesToOp[(functionOk.body as CompoundStatement).statements[2]] = setOf("reset()") + nodesToOp[(functionOk.body as Block).statements[2]] = setOf("reset()") val orderEvaluator = DFAOrderEvaluator(dfa, consideredDecl, nodesToOp) val everythingOk = orderEvaluator.evaluateOrder(p6Decl) @@ -343,17 +342,16 @@ class ComplexDFAOrderEvaluationTest { val consideredDecl = mutableSetOf(p7Decl.declarations[0]) val nodesToOp = mutableMapOf>() - nodesToOp[(functionOk.body as CompoundStatement).statements[1]] = setOf("create()") - nodesToOp[(functionOk.body as CompoundStatement).statements[2]] = setOf("init()") + nodesToOp[(functionOk.body as Block).statements[1]] = setOf("create()") + nodesToOp[(functionOk.body as Block).statements[2]] = setOf("init()") val loopBody = - ((functionOk.body as CompoundStatement).statements[3] as? WhileStatement)?.statement - as? CompoundStatement + ((functionOk.body as Block).statements[3] as? WhileStatement)?.statement as? Block assertNotNull(loopBody) nodesToOp[loopBody.statements[0]] = setOf("start()") nodesToOp[loopBody.statements[1]] = setOf("process()") nodesToOp[loopBody.statements[2]] = setOf("finish()") - nodesToOp[(functionOk.body as CompoundStatement).statements[4]] = setOf("reset()") + nodesToOp[(functionOk.body as Block).statements[4]] = setOf("reset()") val orderEvaluator = DFAOrderEvaluator(dfa, consideredDecl, nodesToOp) val everythingOk = orderEvaluator.evaluateOrder(p7Decl) @@ -373,17 +371,16 @@ class ComplexDFAOrderEvaluationTest { val consideredDecl = mutableSetOf(p7Decl.declarations[0]) val nodesToOp = mutableMapOf>() - nodesToOp[(functionOk.body as CompoundStatement).statements[1]] = setOf("create()") - nodesToOp[(functionOk.body as CompoundStatement).statements[2]] = setOf("init()") + nodesToOp[(functionOk.body as Block).statements[1]] = setOf("create()") + nodesToOp[(functionOk.body as Block).statements[2]] = setOf("init()") val loopBody = - ((functionOk.body as CompoundStatement).statements[3] as? WhileStatement)?.statement - as? CompoundStatement + ((functionOk.body as Block).statements[3] as? WhileStatement)?.statement as? Block assertNotNull(loopBody) nodesToOp[loopBody.statements[0]] = setOf("start()") nodesToOp[loopBody.statements[1]] = setOf("process()") nodesToOp[loopBody.statements[2]] = setOf("finish()") - nodesToOp[(functionOk.body as CompoundStatement).statements[4]] = setOf("reset()") + nodesToOp[(functionOk.body as Block).statements[4]] = setOf("reset()") val orderEvaluator = DFAOrderEvaluator(dfa, consideredDecl, nodesToOp) val everythingOk = orderEvaluator.evaluateOrder(p7Decl) @@ -403,20 +400,19 @@ class ComplexDFAOrderEvaluationTest { val consideredDecl = mutableSetOf(p8Decl.declarations[0]) val nodesToOp = mutableMapOf>() - nodesToOp[(functionOk.body as CompoundStatement).statements[1]] = setOf("create()") - nodesToOp[(functionOk.body as CompoundStatement).statements[2]] = setOf("init()") - nodesToOp[(functionOk.body as CompoundStatement).statements[3]] = setOf("start()") - nodesToOp[(functionOk.body as CompoundStatement).statements[4]] = setOf("process()") - nodesToOp[(functionOk.body as CompoundStatement).statements[5]] = setOf("finish()") + nodesToOp[(functionOk.body as Block).statements[1]] = setOf("create()") + nodesToOp[(functionOk.body as Block).statements[2]] = setOf("init()") + nodesToOp[(functionOk.body as Block).statements[3]] = setOf("start()") + nodesToOp[(functionOk.body as Block).statements[4]] = setOf("process()") + nodesToOp[(functionOk.body as Block).statements[5]] = setOf("finish()") val loopBody = - ((functionOk.body as CompoundStatement).statements[6] as? WhileStatement)?.statement - as? CompoundStatement + ((functionOk.body as Block).statements[6] as? WhileStatement)?.statement as? Block assertNotNull(loopBody) nodesToOp[loopBody.statements[0]] = setOf("start()") nodesToOp[loopBody.statements[1]] = setOf("process()") nodesToOp[loopBody.statements[2]] = setOf("finish()") - nodesToOp[(functionOk.body as CompoundStatement).statements[7]] = setOf("reset()") + nodesToOp[(functionOk.body as Block).statements[7]] = setOf("reset()") val orderEvaluator = DFAOrderEvaluator(dfa, consideredDecl, nodesToOp) val everythingOk = orderEvaluator.evaluateOrder(p8Decl) @@ -436,17 +432,15 @@ class ComplexDFAOrderEvaluationTest { val consideredDecl = mutableSetOf(p6Decl.declarations[0]) val nodesToOp = mutableMapOf>() - nodesToOp[(functionOk.body as CompoundStatement).statements[1]] = setOf("create()") - nodesToOp[(functionOk.body as CompoundStatement).statements[2]] = setOf("init()") - val loopBody = - ((functionOk.body as CompoundStatement).statements[3] as DoStatement).statement - as? CompoundStatement + nodesToOp[(functionOk.body as Block).statements[1]] = setOf("create()") + nodesToOp[(functionOk.body as Block).statements[2]] = setOf("init()") + val loopBody = ((functionOk.body as Block).statements[3] as DoStatement).statement as? Block assertNotNull(loopBody) nodesToOp[loopBody.statements[0]] = setOf("start()") nodesToOp[loopBody.statements[1]] = setOf("process()") nodesToOp[loopBody.statements[2]] = setOf("finish()") - nodesToOp[(functionOk.body as CompoundStatement).statements[4]] = setOf("reset()") + nodesToOp[(functionOk.body as Block).statements[4]] = setOf("reset()") val orderEvaluator = DFAOrderEvaluator(dfa, consideredDecl, nodesToOp) val everythingOk = orderEvaluator.evaluateOrder(p6Decl) @@ -466,9 +460,9 @@ class ComplexDFAOrderEvaluationTest { val consideredDecl = mutableSetOf(p1Decl.declarations[0]) val nodesToOp = mutableMapOf>() - nodesToOp[(functionOk.body as CompoundStatement).statements[1]] = setOf("create()") - nodesToOp[(functionOk.body as CompoundStatement).statements[3]] = setOf("start()") - nodesToOp[(functionOk.body as CompoundStatement).statements[4]] = setOf("finish()") + nodesToOp[(functionOk.body as Block).statements[1]] = setOf("create()") + nodesToOp[(functionOk.body as Block).statements[3]] = setOf("start()") + nodesToOp[(functionOk.body as Block).statements[4]] = setOf("finish()") val afterInterprocNodes = mutableListOf() val withoutInterprocNodes = mutableListOf() @@ -486,7 +480,7 @@ class ComplexDFAOrderEvaluationTest { assertFalse(everythingOk, "Expected incorrect order") assertContains( afterInterprocNodes, - (functionOk.body as CompoundStatement).statements[3], + (functionOk.body as Block).statements[3], "Expected start() node in list of unknown nodes" ) assertTrue(withoutInterprocNodes.isEmpty(), "No node should be clearly violating the rule") @@ -504,9 +498,9 @@ class ComplexDFAOrderEvaluationTest { val consideredDecl = mutableSetOf(p1Decl) val nodesToOp = mutableMapOf>() - nodesToOp[(functionOk.body as CompoundStatement).statements[0]] = setOf("init()") - nodesToOp[(functionOk.body as CompoundStatement).statements[1]] = setOf("start()") - nodesToOp[(functionOk.body as CompoundStatement).statements[2]] = setOf("finish()") + nodesToOp[(functionOk.body as Block).statements[0]] = setOf("init()") + nodesToOp[(functionOk.body as Block).statements[1]] = setOf("start()") + nodesToOp[(functionOk.body as Block).statements[2]] = setOf("finish()") val afterInterprocNodes = mutableListOf() val withoutInterprocNodes = mutableListOf() @@ -521,13 +515,12 @@ class ComplexDFAOrderEvaluationTest { ) // We cannot use p1Decl as start of the analysis because it has no nextEOG edges. Instead, // we want to start with the first instruction of the function. - val everythingOk = - orderEvaluator.evaluateOrder((functionOk.body as CompoundStatement).statements[0]) + val everythingOk = orderEvaluator.evaluateOrder((functionOk.body as Block).statements[0]) assertFalse(everythingOk, "Expected incorrect order") assertContains( afterInterprocNodes, - (functionOk.body as CompoundStatement).statements[0], + (functionOk.body as Block).statements[0], "Expected init() node in list of unknown nodes" ) assertTrue(withoutInterprocNodes.isEmpty(), "No node should be clearly violating the rule") @@ -545,9 +538,9 @@ class ComplexDFAOrderEvaluationTest { val consideredDecl = mutableSetOf(p1Decl.declarations[0]) val nodesToOp = mutableMapOf>() - nodesToOp[(functionOk.body as CompoundStatement).statements[1]] = setOf("create()") - nodesToOp[(functionOk.body as CompoundStatement).statements[2]] = setOf("init()") - nodesToOp[(functionOk.body as CompoundStatement).statements[3]] = setOf("start()") + nodesToOp[(functionOk.body as Block).statements[1]] = setOf("create()") + nodesToOp[(functionOk.body as Block).statements[2]] = setOf("init()") + nodesToOp[(functionOk.body as Block).statements[3]] = setOf("start()") val possibleInterprocFailures = mutableListOf() val withoutInterprocNodes = mutableListOf() @@ -565,7 +558,7 @@ class ComplexDFAOrderEvaluationTest { assertFalse(everythingOk, "Expected incorrect order") assertContains( possibleInterprocFailures, - (functionOk.body as CompoundStatement).statements[3], + (functionOk.body as Block).statements[3], "Expected start() node in list of unknown nodes" ) assertTrue(withoutInterprocNodes.isEmpty(), "No node should be clearly violating the rule") @@ -583,9 +576,9 @@ class ComplexDFAOrderEvaluationTest { val consideredDecl = mutableSetOf(p1Decl.declarations[0]) val nodesToOp = mutableMapOf>() - nodesToOp[(functionOk.body as CompoundStatement).statements[1]] = setOf("create()") - nodesToOp[(functionOk.body as CompoundStatement).statements[3]] = setOf("start()") - nodesToOp[(functionOk.body as CompoundStatement).statements[4]] = setOf("finish()") + nodesToOp[(functionOk.body as Block).statements[1]] = setOf("create()") + nodesToOp[(functionOk.body as Block).statements[3]] = setOf("start()") + nodesToOp[(functionOk.body as Block).statements[4]] = setOf("finish()") val afterInterprocNodes = mutableListOf() val withoutInterprocNodes = mutableListOf() @@ -603,12 +596,12 @@ class ComplexDFAOrderEvaluationTest { assertFalse(everythingOk, "Expected incorrect order") assertContains( afterInterprocNodes, - (functionOk.body as CompoundStatement).statements[3], + (functionOk.body as Block).statements[3], "Expected start() node in list of unknown nodes" ) assertContains( withoutInterprocNodes, - (functionOk.body as CompoundStatement).statements[3], + (functionOk.body as Block).statements[3], "Expected start() node in list of unknown nodes" ) } @@ -625,9 +618,9 @@ class ComplexDFAOrderEvaluationTest { val consideredDecl = mutableSetOf(p1Decl.declarations[0]) val nodesToOp = mutableMapOf>() - nodesToOp[(functionOk.body as CompoundStatement).statements[2]] = setOf("create()") - nodesToOp[(functionOk.body as CompoundStatement).statements[5]] = setOf("start()") - nodesToOp[(functionOk.body as CompoundStatement).statements[6]] = setOf("finish()") + nodesToOp[(functionOk.body as Block).statements[2]] = setOf("create()") + nodesToOp[(functionOk.body as Block).statements[5]] = setOf("start()") + nodesToOp[(functionOk.body as Block).statements[6]] = setOf("finish()") val afterInterprocNodes = mutableListOf() val withoutInterprocNodes = mutableListOf() @@ -646,7 +639,7 @@ class ComplexDFAOrderEvaluationTest { assertTrue(afterInterprocNodes.isEmpty(), "All nodes clearly violate the rule") assertContains( withoutInterprocNodes, - (functionOk.body as CompoundStatement).statements[5], + (functionOk.body as Block).statements[5], "Expected start() node in list of unknown nodes" ) } @@ -693,14 +686,14 @@ class ComplexDFAOrderEvaluationTest { ) { val lastNode = fsm.executionTrace.last().cpgNode as CallExpression var baseOfLastNode = getBaseOfNode(lastNode) - if (baseOfLastNode is DeclaredReferenceExpression) { + if (baseOfLastNode is Reference) { baseOfLastNode = baseOfLastNode.refersTo } val returnStatements = lastNode.followNextEOG { edge -> edge.end is ReturnStatement && - ((edge.end as ReturnStatement).returnValue as? DeclaredReferenceExpression) - ?.refersTo == baseOfLastNode + ((edge.end as ReturnStatement).returnValue as? Reference)?.refersTo == + baseOfLastNode } if (returnStatements?.isNotEmpty() == true) { // There was a return statement returning the respective variable. The flow of diff --git a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/fsm/SimpleDFAOrderEvaluationTest.kt b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/fsm/SimpleDFAOrderEvaluationTest.kt index 3f79a849d7..4fffc76f4f 100644 --- a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/fsm/SimpleDFAOrderEvaluationTest.kt +++ b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/fsm/SimpleDFAOrderEvaluationTest.kt @@ -32,9 +32,9 @@ import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.bodyOrNull import de.fraunhofer.aisec.cpg.graph.byNameOrNull import de.fraunhofer.aisec.cpg.graph.declarations.* -import de.fraunhofer.aisec.cpg.graph.statements.CompoundStatement import de.fraunhofer.aisec.cpg.graph.statements.DeclarationStatement import de.fraunhofer.aisec.cpg.graph.statements.IfStatement +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Block import de.fraunhofer.aisec.cpg.passes.EdgeCachePass import de.fraunhofer.aisec.cpg.passes.UnreachableEOGPass import java.nio.file.Path @@ -90,8 +90,8 @@ class SimpleDFAOrderEvaluationTest { val consideredDecl = mutableSetOf(p4Decl.declarations[0]) val nodesToOp = mutableMapOf>() - nodesToOp[(functionOk.body as CompoundStatement).statements[1]] = setOf("start()") - nodesToOp[(functionOk.body as CompoundStatement).statements[2]] = setOf("finish()") + nodesToOp[(functionOk.body as Block).statements[1]] = setOf("start()") + nodesToOp[(functionOk.body as Block).statements[2]] = setOf("finish()") val orderEvaluator = DFAOrderEvaluator(dfa, consideredDecl, nodesToOp) val everythingOk = orderEvaluator.evaluateOrder(p4Decl) @@ -111,9 +111,9 @@ class SimpleDFAOrderEvaluationTest { val consideredDecl = mutableSetOf(p4Decl.declarations[0]) val nodesToOp = mutableMapOf>() - nodesToOp[(functionOk.body as CompoundStatement).statements[1]] = setOf("start()") + nodesToOp[(functionOk.body as Block).statements[1]] = setOf("start()") // We do not model the call to foo() because it does not exist in our model. - nodesToOp[(functionOk.body as CompoundStatement).statements[3]] = setOf("finish()") + nodesToOp[(functionOk.body as Block).statements[3]] = setOf("finish()") val orderEvaluator = DFAOrderEvaluator(dfa, consideredDecl, nodesToOp) val everythingOk = orderEvaluator.evaluateOrder(p4Decl) @@ -135,18 +135,16 @@ class SimpleDFAOrderEvaluationTest { val nodesToOp = mutableMapOf>() // We model the calls to start() for the then and the else branch val thenBranch = - ((functionOk.body as CompoundStatement).statements[2] as? IfStatement)?.thenStatement - as? CompoundStatement + ((functionOk.body as Block).statements[2] as? IfStatement)?.thenStatement as? Block assertNotNull(thenBranch) nodesToOp[thenBranch.statements[0]] = setOf("start()") val elseBranch = - ((functionOk.body as CompoundStatement).statements[2] as? IfStatement)?.elseStatement - as? CompoundStatement + ((functionOk.body as Block).statements[2] as? IfStatement)?.elseStatement as? Block assertNotNull(elseBranch) nodesToOp[elseBranch.statements[0]] = setOf("start()") // We do not model the call to foo() because it does not exist in our model. - nodesToOp[(functionOk.body as CompoundStatement).statements[3]] = setOf("finish()") + nodesToOp[(functionOk.body as Block).statements[3]] = setOf("finish()") val orderEvaluator = DFAOrderEvaluator(dfa, consideredDecl, nodesToOp) val everythingOk = orderEvaluator.evaluateOrder(p4Decl) @@ -166,11 +164,11 @@ class SimpleDFAOrderEvaluationTest { val consideredBases = mutableSetOf(pDecl.declarations[0]) val nodesToOp = mutableMapOf>() - nodesToOp[(functionOk.body as CompoundStatement).statements[1]] = setOf("set_key()") - nodesToOp[(functionOk.body as CompoundStatement).statements[2]] = setOf("start()") - nodesToOp[(functionOk.body as CompoundStatement).statements[3]] = setOf("finish()") + nodesToOp[(functionOk.body as Block).statements[1]] = setOf("set_key()") + nodesToOp[(functionOk.body as Block).statements[2]] = setOf("start()") + nodesToOp[(functionOk.body as Block).statements[3]] = setOf("finish()") // We do not model the call to foo() because it does not exist in our model. - nodesToOp[(functionOk.body as CompoundStatement).statements[5]] = setOf("set_key()") + nodesToOp[(functionOk.body as Block).statements[5]] = setOf("set_key()") val orderEvaluator = DFAOrderEvaluator(dfa, consideredBases, nodesToOp) val everythingOk = orderEvaluator.evaluateOrder(pDecl) @@ -190,7 +188,7 @@ class SimpleDFAOrderEvaluationTest { val consideredBases = mutableSetOf(p2Decl.declarations[0]) val nodesToOp = mutableMapOf>() - nodesToOp[(functionOk.body as CompoundStatement).statements[1]] = setOf("start()") + nodesToOp[(functionOk.body as Block).statements[1]] = setOf("start()") val orderEvaluator = DFAOrderEvaluator(dfa, consideredBases, nodesToOp) val everythingOk = orderEvaluator.evaluateOrder(p2Decl) @@ -211,11 +209,10 @@ class SimpleDFAOrderEvaluationTest { val nodesToOp = mutableMapOf>() val thenBranch = - ((functionOk.body as CompoundStatement).statements[1] as? IfStatement)?.thenStatement - as? CompoundStatement + ((functionOk.body as Block).statements[1] as? IfStatement)?.thenStatement as? Block assertNotNull(thenBranch) nodesToOp[thenBranch.statements[0]] = setOf("start()") - nodesToOp[(functionOk.body as CompoundStatement).statements[2]] = setOf("finish()") + nodesToOp[(functionOk.body as Block).statements[2]] = setOf("finish()") val orderEvaluator = DFAOrderEvaluator(dfa, consideredDecl, nodesToOp) val everythingOk = orderEvaluator.evaluateOrder(p3Decl) @@ -236,13 +233,12 @@ class SimpleDFAOrderEvaluationTest { val nodesToOp = mutableMapOf>() val thenBranch = - ((functionOk.body as CompoundStatement).statements[1] as? IfStatement)?.thenStatement - as? CompoundStatement + ((functionOk.body as Block).statements[1] as? IfStatement)?.thenStatement as? Block assertNotNull(thenBranch) nodesToOp[thenBranch.statements[0]] = setOf("start()") nodesToOp[thenBranch.statements[1]] = setOf("finish()") - nodesToOp[(functionOk.body as CompoundStatement).statements[2]] = setOf("start()") - nodesToOp[(functionOk.body as CompoundStatement).statements[4]] = setOf("finish()") + nodesToOp[(functionOk.body as Block).statements[2]] = setOf("start()") + nodesToOp[(functionOk.body as Block).statements[4]] = setOf("finish()") val orderEvaluator = DFAOrderEvaluator(dfa, consideredDecl, nodesToOp) val everythingOk = orderEvaluator.evaluateOrder(p4Decl) diff --git a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGPassTest.kt b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGPassTest.kt index 7d26b62d7e..1fa67f2282 100644 --- a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGPassTest.kt +++ b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGPassTest.kt @@ -86,7 +86,7 @@ class UnreachableEOGPassTest { val incOp = thenDecl.end.nextEOGEdges[0] assertFalse(incOp.getProperty(Properties.UNREACHABLE) as Boolean) assertEquals(1, incOp.end.nextEOGEdges.size) - // The compoundStmt + // The block val thenCompound = incOp.end.nextEOGEdges[0] assertFalse(thenCompound.getProperty(Properties.UNREACHABLE) as Boolean) assertEquals(1, thenCompound.end.nextEOGEdges.size) @@ -103,7 +103,7 @@ class UnreachableEOGPassTest { val decOp = elseDecl.end.nextEOGEdges[0] assertTrue(decOp.getProperty(Properties.UNREACHABLE) as Boolean) assertEquals(1, decOp.end.nextEOGEdges.size) - // The compoundStmt + // The block val elseCompound = decOp.end.nextEOGEdges[0] assertTrue(elseCompound.getProperty(Properties.UNREACHABLE) as Boolean) assertEquals(1, elseCompound.end.nextEOGEdges.size) diff --git a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/query/QueryTest.kt b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/query/QueryTest.kt index 0a70fa2e8d..6cccf1d340 100644 --- a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/query/QueryTest.kt +++ b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/query/QueryTest.kt @@ -138,8 +138,7 @@ class QueryTest { val queryTreeResult = result.all({ it.name.localName == "free" }) { outer -> !executionPath(outer) { - (it as? DeclaredReferenceExpression)?.refersTo == - (outer.arguments[0] as? DeclaredReferenceExpression)?.refersTo + (it as? Reference)?.refersTo == (outer.arguments[0] as? Reference)?.refersTo } .value } @@ -152,8 +151,8 @@ class QueryTest { { outer -> not( executionPath(outer) { - (it as? DeclaredReferenceExpression)?.refersTo == - (outer.arguments[0] as? DeclaredReferenceExpression)?.refersTo + (it as? Reference)?.refersTo == + (outer.arguments[0] as? Reference)?.refersTo } ) } @@ -178,10 +177,8 @@ class QueryTest { result.all({ it.name.localName == "free" }) { outer -> !executionPath(outer) { (it as? CallExpression)?.name?.localName == "free" && - ((it as? CallExpression)?.arguments?.getOrNull(0) - as? DeclaredReferenceExpression) - ?.refersTo == - (outer.arguments[0] as? DeclaredReferenceExpression)?.refersTo + ((it as? CallExpression)?.arguments?.getOrNull(0) as? Reference) + ?.refersTo == (outer.arguments[0] as? Reference)?.refersTo } .value } @@ -195,10 +192,8 @@ class QueryTest { not( executionPath(outer) { (it as? CallExpression)?.name?.localName == "free" && - ((it as? CallExpression)?.arguments?.getOrNull(0) - as? DeclaredReferenceExpression) - ?.refersTo == - (outer.arguments[0] as? DeclaredReferenceExpression)?.refersTo + ((it as? CallExpression)?.arguments?.getOrNull(0) as? Reference) + ?.refersTo == (outer.arguments[0] as? Reference)?.refersTo } ) } @@ -494,7 +489,7 @@ class QueryTest { val result = analyzer.analyze().get() val queryTreeResult = - result.all( + result.all( mustSatisfy = { max(it.subscriptExpression) < min(it.arraySize) && min(it.subscriptExpression) >= 0 @@ -503,7 +498,7 @@ class QueryTest { assertFalse(queryTreeResult.first) val queryTreeResult2 = - result.allExtended( + result.allExtended( mustSatisfy = { (max(it.subscriptExpression) lt min(it.arraySize)) and (min(it.subscriptExpression) ge 0) @@ -526,7 +521,7 @@ class QueryTest { val result = analyzer.analyze().get() val queryTreeResult = - result.exists( + result.exists( mustSatisfy = { max(it.subscriptExpression) >= min(it.arraySize) || min(it.subscriptExpression) < 0 @@ -535,7 +530,7 @@ class QueryTest { assertTrue(queryTreeResult.first) val queryTreeResult2 = - result.existsExtended( + result.existsExtended( mustSatisfy = { (it.subscriptExpression.max ge it.arraySize.min) or (it.subscriptExpression.min lt 0) @@ -559,7 +554,7 @@ class QueryTest { val result = analyzer.analyze().get() val queryTreeResult = - result.all( + result.all( mustSatisfy = { max(it.subscriptExpression) < min(it.arraySize) && min(it.subscriptExpression) >= 0 @@ -568,7 +563,7 @@ class QueryTest { assertFalse(queryTreeResult.first) val queryTreeResult2 = - result.allExtended( + result.allExtended( mustSatisfy = { (max(it.subscriptExpression) lt min(it.arraySize)) and (min(it.subscriptExpression) ge 0) @@ -592,36 +587,28 @@ class QueryTest { val result = analyzer.analyze().get() val queryTreeResult = - result.all( + result.all( mustSatisfy = { max(it.subscriptExpression) < min( it.arrayExpression - .followPrevDFGEdgesUntilHit { node -> - node is ArrayCreationExpression - } + .followPrevDFGEdgesUntilHit { node -> node is NewArrayExpression } .fulfilled - .map { it2 -> - (it2.last() as ArrayCreationExpression).dimensions[0] - } + .map { it2 -> (it2.last() as NewArrayExpression).dimensions[0] } ) && min(it.subscriptExpression) > 0 } ) assertFalse(queryTreeResult.first) val queryTreeResult2 = - result.allExtended( + result.allExtended( mustSatisfy = { (max(it.subscriptExpression) lt min( it.arrayExpression - .followPrevDFGEdgesUntilHit { node -> - node is ArrayCreationExpression - } + .followPrevDFGEdgesUntilHit { node -> node is NewArrayExpression } .fulfilled - .map { it2 -> - (it2.last() as ArrayCreationExpression).dimensions[0] - } + .map { it2 -> (it2.last() as NewArrayExpression).dimensions[0] } )) and (min(it.subscriptExpression) ge 0) } ) @@ -643,7 +630,7 @@ class QueryTest { val result = analyzer.analyze().get() val queryTreeResult = - result.all( + result.all( mustSatisfy = { val max_sub = max(it.subscriptExpression) val min_dim = min(it.arraySize) @@ -654,7 +641,7 @@ class QueryTest { assertTrue(queryTreeResult.first) val queryTreeResult2 = - result.allExtended( + result.allExtended( mustSatisfy = { val max_sub = max(it.subscriptExpression) val min_dim = min(it.arraySize) diff --git a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/NullPointerCheck.kt b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/NullPointerCheck.kt index dc376e8343..80606ac31a 100644 --- a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/NullPointerCheck.kt +++ b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/NullPointerCheck.kt @@ -61,7 +61,7 @@ class NullPointerCheck { handleHasBase(v) } - fun visit(v: ArraySubscriptionExpression) { + fun visit(v: SubscriptExpression) { handleHasBase(v) } } diff --git a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/OutOfBoundsCheck.kt b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/OutOfBoundsCheck.kt index 9f73e97f16..b30557ec37 100644 --- a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/OutOfBoundsCheck.kt +++ b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/OutOfBoundsCheck.kt @@ -30,9 +30,9 @@ import de.fraunhofer.aisec.cpg.console.fancyCode import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.capacity import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration -import de.fraunhofer.aisec.cpg.graph.statements.expressions.ArrayCreationExpression -import de.fraunhofer.aisec.cpg.graph.statements.expressions.ArraySubscriptionExpression -import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.NewArrayExpression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Reference +import de.fraunhofer.aisec.cpg.graph.statements.expressions.SubscriptExpression import de.fraunhofer.aisec.cpg.processing.IVisitor import de.fraunhofer.aisec.cpg.processing.strategy.Strategy import de.fraunhofer.aisec.cpg.sarif.PhysicalLocation @@ -53,7 +53,7 @@ class OutOfBoundsCheck { tu.accept( Strategy::AST_FORWARD, object : IVisitor() { - fun visit(v: ArraySubscriptionExpression) { + fun visit(v: SubscriptExpression) { val evaluator = ValueEvaluator() val resolvedIndex = evaluator.evaluate(v.subscriptExpression) @@ -61,9 +61,8 @@ class OutOfBoundsCheck { // check, if we know that the array was initialized with a fixed length // TODO(oxisto): it would be nice to have a helper that follows the expr val decl = - (v.arrayExpression as? DeclaredReferenceExpression)?.refersTo - as? VariableDeclaration - (decl?.initializer as? ArrayCreationExpression)?.let { + (v.arrayExpression as? Reference)?.refersTo as? VariableDeclaration + (decl?.initializer as? NewArrayExpression)?.let { val capacity = it.capacity if (resolvedIndex >= capacity) { diff --git a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/console/Extensions.kt b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/console/Extensions.kt index 274b25f3dc..da3832b394 100644 --- a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/console/Extensions.kt +++ b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/console/Extensions.kt @@ -28,7 +28,6 @@ package de.fraunhofer.aisec.cpg.console import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration -import de.fraunhofer.aisec.cpg.graph.statements.CompoundStatement import de.fraunhofer.aisec.cpg.graph.statements.DeclarationStatement import de.fraunhofer.aisec.cpg.graph.statements.IfStatement import de.fraunhofer.aisec.cpg.graph.statements.expressions.* @@ -191,7 +190,7 @@ fun getFanciesFor(original: Node?, node: Node?): List { + is Reference -> { // also color it, if it's on its own if (original == node) { node.location?.let { styles.identifier?.let { id -> list += Pair(id, it.region) } } @@ -215,7 +214,7 @@ fun getFanciesFor(original: Node?, node: Node?): List { + is Block -> { // loop through statements for (statement in node.statements) { list.addAll(getFanciesFor(original, statement)) @@ -245,7 +244,7 @@ fun getFanciesFor(original: Node?, node: Node?): List { + is NewArrayExpression -> { fancyWord("new", node, list, styles.keyword) // check for primitive types 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 4e42d509f3..12129c0d2e 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 @@ -30,8 +30,9 @@ import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.declarations.* import de.fraunhofer.aisec.cpg.graph.scopes.* import de.fraunhofer.aisec.cpg.graph.statements.* +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Block import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression -import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Reference import de.fraunhofer.aisec.cpg.graph.types.FunctionPointerType import de.fraunhofer.aisec.cpg.graph.types.IncompleteType import de.fraunhofer.aisec.cpg.graph.types.Type @@ -88,8 +89,8 @@ class ScopeManager : ScopeProvider { get() = scopeMap[null] as? GlobalScope /** The current block, according to the scope that is currently active. */ - val currentBlock: CompoundStatement? - get() = this.firstScopeIsInstanceOrNull()?.astNode as? CompoundStatement + val currentBlock: Block? + get() = this.firstScopeIsInstanceOrNull()?.astNode as? Block /** The current function, according to the scope that is currently active. */ val currentFunction: FunctionDeclaration? get() = this.firstScopeIsInstanceOrNull()?.astNode as? FunctionDeclaration @@ -213,7 +214,7 @@ class ScopeManager : ScopeProvider { * on-the-fly, if they do not exist. * * The scope manager has an internal association between the type of scope, e.g. a [BlockScope] - * and the CPG node it represents, e.g. a [CompoundStatement]. + * and the CPG node it represents, e.g. a [Block]. * * Afterwards, all calls to [addDeclaration] will be distributed to the * [de.fraunhofer.aisec.cpg.graph.DeclarationHolder] that is currently in-scope. @@ -225,7 +226,7 @@ class ScopeManager : ScopeProvider { if (!scopeMap.containsKey(nodeToScope)) { newScope = when (nodeToScope) { - is CompoundStatement -> BlockScope(nodeToScope) + is Block -> BlockScope(nodeToScope) is WhileStatement, is DoStatement, is AssertStatement -> LoopScope(nodeToScope as Statement) @@ -596,10 +597,7 @@ class ScopeManager : ScopeProvider { * TODO: We should merge this function with [.resolveFunction] */ @JvmOverloads - fun resolveReference( - ref: DeclaredReferenceExpression, - scope: Scope? = currentScope - ): ValueDeclaration? { + fun resolveReference(ref: Reference, scope: Scope? = currentScope): ValueDeclaration? { return resolve(scope) { if ( it.name.lastPartsMatch(ref.name) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/DeclarationBuilder.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/DeclarationBuilder.kt index 06a1cd5ee7..b7c8f0b23c 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/DeclarationBuilder.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/DeclarationBuilder.kt @@ -30,8 +30,8 @@ import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend import de.fraunhofer.aisec.cpg.graph.Node.Companion.EMPTY_NAME import de.fraunhofer.aisec.cpg.graph.NodeBuilder.log import de.fraunhofer.aisec.cpg.graph.declarations.* -import de.fraunhofer.aisec.cpg.graph.statements.expressions.ArrayCreationExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.NewArrayExpression import de.fraunhofer.aisec.cpg.graph.types.Type import de.fraunhofer.aisec.cpg.sarif.PhysicalLocation @@ -129,14 +129,14 @@ fun MetadataProvider.newConstructorDeclaration( * argument. */ @JvmOverloads -fun MetadataProvider.newParamVariableDeclaration( +fun MetadataProvider.newParameterDeclaration( name: CharSequence?, type: Type = unknownType(), variadic: Boolean = false, code: String? = null, rawNode: Any? = null -): ParamVariableDeclaration { - val node = ParamVariableDeclaration() +): ParameterDeclaration { + val node = ParameterDeclaration() node.applyMetadata(this, name, rawNode, code, localNameOnly = true) node.type = type @@ -222,18 +222,18 @@ fun MetadataProvider.newTypedefDeclaration( } /** - * Creates a new [TypeParamDeclaration]. The [MetadataProvider] receiver will be used to fill + * Creates a new [TypeParameterDeclaration]. The [MetadataProvider] receiver will be used to fill * different meta-data using [Node.applyMetadata]. Calling this extension function outside of Kotlin * requires an appropriate [MetadataProvider], such as a [LanguageFrontend] as an additional * prepended argument. */ @JvmOverloads -fun MetadataProvider.newTypeParamDeclaration( +fun MetadataProvider.newTypeParameterDeclaration( name: CharSequence?, code: String? = null, rawNode: Any? = null -): TypeParamDeclaration { - val node = TypeParamDeclaration() +): TypeParameterDeclaration { + val node = TypeParameterDeclaration() node.applyMetadata(this, name, rawNode, code, true) log(node) @@ -304,18 +304,18 @@ fun MetadataProvider.newFunctionTemplateDeclaration( } /** - * Creates a new [ClassTemplateDeclaration]. The [MetadataProvider] receiver will be used to fill + * Creates a new [RecordTemplateDeclaration]. The [MetadataProvider] receiver will be used to fill * different meta-data using [Node.applyMetadata]. Calling this extension function outside of Kotlin * requires an appropriate [MetadataProvider], such as a [LanguageFrontend] as an additional * prepended argument. */ @JvmOverloads -fun MetadataProvider.newClassTemplateDeclaration( +fun MetadataProvider.newRecordTemplateDeclaration( name: CharSequence?, code: String? = null, rawNode: Any? = null -): ClassTemplateDeclaration { - val node = ClassTemplateDeclaration() +): RecordTemplateDeclaration { + val node = RecordTemplateDeclaration() node.applyMetadata(this, name, rawNode, code, true) log(node) @@ -369,7 +369,7 @@ fun MetadataProvider.newFieldDeclaration( node.location = location node.isImplicitInitializerAllowed = implicitInitializerAllowed if (initializer != null) { - if (initializer is ArrayCreationExpression) { + if (initializer is NewArrayExpression) { node.isArray = true } node.initializer = initializer @@ -442,18 +442,18 @@ fun MetadataProvider.newNamespaceDeclaration( } /** - * Creates a new [UsingDirective]. The [MetadataProvider] receiver will be used to fill different + * Creates a new [UsingDeclaration]. The [MetadataProvider] receiver will be used to fill different * meta-data using [Node.applyMetadata]. Calling this extension function outside of Kotlin requires * an appropriate [MetadataProvider], such as a [LanguageFrontend] as an additional prepended * argument. */ @JvmOverloads -fun MetadataProvider.newUsingDirective( +fun MetadataProvider.newUsingDeclaration( code: String? = null, qualifiedName: CharSequence?, rawNode: Any? = null -): UsingDirective { - val node = UsingDirective() +): UsingDeclaration { + val node = UsingDeclaration() node.applyMetadata(this, qualifiedName, rawNode, code) node.qualifiedName = qualifiedName.toString() diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/ExpressionBuilder.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/ExpressionBuilder.kt index 94eb6f4691..b9edf83c4c 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/ExpressionBuilder.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/ExpressionBuilder.kt @@ -192,8 +192,8 @@ fun MetadataProvider.newConstructExpression( @JvmOverloads fun MetadataProvider.newConditionalExpression( condition: Expression, - thenExpr: Expression?, - elseExpr: Expression?, + thenExpression: Expression?, + elseExpression: Expression?, type: Type = unknownType(), code: String? = null, rawNode: Any? = null @@ -203,8 +203,8 @@ fun MetadataProvider.newConditionalExpression( node.type = type node.condition = condition - node.thenExpr = thenExpr - node.elseExpr = elseExpr + node.thenExpression = thenExpression + node.elseExpression = elseExpression log(node) return node @@ -252,17 +252,13 @@ fun MetadataProvider.newLambdaExpression( } /** - * Creates a new [CompoundStatementExpression]. The [MetadataProvider] receiver will be used to fill - * different meta-data using [Node.applyMetadata]. Calling this extension function outside of Kotlin - * requires an appropriate [MetadataProvider], such as a [LanguageFrontend] as an additional - * prepended argument. + * Creates a new [Block]. The [MetadataProvider] receiver will be used to fill different meta-data + * using [Node.applyMetadata]. Calling this extension function outside of Kotlin requires an + * appropriate [MetadataProvider], such as a [LanguageFrontend] as an additional prepended argument. */ @JvmOverloads -fun MetadataProvider.newCompoundStatementExpression( - code: String? = null, - rawNode: Any? = null -): CompoundStatementExpression { - val node = CompoundStatementExpression() +fun MetadataProvider.newBlock(code: String? = null, rawNode: Any? = null): Block { + val node = Block() node.applyMetadata(this, EMPTY_NAME, rawNode, code, true) log(node) @@ -300,12 +296,12 @@ fun MetadataProvider.newCallExpression( * argument. */ @JvmOverloads -fun MetadataProvider.newExplicitConstructorInvocation( +fun MetadataProvider.newConstructorCallExpression( containingClass: String?, code: String? = null, rawNode: Any? = null -): ExplicitConstructorInvocation { - val node = ExplicitConstructorInvocation() +): ConstructorCallExpression { + val node = ConstructorCallExpression() node.applyMetadata(this, EMPTY_NAME, rawNode, code, true) node.containingClass = containingClass @@ -409,17 +405,17 @@ fun MetadataProvider.newTypeIdExpression( } /** - * Creates a new [ArraySubscriptionExpression]. The [MetadataProvider] receiver will be used to fill + * Creates a new [SubscriptExpression]. The [MetadataProvider] receiver will be used to fill * different meta-data using [Node.applyMetadata]. Calling this extension function outside of Kotlin * requires an appropriate [MetadataProvider], such as a [LanguageFrontend] as an additional * prepended argument. */ @JvmOverloads -fun MetadataProvider.newArraySubscriptionExpression( +fun MetadataProvider.newSubscriptExpression( code: String? = null, rawNode: Any? = null -): ArraySubscriptionExpression { - val node = ArraySubscriptionExpression() +): SubscriptExpression { + val node = SubscriptExpression() node.applyMetadata(this, EMPTY_NAME, rawNode, code, true) log(node) @@ -450,17 +446,17 @@ fun MetadataProvider.newRangeExpression( } /** - * Creates a new [ArrayCreationExpression]. The [MetadataProvider] receiver will be used to fill + * Creates a new [NewArrayExpression]. The [MetadataProvider] receiver will be used to fill * different meta-data using [Node.applyMetadata]. Calling this extension function outside of Kotlin * requires an appropriate [MetadataProvider], such as a [LanguageFrontend] as an additional * prepended argument. */ @JvmOverloads -fun MetadataProvider.newArrayCreationExpression( +fun MetadataProvider.newNewArrayExpression( code: String? = null, rawNode: Any? = null -): ArrayCreationExpression { - val node = ArrayCreationExpression() +): NewArrayExpression { + val node = NewArrayExpression() node.applyMetadata(this, EMPTY_NAME, rawNode, code, true) log(node) @@ -468,19 +464,19 @@ fun MetadataProvider.newArrayCreationExpression( } /** - * Creates a new [DeclaredReferenceExpression]. The [MetadataProvider] receiver will be used to fill - * different meta-data using [Node.applyMetadata]. Calling this extension function outside of Kotlin - * requires an appropriate [MetadataProvider], such as a [LanguageFrontend] as an additional - * prepended argument. + * Creates a new [Reference]. The [MetadataProvider] receiver will be used to fill different + * meta-data using [Node.applyMetadata]. Calling this extension function outside of Kotlin requires + * an appropriate [MetadataProvider], such as a [LanguageFrontend] as an additional prepended + * argument. */ @JvmOverloads -fun MetadataProvider.newDeclaredReferenceExpression( +fun MetadataProvider.newReference( name: CharSequence?, type: Type = unknownType(), code: String? = null, rawNode: Any? = null -): DeclaredReferenceExpression { - val node = DeclaredReferenceExpression() +): Reference { + val node = Reference() node.applyMetadata(this, name, rawNode, code, true) node.type = type diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt index 6c7dbc9f82..2da58dba07 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt @@ -29,11 +29,11 @@ import de.fraunhofer.aisec.cpg.TranslationResult import de.fraunhofer.aisec.cpg.graph.declarations.* import de.fraunhofer.aisec.cpg.graph.edge.Properties import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge -import de.fraunhofer.aisec.cpg.graph.statements.CompoundStatement import de.fraunhofer.aisec.cpg.graph.statements.IfStatement import de.fraunhofer.aisec.cpg.graph.statements.Statement import de.fraunhofer.aisec.cpg.graph.statements.SwitchStatement import de.fraunhofer.aisec.cpg.graph.statements.expressions.* +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Block import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker import de.fraunhofer.aisec.cpg.passes.astParent @@ -168,8 +168,8 @@ inline fun DeclarationHolder.byName( * For convenience, `n` defaults to zero, so that the first statement is always easy to fetch. */ inline fun FunctionDeclaration.bodyOrNull(n: Int = 0): T? { - return if (this.body is CompoundStatement) { - return (body as? CompoundStatement)?.statements?.filterIsInstance()?.getOrNull(n) + return if (this.body is Block) { + return (body as? Block)?.statements?.filterIsInstance()?.getOrNull(n) } else { if (n == 0 && this.body is T) { this.body as T @@ -515,8 +515,8 @@ val Node?.methods: List val Node?.fields: List get() = this.allChildren() -/** Returns all [ParamVariableDeclaration] children in this graph, starting with this [Node]. */ -val Node?.parameters: List +/** Returns all [ParameterDeclaration] children in this graph, starting with this [Node]. */ +val Node?.parameters: List get() = this.allChildren() /** Returns all [FunctionDeclaration] children in this graph, starting with this [Node]. */ @@ -539,8 +539,8 @@ val Node?.variables: List val Node?.literals: List> get() = this.allChildren() -/** Returns all [DeclaredReferenceExpression] children in this graph, starting with this [Node]. */ -val Node?.refs: List +/** Returns all [Reference] children in this graph, starting with this [Node]. */ +val Node?.refs: List get() = this.allChildren() /** Returns all [Assignment] child edges in this graph, starting with this [Node]. */ @@ -559,10 +559,7 @@ val Node?.assignments: List val VariableDeclaration.firstAssignment: Expression? get() { val start = this.scope?.astNode ?: return null - val assignments = - start.assignments.filter { - (it.target as? DeclaredReferenceExpression)?.refersTo == this - } + val assignments = start.assignments.filter { (it.target as? Reference)?.refersTo == this } // We need to measure the distance between the start and each assignment value return assignments @@ -604,7 +601,7 @@ val FunctionDeclaration.callees: Set operator fun FunctionDeclaration.get(n: Int): Statement? { val body = this.body - if (body is CompoundStatement) { + if (body is Block) { return body[n] } else if (n == 0) { return body @@ -646,10 +643,10 @@ fun Node.controlledBy(): List { * Returns the expression specifying the dimension (i.e., size) of the array during its * initialization. */ -val ArraySubscriptionExpression.arraySize: Expression +val SubscriptExpression.arraySize: Expression get() = - (((this.arrayExpression as DeclaredReferenceExpression).refersTo as VariableDeclaration) - .initializer as ArrayCreationExpression) + (((this.arrayExpression as Reference).refersTo as VariableDeclaration).initializer + as NewArrayExpression) .dimensions[0] /** diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Holder.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Holder.kt index 47e55df0d0..ad44450b1c 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Holder.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Holder.kt @@ -25,7 +25,7 @@ */ package de.fraunhofer.aisec.cpg.graph -import de.fraunhofer.aisec.cpg.graph.statements.CompoundStatement +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Block import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression /** @@ -33,8 +33,8 @@ import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression * [StatementHolder], in which [Holder] is used as a common interface. * * A primary use-case for the usage of this interface is the Node Fluent DSL in order to create node - * objects which can either be used as a statement (e.g. in a [CompoundStatement]) or as an argument - * (e.g. of a [CallExpression]). + * objects which can either be used as a statement (e.g. in a [Block]) or as an argument (e.g. of a + * [CallExpression]). */ interface Holder { /** Adds a [Node] to the list of "held" nodes. */ diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/StatementBuilder.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/StatementBuilder.kt index c16186e5f0..18f4b65de9 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/StatementBuilder.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/StatementBuilder.kt @@ -147,24 +147,6 @@ fun MetadataProvider.newEmptyStatement(code: String? = null, rawNode: Any? = nul return node } -/** - * Creates a new [CompoundStatement]. The [MetadataProvider] receiver will be used to fill different - * meta-data using [Node.applyMetadata]. Calling this extension function outside of Kotlin requires - * an appropriate [MetadataProvider], such as a [LanguageFrontend] as an additional prepended - * argument. - */ -@JvmOverloads -fun MetadataProvider.newCompoundStatement( - code: String? = null, - rawNode: Any? = null -): CompoundStatement { - val node = CompoundStatement() - node.applyMetadata(this, EMPTY_NAME, rawNode, code, true) - - log(node) - return node -} - /** * Creates a new [DeclarationStatement]. The [MetadataProvider] receiver will be used to fill * different meta-data using [Node.applyMetadata]. Calling this extension function outside of Kotlin diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt index 7efefcf1f2..02e02f8750 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt @@ -209,31 +209,29 @@ context(RecordDeclaration) fun LanguageFrontend<*, *>.constructor( init: ConstructorDeclaration.() -> Unit ): ConstructorDeclaration { - val recordDecl: RecordDeclaration = this@RecordDeclaration - val node = newConstructorDeclaration(recordDecl.name, recordDeclaration = recordDecl) + val recordDeclaration: RecordDeclaration = this@RecordDeclaration + val node = + newConstructorDeclaration(recordDeclaration.name, recordDeclaration = recordDeclaration) scopeManager.enterScope(node) init(node) scopeManager.leaveScope(node) scopeManager.addDeclaration(node) - recordDecl.addConstructor(node) + recordDeclaration.addConstructor(node) return node } /** - * Creates a new [CompoundStatement] in the Fluent Node DSL and sets it to the - * [FunctionDeclaration.body] of the nearest enclosing [FunctionDeclaration]. The [init] block can - * be used to create further sub-nodes as well as configuring the created node itself. + * Creates a new [Block] in the Fluent Node DSL and sets it to the [FunctionDeclaration.body] of the + * nearest enclosing [FunctionDeclaration]. The [init] block can be used to create further sub-nodes + * as well as configuring the created node itself. */ context(FunctionDeclaration) -fun LanguageFrontend<*, *>.body( - needsScope: Boolean = true, - init: CompoundStatement.() -> Unit -): CompoundStatement { - val node = newCompoundStatement() +fun LanguageFrontend<*, *>.body(needsScope: Boolean = true, init: Block.() -> Unit): Block { + val node = newBlock() scopeIfNecessary(needsScope, node, init) body = node @@ -242,7 +240,7 @@ fun LanguageFrontend<*, *>.body( } /** - * Creates a new [ParamVariableDeclaration] in the Fluent Node DSL and adds it to the + * Creates a new [ParameterDeclaration] in the Fluent Node DSL and adds it to the * [FunctionDeclaration.parameters] of the nearest enclosing [FunctionDeclaration]. The [init] block * can be used to create further sub-nodes as well as configuring the created node itself. */ @@ -251,10 +249,10 @@ context(FunctionDeclaration) fun LanguageFrontend<*, *>.param( name: CharSequence, type: Type = unknownType(), - init: (ParamVariableDeclaration.() -> Unit)? = null -): ParamVariableDeclaration { + init: (ParameterDeclaration.() -> Unit)? = null +): ParameterDeclaration { val node = - (this@LanguageFrontend).newParamVariableDeclaration( + (this@LanguageFrontend).newParameterDeclaration( name, type, ) @@ -284,9 +282,9 @@ fun LanguageFrontend<*, *>.returnStmt(init: ReturnStatement.() -> Unit): ReturnS context(Holder) fun LanguageFrontend<*, *>.ase( - init: (ArraySubscriptionExpression.() -> Unit)? = null -): ArraySubscriptionExpression { - val node = newArraySubscriptionExpression() + init: (SubscriptExpression.() -> Unit)? = null +): SubscriptExpression { + val node = newSubscriptExpression() if (init != null) { init(node) @@ -347,9 +345,8 @@ fun LanguageFrontend<*, *>.variable( * * The type of expression is determined whether [name] is either a [Name] with a [Name.parent] or if * it can be parsed as a FQN in the given language. It also automatically creates either a - * [DeclaredReferenceExpression] or [MemberExpression] and sets it as the [CallExpression.callee]. - * The [init] block can be used to create further sub-nodes as well as configuring the created node - * itself. + * [Reference] or [MemberExpression] and sets it as the [CallExpression.callee]. The [init] block + * can be used to create further sub-nodes as well as configuring the created node itself. */ context(Holder) @@ -367,7 +364,7 @@ fun LanguageFrontend<*, *>.call( isStatic ) } else { - newCallExpression(newDeclaredReferenceExpression(parsedName)) + newCallExpression(newReference(parsedName)) } if (init != null) { init(node) @@ -390,9 +387,8 @@ fun LanguageFrontend<*, *>.call( * * The type of expression is determined whether [localName] is either a [Name] with a [Name.parent] * or if it can be parsed as a FQN in the given language. It also automatically creates either a - * [DeclaredReferenceExpression] or [MemberExpression] and sets it as the [CallExpression.callee]. - * The [init] block can be used to create further sub-nodes as well as configuring the created node - * itself. + * [Reference] or [MemberExpression] and sets it as the [CallExpression.callee]. The [init] block + * can be used to create further sub-nodes as well as configuring the created node itself. */ context(Holder) @@ -487,7 +483,7 @@ fun LanguageFrontend<*, *>.memberOrRef(name: Name, type: Type = unknownType()): if (name.parent != null) { newMemberExpression(name.localName, memberOrRef(name.parent)) } else { - newDeclaredReferenceExpression(name.localName) + newReference(name.localName) } if (type !is UnknownType) { node.type = type @@ -596,17 +592,14 @@ fun LanguageFrontend<*, *>.whileCondition( } /** - * Creates a new [CompoundStatement] in the Fluent Node DSL and sets it to the - * [IfStatement.thenStatement] of the nearest enclosing [IfStatement]. The [init] block can be used - * to create further sub-nodes as well as configuring the created node itself. + * Creates a new [Block] in the Fluent Node DSL and sets it to the [IfStatement.thenStatement] of + * the nearest enclosing [IfStatement]. The [init] block can be used to create further sub-nodes as + * well as configuring the created node itself. */ context(IfStatement) -fun LanguageFrontend<*, *>.thenStmt( - needsScope: Boolean = true, - init: CompoundStatement.() -> Unit -): CompoundStatement { - val node = newCompoundStatement() +fun LanguageFrontend<*, *>.thenStmt(needsScope: Boolean = true, init: Block.() -> Unit): Block { + val node = newBlock() scopeIfNecessary(needsScope, node, init) thenStatement = node @@ -633,28 +626,28 @@ fun LanguageFrontend<*, *>.elseIf(init: IfStatement.() -> Unit): IfStatement { // TODO: Merge the bodies together /** - * Creates a new [CompoundStatement] in the Fluent Node DSL and sets it to the - * [WhileStatement.statement] of the nearest enclosing [WhileStatement]. The [init] block can be - * used to create further sub-nodes as well as configuring the created node itself. + * Creates a new [Block] in the Fluent Node DSL and sets it to the [WhileStatement.statement] of the + * nearest enclosing [WhileStatement]. The [init] block can be used to create further sub-nodes as + * well as configuring the created node itself. */ context(WhileStatement) -fun LanguageFrontend<*, *>.loopBody(init: CompoundStatement.() -> Unit): CompoundStatement { - val node = newCompoundStatement() +fun LanguageFrontend<*, *>.loopBody(init: Block.() -> Unit): Block { + val node = newBlock() init(node) statement = node return node } /** - * Creates a new [CompoundStatement] in the Fluent Node DSL and sets it to the - * [WhileStatement.statement] of the nearest enclosing [WhileStatement]. The [init] block can be - * used to create further sub-nodes as well as configuring the created node itself. + * Creates a new [Block] in the Fluent Node DSL and sets it to the [WhileStatement.statement] of the + * nearest enclosing [WhileStatement]. The [init] block can be used to create further sub-nodes as + * well as configuring the created node itself. */ context(ForEachStatement) -fun LanguageFrontend<*, *>.loopBody(init: CompoundStatement.() -> Unit): CompoundStatement { - val node = newCompoundStatement() +fun LanguageFrontend<*, *>.loopBody(init: Block.() -> Unit): Block { + val node = newBlock() init(node) statement = node @@ -662,14 +655,14 @@ fun LanguageFrontend<*, *>.loopBody(init: CompoundStatement.() -> Unit): Compoun } /** - * Creates a new [CompoundStatement] in the Fluent Node DSL and sets it to the + * Creates a new [BlockStatement] in the Fluent Node DSL and sets it to the * [SwitchStatement.statement] of the nearest enclosing [SwitchStatement]. The [init] block can be * used to create further sub-nodes as well as configuring the created node itself. */ context(SwitchStatement) -fun LanguageFrontend<*, *>.switchBody(init: CompoundStatement.() -> Unit): CompoundStatement { - val node = newCompoundStatement() +fun LanguageFrontend<*, *>.switchBody(init: Block.() -> Unit): Block { + val node = newBlock() init(node) statement = node @@ -677,17 +670,14 @@ fun LanguageFrontend<*, *>.switchBody(init: CompoundStatement.() -> Unit): Compo } /** - * Creates a new [CompoundStatement] in the Fluent Node DSL and sets it to the - * [IfStatement.elseStatement] of the nearest enclosing [IfStatement]. The [init] block can be used - * to create further sub-nodes as well as configuring the created node itself. + * Creates a new [Block] in the Fluent Node DSL and sets it to the [IfStatement.elseStatement] of + * the nearest enclosing [IfStatement]. The [init] block can be used to create further sub-nodes as + * well as configuring the created node itself. */ context(IfStatement) -fun LanguageFrontend<*, *>.elseStmt( - needsScope: Boolean = true, - init: CompoundStatement.() -> Unit -): CompoundStatement { - val node = newCompoundStatement() +fun LanguageFrontend<*, *>.elseStmt(needsScope: Boolean = true, init: Block.() -> Unit): Block { + val node = newBlock() scopeIfNecessary(needsScope, node, init) elseStatement = node @@ -760,9 +750,9 @@ fun LanguageFrontend<*, *>.breakStmt(label: String? = null): BreakStatement { */ context(Holder) -fun LanguageFrontend<*, *>.case(caseExpr: Expression? = null): CaseStatement { +fun LanguageFrontend<*, *>.case(caseExpression: Expression? = null): CaseStatement { val node = newCaseStatement() - node.caseExpression = caseExpr + node.caseExpression = caseExpression // Only add this to a statement holder if the nearest holder is a statement holder val holder = this@Holder @@ -836,18 +826,17 @@ fun LanguageFrontend<*, *>.ile( } /** - * Creates a new [DeclaredReferenceExpression] in the Fluent Node DSL and invokes - * [ArgumentHolder.addArgument] of the nearest enclosing [Holder], but only if it is an - * [ArgumentHolder]. + * Creates a new [Reference] in the Fluent Node DSL and invokes [ArgumentHolder.addArgument] of the + * nearest enclosing [Holder], but only if it is an [ArgumentHolder]. */ context(Holder) fun LanguageFrontend<*, *>.ref( name: CharSequence, type: Type = unknownType(), - init: (DeclaredReferenceExpression.() -> Unit)? = null -): DeclaredReferenceExpression { - val node = newDeclaredReferenceExpression(name) + init: (Reference.() -> Unit)? = null +): Reference { + val node = newReference(name) node.type = type if (init != null) { @@ -1099,10 +1088,11 @@ context(LanguageFrontend<*, *>, Holder) fun Expression.conditional( condition: Expression, - thenExpr: Expression, - elseExpr: Expression + thenExpression: Expression, + elseExpression: Expression ): ConditionalExpression { - val node = (this@LanguageFrontend).newConditionalExpression(condition, thenExpr, elseExpr) + val node = + (this@LanguageFrontend).newConditionalExpression(condition, thenExpression, elseExpression) if (this@Holder is StatementHolder) { (this@Holder) += node diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/FieldDeclaration.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/FieldDeclaration.kt index e76b68b601..6986ec21e2 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/FieldDeclaration.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/FieldDeclaration.kt @@ -25,7 +25,6 @@ */ package de.fraunhofer.aisec.cpg.graph.declarations -import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression import java.util.* import org.apache.commons.lang3.builder.ToStringBuilder diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/FunctionDeclaration.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/FunctionDeclaration.kt index e92b8d7e7a..49466af8c8 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/FunctionDeclaration.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/FunctionDeclaration.kt @@ -30,8 +30,8 @@ import de.fraunhofer.aisec.cpg.graph.edge.Properties import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.propertyEqualsList import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdgeDelegate -import de.fraunhofer.aisec.cpg.graph.statements.CompoundStatement import de.fraunhofer.aisec.cpg.graph.statements.Statement +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Block import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression import de.fraunhofer.aisec.cpg.graph.types.Type import de.fraunhofer.aisec.cpg.isDerivedFrom @@ -42,7 +42,7 @@ import org.neo4j.ogm.annotation.Relationship /** Represents the declaration or definition of a function. */ open class FunctionDeclaration : ValueDeclaration(), DeclarationHolder { - /** The function body. Usually a [CompoundStatement]. */ + /** The function body. Usually a [Block]. */ @AST var body: Statement? = null /** @@ -54,7 +54,7 @@ open class FunctionDeclaration : ValueDeclaration(), DeclarationHolder { /** The list of function parameters. */ @Relationship(value = "PARAMETERS", direction = Relationship.Direction.OUTGOING) @AST - var parameterEdges = mutableListOf>() + var parameterEdges = mutableListOf>() /** Virtual property for accessing [parameterEdges] without property edges. */ var parameters by PropertyEdgeDelegate(FunctionDeclaration::parameterEdges) @@ -115,7 +115,7 @@ open class FunctionDeclaration : ValueDeclaration(), DeclarationHolder { val signature = parameters .stream() - .sorted(Comparator.comparingInt(ParamVariableDeclaration::argumentIndex)) + .sorted(Comparator.comparingInt(ParameterDeclaration::argumentIndex)) .collect(Collectors.toList()) return if (targetSignature.size < signature.size) { false @@ -172,16 +172,16 @@ open class FunctionDeclaration : ValueDeclaration(), DeclarationHolder { } fun getBodyStatementAs(i: Int, clazz: Class): T? { - if (body is CompoundStatement) { - val statement = (body as CompoundStatement).statements[i] + if (body is Block) { + val statement = (body as Block).statements[i] return if (clazz.isAssignableFrom(statement.javaClass)) clazz.cast(statement) else null } return null } /** - * A list of default expressions for each item in [parameters]. If a [ParamVariableDeclaration] - * has no default, the list will be null at this index. This list must have the same size as + * A list of default expressions for each item in [parameters]. If a [ParameterDeclaration] has + * no default, the list will be null at this index. This list must have the same size as * [parameters]. */ val defaultParameters: List @@ -205,14 +205,14 @@ open class FunctionDeclaration : ValueDeclaration(), DeclarationHolder { val signatureTypes: List get() = parameters.map { it.type } - fun addParameter(paramVariableDeclaration: ParamVariableDeclaration) { - val propertyEdge = PropertyEdge(this, paramVariableDeclaration) + fun addParameter(parameterDeclaration: ParameterDeclaration) { + val propertyEdge = PropertyEdge(this, parameterDeclaration) propertyEdge.addProperty(Properties.INDEX, parameters.size) parameterEdges.add(propertyEdge) } - fun removeParameter(paramVariableDeclaration: ParamVariableDeclaration) { - parameterEdges.removeIf { it.end == paramVariableDeclaration } + fun removeParameter(parameterDeclaration: ParameterDeclaration) { + parameterEdges.removeIf { it.end == parameterDeclaration } } override fun toString(): String { @@ -239,7 +239,7 @@ open class FunctionDeclaration : ValueDeclaration(), DeclarationHolder { override fun hashCode() = Objects.hash(super.hashCode(), body, parameters, throwsTypes) override fun addDeclaration(declaration: Declaration) { - if (declaration is ParamVariableDeclaration) { + if (declaration is ParameterDeclaration) { addIfNotContains(parameterEdges, declaration) } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/FunctionTemplateDeclaration.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/FunctionTemplateDeclaration.kt index 438104863e..ee452931cc 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/FunctionTemplateDeclaration.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/FunctionTemplateDeclaration.kt @@ -59,7 +59,7 @@ class FunctionTemplateDeclaration : TemplateDeclaration() { } override fun addDeclaration(declaration: Declaration) { - if (declaration is TypeParamDeclaration || declaration is ParamVariableDeclaration) { + if (declaration is TypeParameterDeclaration || declaration is ParameterDeclaration) { addIfNotContains(this.parameterEdges, declaration) } else if (declaration is FunctionDeclaration) { addIfNotContains(realizationEdges, declaration) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/MethodDeclaration.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/MethodDeclaration.kt index 500590d55b..edfe3e7a6c 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/MethodDeclaration.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/MethodDeclaration.kt @@ -26,7 +26,7 @@ package de.fraunhofer.aisec.cpg.graph.declarations import de.fraunhofer.aisec.cpg.graph.AST -import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Reference import de.fraunhofer.aisec.cpg.passes.CallResolver import de.fraunhofer.aisec.cpg.passes.VariableUsageResolver @@ -51,26 +51,25 @@ open class MethodDeclaration : FunctionDeclaration() { * created (usually in the form of a [VariableDeclaration] and added to the scope of the * [MethodDeclaration] by a language frontend. Furthermore, it must be manually set to the * [receiver] property of the method, since the scope manager cannot do this. If the name of the - * receiver, e.g., `this`, is used anywhere in the method body, a [DeclaredReferenceExpression] - * must be created by the language frontend, and its [DeclaredReferenceExpression.refersTo] - * property must point to this [receiver]. The latter is done automatically by the - * [VariableUsageResolver], which treats it like any other regular variable. + * receiver, e.g., `this`, is used anywhere in the method body, a [Reference] must be created by + * the language frontend, and its [Reference.refersTo] property must point to this [receiver]. + * The latter is done automatically by the [VariableUsageResolver], which treats it like any + * other regular variable. * * Some languages (for example Python) denote the first argument in a method declaration as the * receiver (e.g., in `def foo(self, arg1)`, `self` is the receiver). In this case, extra care * needs to be taken that for the first argument of the method, a [VariableDeclaration] is * created and stored in [receiver]. All other arguments must then be processed normally - * (usually into a [ParamVariableDeclaration]). This is also important because from the - * "outside" the method only has the remaining arguments, when called (e.g., - * `object.foo("myarg1")`). + * (usually into a [ParameterDeclaration]). This is also important because from the "outside" + * the method only has the remaining arguments, when called (e.g., `object.foo("myarg1")`). * * There is one special case that concerns the Java language: In Java, there also exists a * `super` keyword, which can be used to explicitly access methods or fields of the (single) - * superclass of the current class. In this case, a [DeclaredReferenceExpression] will also be - * created (with the name `super`) and it will also refer to this receiver, even though the - * receiver's name is `this`. This is one of the very few exceptions where the reference and its - * declaration do not share the same name. The [CallResolver] will recognize this and treat the - * scoping aspect of the super-call accordingly. + * superclass of the current class. In this case, a [Reference] will also be created (with the + * name `super`) and it will also refer to this receiver, even though the receiver's name is + * `this`. This is one of the very few exceptions where the reference and its declaration do not + * share the same name. The [CallResolver] will recognize this and treat the scoping aspect of + * the super-call accordingly. */ @AST var receiver: VariableDeclaration? = null } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/ParamVariableDeclaration.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/ParameterDeclaration.kt similarity index 92% rename from cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/ParamVariableDeclaration.kt rename to cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/ParameterDeclaration.kt index 68def66fe4..d515eaf588 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/ParamVariableDeclaration.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/ParameterDeclaration.kt @@ -32,7 +32,7 @@ import java.util.* import org.neo4j.ogm.annotation.Relationship /** A declaration of a function or nontype template parameter. */ -class ParamVariableDeclaration : ValueDeclaration(), HasDefault { +class ParameterDeclaration : ValueDeclaration(), HasDefault { var isVariadic = false @Relationship(value = "DEFAULT", direction = Relationship.Direction.OUTGOING) @@ -47,7 +47,7 @@ class ParamVariableDeclaration : ValueDeclaration(), HasDefault { override fun equals(other: Any?): Boolean { if (this === other) return true - if (other == null || other !is ParamVariableDeclaration) return false + if (other == null || other !is ParameterDeclaration) return false return super.equals(other) && isVariadic == other.isVariadic && defaultValue == other.defaultValue diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/ClassTemplateDeclaration.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/RecordTemplateDeclaration.kt similarity index 90% rename from cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/ClassTemplateDeclaration.kt rename to cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/RecordTemplateDeclaration.kt index 625e5713fa..4a50f04d19 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/ClassTemplateDeclaration.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/RecordTemplateDeclaration.kt @@ -34,8 +34,8 @@ import java.util.* import kotlin.collections.ArrayList import org.neo4j.ogm.annotation.Relationship -/** Node representing a declaration of a ClassTemplate */ -class ClassTemplateDeclaration : TemplateDeclaration() { +/** Node representing a declaration of a template class or struct */ +class RecordTemplateDeclaration : TemplateDeclaration() { /** * Edges pointing to all RecordDeclarations that are realized by the ClassTempalte. Before the * expansion pass there is only a single RecordDeclaration which is instantiated after the @@ -46,7 +46,7 @@ class ClassTemplateDeclaration : TemplateDeclaration() { val realizationEdges: MutableList> = ArrayList() override val realizations: List by - PropertyEdgeDelegate(ClassTemplateDeclaration::realizationEdges) + PropertyEdgeDelegate(RecordTemplateDeclaration::realizationEdges) fun addRealization(realizedRecord: RecordDeclaration) { val propertyEdge = PropertyEdge(this, realizedRecord) @@ -59,7 +59,7 @@ class ClassTemplateDeclaration : TemplateDeclaration() { } override fun addDeclaration(declaration: Declaration) { - if (declaration is TypeParamDeclaration || declaration is ParamVariableDeclaration) { + if (declaration is TypeParameterDeclaration || declaration is ParameterDeclaration) { addIfNotContains(super.parameterEdges, declaration) } else if (declaration is RecordDeclaration) { addIfNotContains(realizationEdges, declaration) @@ -70,7 +70,7 @@ class ClassTemplateDeclaration : TemplateDeclaration() { if (this === other) return true if (other == null || javaClass != other.javaClass) return false if (!super.equals(other)) return false - val that = other as ClassTemplateDeclaration + val that = other as RecordTemplateDeclaration return realizations == that.realizations && propertyEqualsList(realizationEdges, that.realizationEdges) && parameters == that.parameters && diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/TemplateDeclaration.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/TemplateDeclaration.kt index 7b29ed17ba..15d1f482c5 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/TemplateDeclaration.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/TemplateDeclaration.kt @@ -61,8 +61,8 @@ abstract class TemplateDeclaration : Declaration(), DeclarationHolder { val parametersWithDefaults: MutableList = ArrayList() for (declaration in parameters) { if ( - (declaration is TypeParamDeclaration && declaration.default != null) || - (declaration is ParamVariableDeclaration && declaration.default != null) + (declaration is TypeParameterDeclaration && declaration.default != null) || + (declaration is ParameterDeclaration && declaration.default != null) ) { parametersWithDefaults.add(declaration) } @@ -74,22 +74,22 @@ abstract class TemplateDeclaration : Declaration(), DeclarationHolder { get() { val defaults: MutableList = ArrayList() for (declaration in parameters) { - if (declaration is TypeParamDeclaration) { + if (declaration is TypeParameterDeclaration) { defaults.add(declaration.default) - } else if (declaration is ParamVariableDeclaration) { + } else if (declaration is ParameterDeclaration) { defaults.add(declaration.default) } } return defaults } - fun addParameter(parameterizedType: TypeParamDeclaration) { + fun addParameter(parameterizedType: TypeParameterDeclaration) { val propertyEdge = PropertyEdge(this, parameterizedType) propertyEdge.addProperty(Properties.INDEX, parameterEdges.size) parameterEdges.add(propertyEdge) } - fun addParameter(nonTypeTemplateParamDeclaration: ParamVariableDeclaration) { + fun addParameter(nonTypeTemplateParamDeclaration: ParameterDeclaration) { val propertyEdge = PropertyEdge(this, nonTypeTemplateParamDeclaration) propertyEdge.addProperty(Properties.INDEX, parameterEdges.size) parameterEdges.add(propertyEdge) @@ -102,11 +102,11 @@ abstract class TemplateDeclaration : Declaration(), DeclarationHolder { return list } - fun removeParameter(parameterizedType: TypeParamDeclaration?) { + fun removeParameter(parameterizedType: TypeParameterDeclaration?) { parameterEdges.removeIf { it.end == parameterizedType } } - fun removeParameter(nonTypeTemplateParamDeclaration: ParamVariableDeclaration?) { + fun removeParameter(nonTypeTemplateParamDeclaration: ParameterDeclaration?) { parameterEdges.removeIf { it.end == nonTypeTemplateParamDeclaration } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/TypeParamDeclaration.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/TypeParameterDeclaration.kt similarity index 93% rename from cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/TypeParamDeclaration.kt rename to cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/TypeParameterDeclaration.kt index cde9143f37..75e9f05d11 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/TypeParamDeclaration.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/TypeParameterDeclaration.kt @@ -32,7 +32,7 @@ import java.util.* import org.neo4j.ogm.annotation.Relationship /** A declaration of a type template parameter */ -class TypeParamDeclaration : ValueDeclaration(), HasDefault { +class TypeParameterDeclaration : ValueDeclaration(), HasDefault { /** TemplateParameters can define a default for the type parameter. */ @Relationship(value = "DEFAULT", direction = Relationship.Direction.OUTGOING) @AST @@ -41,7 +41,7 @@ class TypeParamDeclaration : ValueDeclaration(), HasDefault { override fun equals(other: Any?): Boolean { if (this === other) return true if (other == null || javaClass != other.javaClass) return false - val that = other as TypeParamDeclaration + val that = other as TypeParameterDeclaration return super.equals(that) && default == that.default } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/UsingDirective.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/UsingDeclaration.kt similarity index 88% rename from cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/UsingDirective.kt rename to cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/UsingDeclaration.kt index 696df394c6..fd2e1cb1d8 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/UsingDirective.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/UsingDeclaration.kt @@ -27,13 +27,13 @@ package de.fraunhofer.aisec.cpg.graph.declarations import java.util.Objects -// TODO: Documentation -class UsingDirective : Declaration() { +/** Represents a using directive used to extend the currently valid name scope. */ +class UsingDeclaration : Declaration() { var qualifiedName: String? = null override fun equals(other: Any?): Boolean { if (this === other) return true - if (other !is UsingDirective) return false + if (other !is UsingDeclaration) return false return super.equals(other) && qualifiedName == other.qualifiedName } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/ValueDeclaration.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/ValueDeclaration.kt index 216c175400..3204d7cfd5 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/ValueDeclaration.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/ValueDeclaration.kt @@ -30,7 +30,7 @@ import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.edge.Properties import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.unwrap -import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Reference import de.fraunhofer.aisec.cpg.graph.types.* import de.fraunhofer.aisec.cpg.helpers.identitySetOf import de.fraunhofer.aisec.cpg.passes.VariableUsageResolver @@ -72,23 +72,23 @@ abstract class ValueDeclaration : Declaration(), HasType { } /** - * Links to all the [DeclaredReferenceExpression]s accessing the variable and the respective - * access value (read, write, readwrite). + * Links to all the [Reference]s accessing the variable and the respective access value (read, + * write, readwrite). */ @PopulatedByPass(VariableUsageResolver::class) @Relationship(value = "USAGE") - var usageEdges: MutableList> = ArrayList() + var usageEdges: MutableList> = ArrayList() /** All usages of the variable/field. */ @PopulatedByPass(VariableUsageResolver::class) - var usages: List + var usages: List get() = unwrap(usageEdges, true) /** Set all usages of the variable/field and assembles the access properties. */ set(usages) { usageEdges = usages .stream() - .map { ref: DeclaredReferenceExpression -> + .map { ref: Reference -> val edge = PropertyEdge(this, ref) edge.addProperty(Properties.ACCESS, ref.access) edge @@ -97,7 +97,7 @@ abstract class ValueDeclaration : Declaration(), HasType { } /** Adds a usage of the variable/field and assembles the access property. */ - fun addUsage(reference: DeclaredReferenceExpression) { + fun addUsage(reference: Reference) { val usageEdge = PropertyEdge(this, reference) usageEdge.addProperty(Properties.ACCESS, reference.access) usageEdges.add(usageEdge) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/VariableDeclaration.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/VariableDeclaration.kt index 0cd98b7f1b..c6e7b5b982 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/VariableDeclaration.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/VariableDeclaration.kt @@ -27,8 +27,8 @@ package de.fraunhofer.aisec.cpg.graph.declarations import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.statements.expressions.ConstructExpression -import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Reference import de.fraunhofer.aisec.cpg.graph.types.AutoType import de.fraunhofer.aisec.cpg.graph.types.HasType import de.fraunhofer.aisec.cpg.graph.types.TupleType @@ -67,7 +67,7 @@ open class VariableDeclaration : ValueDeclaration(), HasInitializer, HasType.Typ set(value) { field?.unregisterTypeObserver(this) field = value - if (value is DeclaredReferenceExpression) { + if (value is Reference) { value.resolutionHelper = this } value?.registerTypeObserver(this) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/scopes/BlockScope.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/scopes/BlockScope.kt index a3a4c8d4fb..1b038e8b80 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/scopes/BlockScope.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/scopes/BlockScope.kt @@ -26,10 +26,13 @@ package de.fraunhofer.aisec.cpg.graph.scopes import de.fraunhofer.aisec.cpg.graph.statements.BreakStatement -import de.fraunhofer.aisec.cpg.graph.statements.CompoundStatement +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Block -class BlockScope(blockStatement: CompoundStatement) : - ValueDeclarationScope(blockStatement), Breakable { +/** + * Scope of validity associated to a block of statements. Variables declared inside a block are not + * visible outside. + */ +class BlockScope(blockStatement: Block) : ValueDeclarationScope(blockStatement), Breakable { private val breaks: MutableList = ArrayList() override fun addBreakStatement(breakStatement: BreakStatement) { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/scopes/ValueDeclarationScope.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/scopes/ValueDeclarationScope.kt index 008c4aa2e5..60cac11566 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/scopes/ValueDeclarationScope.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/scopes/ValueDeclarationScope.kt @@ -86,7 +86,7 @@ open class ValueDeclarationScope(override var astNode: Node?) : Scope(astNode) { There are nodes where we do not set the declaration when storing them in the scope, mostly for structures that have a single value-declaration: WhileStatement, DoStatement, ForStatement, SwitchStatement; and others where the location of declaration is somewhere - deeper in the AST-subtree: CompoundStatement, AssertStatement. + deeper in the AST-subtree: Block, AssertStatement. */ } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ASMDeclarationStatement.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ASMDeclarationStatement.kt index 7762cedf89..4c75abff9c 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ASMDeclarationStatement.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ASMDeclarationStatement.kt @@ -24,5 +24,5 @@ * */ package de.fraunhofer.aisec.cpg.graph.statements - +// TODO Merge and/or refactor class ASMDeclarationStatement : DeclarationStatement() diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/CatchClause.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/CatchClause.kt index a07ab7525e..5c0619c9f8 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/CatchClause.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/CatchClause.kt @@ -29,12 +29,13 @@ import de.fraunhofer.aisec.cpg.graph.AST import de.fraunhofer.aisec.cpg.graph.BranchingNode import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Block import java.util.Objects class CatchClause : Statement(), BranchingNode { @AST var parameter: VariableDeclaration? = null - @AST var body: CompoundStatement? = null + @AST var body: Block? = null override val branchedBy: Node? get() = parameter diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/DoStatement.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/DoStatement.kt index 8f8f583558..4ec062bea7 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/DoStatement.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/DoStatement.kt @@ -39,7 +39,7 @@ class DoStatement : Statement() { /** * The statement that is going to be executed and re-executed, until the condition evaluates to - * false for the first time. Usually a [CompoundStatement]. + * false for the first time. Usually a [Block]. */ @AST var statement: Statement? = null diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ForEachStatement.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ForEachStatement.kt index 05cce46a10..8060ed57bc 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ForEachStatement.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ForEachStatement.kt @@ -27,7 +27,8 @@ package de.fraunhofer.aisec.cpg.graph.statements import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge -import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Block +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Reference import java.util.Objects class ForEachStatement : Statement(), BranchingNode, StatementHolder { @@ -38,7 +39,7 @@ class ForEachStatement : Statement(), BranchingNode, StatementHolder { @AST var variable: Statement? = null set(value) { - if (value is DeclaredReferenceExpression) { + if (value is Reference) { value.access = AccessValues.WRITE } field = value @@ -72,13 +73,13 @@ class ForEachStatement : Statement(), BranchingNode, StatementHolder { iterable = s } else if (statement == null) { statement = s - } else if (statement !is CompoundStatement) { - val newStmt = newCompoundStatement() - statement?.let { newStmt.addStatement(it) } - newStmt.addStatement(s) - statement = newStmt + } else if (statement !is Block) { + val block = newBlock() + statement?.let { block.addStatement(it) } + block.addStatement(s) + statement = block } else { - (statement as? CompoundStatement)?.addStatement(s) + (statement as? Block)?.addStatement(s) } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/IfStatement.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/IfStatement.kt index 602348d241..88905bc585 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/IfStatement.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/IfStatement.kt @@ -51,15 +51,11 @@ class IfStatement : Statement(), BranchingNode, ArgumentHolder { /** C++ constexpr construct. */ var isConstExpression = false - /** - * The statement that is executed, if the condition is evaluated as true. Usually a - * [CompoundStatement]. - */ + /** The statement that is executed, if the condition is evaluated as true. Usually a [Block]. */ @AST var thenStatement: Statement? = null /** - * The statement that is executed, if the condition is evaluated as false. Usually a - * [CompoundStatement]. + * The statement that is executed, if the condition is evaluated as false. Usually a [Block]. */ @AST var elseStatement: Statement? = null diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/SynchronizedStatement.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/SynchronizedStatement.kt index 7640e2b1ab..c6ed76a482 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/SynchronizedStatement.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/SynchronizedStatement.kt @@ -26,21 +26,20 @@ package de.fraunhofer.aisec.cpg.graph.statements import de.fraunhofer.aisec.cpg.graph.AST +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Block import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression import java.util.Objects class SynchronizedStatement : Statement() { @AST var expression: Expression? = null - @AST var blockStatement: CompoundStatement? = null + @AST var block: Block? = null override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is SynchronizedStatement) return false - return super.equals(other) && - expression == other.expression && - blockStatement == other.blockStatement + return super.equals(other) && expression == other.expression && block == other.block } - override fun hashCode() = Objects.hash(super.hashCode(), expression, blockStatement) + override fun hashCode() = Objects.hash(super.hashCode(), expression, block) } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/TryStatement.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/TryStatement.kt index 0ac0dc6643..d92011554d 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/TryStatement.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/TryStatement.kt @@ -29,6 +29,7 @@ import de.fraunhofer.aisec.cpg.graph.AST import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.propertyEqualsList import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdgeDelegate +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Block import java.util.* import org.neo4j.ogm.annotation.Relationship @@ -40,9 +41,9 @@ class TryStatement : Statement() { var resources by PropertyEdgeDelegate(TryStatement::resourceEdges) - @AST var tryBlock: CompoundStatement? = null + @AST var tryBlock: Block? = null - @AST var finallyBlock: CompoundStatement? = null + @AST var finallyBlock: Block? = null @Relationship(value = "CATCH_CLAUSES", direction = Relationship.Direction.OUTGOING) @AST diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/WhileStatement.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/WhileStatement.kt index 14b08f2180..b121990bcb 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/WhileStatement.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/WhileStatement.kt @@ -44,7 +44,7 @@ class WhileStatement : Statement(), BranchingNode, ArgumentHolder { /** * The statement that is going to be executed, until the condition evaluates to false for the - * first time. Usually a [CompoundStatement]. + * first time. Usually a [Block]. */ @AST var statement: Statement? = null diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/AssignExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/AssignExpression.kt index d48ebec724..448ce2e767 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/AssignExpression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/AssignExpression.kt @@ -60,11 +60,9 @@ class AssignExpression : set(value) { field = value if (operatorCode == "=") { - field.forEach { (it as? DeclaredReferenceExpression)?.access = AccessValues.WRITE } + field.forEach { (it as? Reference)?.access = AccessValues.WRITE } } else { - field.forEach { - (it as? DeclaredReferenceExpression)?.access = AccessValues.READWRITE - } + field.forEach { (it as? Reference)?.access = AccessValues.READWRITE } } } @@ -118,13 +116,13 @@ class AssignExpression : override var declarations = mutableListOf() /** Finds the value (of [rhs]) that is assigned to the particular [lhs] expression. */ - fun findValue(lhsExpr: HasType): Expression? { + fun findValue(lhsExpression: HasType): Expression? { return if (lhs.size > 1) { rhs.singleOrNull() } else { // Basically, we need to find out which index on the lhs this variable belongs to and // find the corresponding index on the rhs. - val idx = lhs.indexOf(lhsExpr) + val idx = lhs.indexOf(lhsExpression) if (idx == -1) { null } else { @@ -134,8 +132,8 @@ class AssignExpression : } /** Finds the targets(s) (within [lhs]) that are assigned to the particular [rhs] expression. */ - fun findTargets(rhsExpr: HasType): List { - val type = rhsExpr.type + fun findTargets(rhsExpression: HasType): List { + val type = rhsExpression.type // There are now two possibilities: Either, we have a tuple type, that we need to // deconstruct, or we have a singular type @@ -152,7 +150,7 @@ class AssignExpression : } else { // Basically, we need to find out which index on the rhs this variable belongs to and // find the corresponding index on the rhs. - val idx = rhs.indexOf(rhsExpr) + val idx = rhs.indexOf(rhsExpression) if (idx == -1) { listOf() } else { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/BinaryOperator.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/BinaryOperator.kt index dad65f97ee..f728c3f79c 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/BinaryOperator.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/BinaryOperator.kt @@ -78,12 +78,11 @@ open class BinaryOperator : private fun connectNewLhs(lhs: Expression) { lhs.registerTypeObserver(this) - if (lhs is DeclaredReferenceExpression && "=" == operatorCode) { + if (lhs is Reference && "=" == operatorCode) { // declared reference expr is the left-hand side of an assignment -> writing to the var lhs.access = AccessValues.WRITE } else if ( - lhs is DeclaredReferenceExpression && - operatorCode in (language?.compoundAssignmentOperators ?: setOf()) + lhs is Reference && operatorCode in (language?.compoundAssignmentOperators ?: setOf()) ) { // declared reference expr is the left-hand side of an assignment -> writing to the var lhs.access = AccessValues.READWRITE diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/CompoundStatement.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/Block.kt similarity index 88% rename from cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/CompoundStatement.kt rename to cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/Block.kt index 8f8257be8a..81d736fc22 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/CompoundStatement.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/Block.kt @@ -23,14 +23,14 @@ * \______/ \__| \______/ * */ -package de.fraunhofer.aisec.cpg.graph.statements +package de.fraunhofer.aisec.cpg.graph.statements.expressions import de.fraunhofer.aisec.cpg.graph.AST import de.fraunhofer.aisec.cpg.graph.StatementHolder import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge -import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.propertyEqualsList -import java.util.* +import de.fraunhofer.aisec.cpg.graph.statements.Statement +import java.util.Objects import org.apache.commons.lang3.builder.ToStringBuilder import org.neo4j.ogm.annotation.Relationship @@ -38,7 +38,7 @@ import org.neo4j.ogm.annotation.Relationship * A statement which contains a list of statements. A common example is a function body within a * [FunctionDeclaration]. */ -class CompoundStatement : Statement(), StatementHolder { +class Block : Expression(), StatementHolder { /** The list of statements. */ @Relationship(value = "STATEMENTS", direction = Relationship.Direction.OUTGOING) @AST @@ -59,10 +59,10 @@ class CompoundStatement : Statement(), StatementHolder { override fun equals(other: Any?): Boolean { if (this === other) return true - if (other !is CompoundStatement) return false + if (other !is Block) return false return super.equals(other) && this.statements == other.statements && - propertyEqualsList(statementEdges, other.statementEdges) + PropertyEdge.propertyEqualsList(statementEdges, other.statementEdges) } override fun hashCode() = Objects.hash(super.hashCode(), statements) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/CallExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/CallExpression.kt index 7b38f9cc39..c98fc1d3a8 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/CallExpression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/CallExpression.kt @@ -91,9 +91,8 @@ open class CallExpression : Expression(), HasType.TypeObserver, ArgumentHolder { /** * The expression that is being "called". This is currently not yet used in the [CallResolver] - * but will be in the future. In most cases, this is a [DeclaredReferenceExpression] and its - * [DeclaredReferenceExpression.refersTo] is intentionally left empty. It is not filled by the - * [VariableUsageResolver]. + * but will be in the future. In most cases, this is a [Reference] and its [Reference.refersTo] + * is intentionally left empty. It is not filled by the [VariableUsageResolver]. */ @AST var callee: Expression? = null diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/CompoundStatementExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/CompoundStatementExpression.kt deleted file mode 100644 index 584a5a8748..0000000000 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/CompoundStatementExpression.kt +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2020, 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.graph.statements.expressions - -import de.fraunhofer.aisec.cpg.graph.AST -import de.fraunhofer.aisec.cpg.graph.statements.Statement -import java.util.Objects - -/** - * An expression, which calls another function. It has a list of arguments (list of [ ]s) and is - * connected via the INVOKES edge to its [FunctionDeclaration]. - */ -class CompoundStatementExpression : Expression() { - /** The list of arguments. */ - @AST var statement: Statement? = null - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other !is CompoundStatementExpression) return false - return super.equals(other) && statement == other.statement - } - - override fun hashCode() = Objects.hash(super.hashCode(), statement) -} diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ConditionalExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ConditionalExpression.kt index 9ba7a88023..c13474224f 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ConditionalExpression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ConditionalExpression.kt @@ -40,7 +40,7 @@ class ConditionalExpression : Expression(), ArgumentHolder, BranchingNode, HasTy @AST var condition: Expression = ProblemExpression("could not parse condition expression") @AST - var thenExpr: Expression? = null + var thenExpression: Expression? = null set(value) { field?.unregisterTypeObserver(this) field = value @@ -48,7 +48,7 @@ class ConditionalExpression : Expression(), ArgumentHolder, BranchingNode, HasTy } @AST - var elseExpr: Expression? = null + var elseExpression: Expression? = null set(value) { field?.unregisterTypeObserver(this) field = value @@ -59,8 +59,8 @@ class ConditionalExpression : Expression(), ArgumentHolder, BranchingNode, HasTy return ToStringBuilder(this, TO_STRING_STYLE) .appendSuper(super.toString()) .append("condition", condition) - .append("thenExpr", thenExpr) - .append("elseExpr", elseExpr) + .append("thenExpr", thenExpression) + .append("elseExpr", elseExpression) .build() } @@ -79,8 +79,8 @@ class ConditionalExpression : Expression(), ArgumentHolder, BranchingNode, HasTy override fun typeChanged(newType: Type, src: HasType) { val types = mutableSetOf() - thenExpr?.type?.let { types.add(it) } - elseExpr?.type?.let { types.add(it) } + thenExpression?.type?.let { types.add(it) } + elseExpression?.type?.let { types.add(it) } val alternative = if (types.isNotEmpty()) types.first() else unknownType() this.type = types.commonType ?: alternative @@ -88,10 +88,10 @@ class ConditionalExpression : Expression(), ArgumentHolder, BranchingNode, HasTy override fun assignedTypeChanged(assignedTypes: Set, src: HasType) { // Merge and propagate the assigned types of our branches - if (src == thenExpr || src == elseExpr) { + if (src == thenExpression || src == elseExpression) { val types = mutableSetOf() - thenExpr?.assignedTypes?.let { types.addAll(it) } - elseExpr?.assignedTypes?.let { types.addAll(it) } + thenExpression?.assignedTypes?.let { types.addAll(it) } + elseExpression?.assignedTypes?.let { types.addAll(it) } addAssignedTypes(types) } } @@ -101,9 +101,10 @@ class ConditionalExpression : Expression(), ArgumentHolder, BranchingNode, HasTy if (other !is ConditionalExpression) return false return super.equals(other) && condition == other.condition && - thenExpr == other.thenExpr && - elseExpr == other.elseExpr + thenExpression == other.thenExpression && + elseExpression == other.elseExpression } - override fun hashCode() = Objects.hash(super.hashCode(), condition, thenExpr, elseExpr) + override fun hashCode() = + Objects.hash(super.hashCode(), condition, thenExpression, elseExpression) } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ConstructExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ConstructExpression.kt index 6c3e413591..e69ab80afd 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ConstructExpression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ConstructExpression.kt @@ -39,6 +39,7 @@ import org.apache.commons.lang3.builder.ToStringBuilder * as part of a [NewExpression]. * * In Java, it is the initializer of a [NewExpression]. */ +// TODO Merge and/or refactor class ConstructExpression : CallExpression() { /** * The link to the [ConstructorDeclaration]. This is populated by the diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ExplicitConstructorInvocation.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ConstructorCallExpression.kt similarity index 92% rename from cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ExplicitConstructorInvocation.kt rename to cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ConstructorCallExpression.kt index ec3c9a39fd..eda95685eb 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ExplicitConstructorInvocation.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ConstructorCallExpression.kt @@ -28,7 +28,8 @@ package de.fraunhofer.aisec.cpg.graph.statements.expressions import java.util.Objects import org.apache.commons.lang3.builder.ToStringBuilder -class ExplicitConstructorInvocation : CallExpression() { +// TODO Merge and/or refactor +class ConstructorCallExpression : CallExpression() { var containingClass: String? = null override fun toString(): String { @@ -40,7 +41,7 @@ class ExplicitConstructorInvocation : CallExpression() { override fun equals(other: Any?): Boolean { if (this === other) return true - if (other !is ExplicitConstructorInvocation) return false + if (other !is ConstructorCallExpression) return false return super.equals(other) && containingClass == other.containingClass } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/DesignatedInitializerExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/DesignatedInitializerExpression.kt index 4b69c0f91d..ecfe7450ad 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/DesignatedInitializerExpression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/DesignatedInitializerExpression.kt @@ -32,6 +32,7 @@ import java.util.Objects import org.apache.commons.lang3.builder.ToStringBuilder import org.neo4j.ogm.annotation.Relationship +// TODO Merge and/or refactor // TODO: Document this class! class DesignatedInitializerExpression : Expression() { @AST var rhs: Expression? = null diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/InitializerListExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/InitializerListExpression.kt index 9a03dd70a3..d3c064e6be 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/InitializerListExpression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/InitializerListExpression.kt @@ -42,6 +42,7 @@ import org.neo4j.ogm.annotation.Relationship * accurate as possible when propagating types, the [InitializerListExpression.type] property MUST * be set before adding any values to [InitializerListExpression.initializers]. */ +// TODO Merge and/or refactor class InitializerListExpression : Expression(), ArgumentHolder, HasType.TypeObserver { /** The list of initializers. */ @Relationship(value = "INITIALIZERS", direction = Relationship.Direction.OUTGOING) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/MemberExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/MemberExpression.kt index 59c9588064..3c599ee29d 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/MemberExpression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/MemberExpression.kt @@ -39,7 +39,7 @@ import org.apache.commons.lang3.builder.ToStringBuilder * use-case is access of a member function (method) as part of the [MemberCallExpression.callee] * property of a [MemberCallExpression]. */ -class MemberExpression : DeclaredReferenceExpression(), HasBase { +class MemberExpression : Reference(), HasBase { @AST override var base: Expression = ProblemExpression("could not parse base expression") set(value) { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ArrayCreationExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/NewArrayExpression.kt similarity index 92% rename from cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ArrayCreationExpression.kt rename to cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/NewArrayExpression.kt index dcf37a3c17..e2b466ebd8 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ArrayCreationExpression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/NewArrayExpression.kt @@ -37,7 +37,8 @@ import org.neo4j.ogm.annotation.Relationship * Expressions of the form `new Type[]` that represents the creation of an array, mostly used in * combination with a [VariableDeclaration]. */ -class ArrayCreationExpression : Expression() { +// TODO Merge and/or refactor with new Expression +class NewArrayExpression : Expression() { /** * The initializer of the expression, if present. Many languages, such as Java, either specify * [dimensions] or an initializer. @@ -58,7 +59,7 @@ class ArrayCreationExpression : Expression() { var dimensionEdges = mutableListOf>() /** Virtual property to access [dimensionEdges] without property edges. */ - var dimensions by PropertyEdgeDelegate(ArrayCreationExpression::dimensionEdges) + var dimensions by PropertyEdgeDelegate(NewArrayExpression::dimensionEdges) /** Adds an [Expression] to the existing [dimensions]. */ fun addDimension(expression: Expression) { @@ -67,7 +68,7 @@ class ArrayCreationExpression : Expression() { override fun equals(other: Any?): Boolean { if (this === other) return true - if (other !is ArrayCreationExpression) return false + if (other !is NewArrayExpression) return false return (super.equals(other) && initializer == other.initializer && dimensions == other.dimensions && diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/RangeExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/RangeExpression.kt index d25e242d2f..7fb5c2e839 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/RangeExpression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/RangeExpression.kt @@ -29,7 +29,7 @@ import java.util.* /** * Represents the specification of a range (e.g., of an array). Usually used in combination with an - * [ArraySubscriptionExpression] as the [ArraySubscriptionExpression.subscriptExpression]. + * [SubscriptExpression] as the [SubscriptExpression.subscriptExpression]. * * Examples can be found in Go: * ```go diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/DeclaredReferenceExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/Reference.kt similarity index 92% rename from cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/DeclaredReferenceExpression.kt rename to cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/Reference.kt index 0a7b589939..e7c06039fb 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/DeclaredReferenceExpression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/Reference.kt @@ -41,11 +41,10 @@ import org.neo4j.ogm.annotation.Relationship /** * An expression, which refers to something which is declared, e.g. a variable. For example, the - * expression `a = b`, which itself is an [AssignExpression], contains two - * [DeclaredReferenceExpression]s, one for the variable `a` and one for variable `b`, which have - * been previously been declared. + * expression `a = b`, which itself is an [AssignExpression], contains two [Reference]s, one for the + * variable `a` and one for variable `b`, which have been previously been declared. */ -open class DeclaredReferenceExpression : Expression(), HasType.TypeObserver { +open class Reference : Expression(), HasType.TypeObserver { /** * The [Declaration]s this expression might refer to. This will influence the [declaredType] of * this expression. @@ -82,8 +81,8 @@ open class DeclaredReferenceExpression : Expression(), HasType.TypeObserver { /** * This is a MAJOR workaround needed to resolve function pointers, until we properly re-design - * the call resolver. When this [DeclaredReferenceExpression] contains a function pointer - * reference that is assigned to a variable (or to another reference), we need to set + * the call resolver. When this [Reference] contains a function pointer reference that is + * assigned to a variable (or to another reference), we need to set */ var resolutionHelper: HasType? = null @@ -134,7 +133,7 @@ open class DeclaredReferenceExpression : Expression(), HasType.TypeObserver { if (this === other) { return true } - if (other !is DeclaredReferenceExpression) { + if (other !is Reference) { return false } return super.equals(other) && refersTo == other.refersTo diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ArraySubscriptionExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/SubscriptExpression.kt similarity index 92% rename from cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ArraySubscriptionExpression.kt rename to cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/SubscriptExpression.kt index 7206864f2c..bb3c7ddc0c 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ArraySubscriptionExpression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/SubscriptExpression.kt @@ -35,11 +35,8 @@ import java.util.* * ([arrayExpression]) and `index` ([subscriptExpression]) are of type [Expression]. CPP can * overload operators thus changing semantics of array access. */ -class ArraySubscriptionExpression : Expression(), HasBase, HasType.TypeObserver, ArgumentHolder { - /** - * The array on which the access is happening. This is most likely a - * [DeclaredReferenceExpression]. - */ +class SubscriptExpression : Expression(), HasBase, HasType.TypeObserver, ArgumentHolder { + /** The array on which the access is happening. This is most likely a [Reference]. */ @AST var arrayExpression: Expression = ProblemExpression("could not parse array expression") set(value) { @@ -51,8 +48,8 @@ class ArraySubscriptionExpression : Expression(), HasBase, HasType.TypeObserver, /** * The expression which represents the "subscription" or index on which the array is accessed. - * This can for example be a reference to another variable ([DeclaredReferenceExpression]), a - * [Literal] or a [RangeExpression]. + * This can for example be a reference to another variable ([Reference]), a [Literal] or a + * [RangeExpression]. */ @AST var subscriptExpression: Expression = ProblemExpression("could not parse index expression") @@ -116,7 +113,7 @@ class ArraySubscriptionExpression : Expression(), HasBase, HasType.TypeObserver, override fun equals(other: Any?): Boolean { if (this === other) return true - if (other !is ArraySubscriptionExpression) return false + if (other !is SubscriptExpression) return false return super.equals(other) && arrayExpression == other.arrayExpression && subscriptExpression == other.subscriptExpression diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/UnaryOperator.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/UnaryOperator.kt index fca3d2deda..6fde30092f 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/UnaryOperator.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/UnaryOperator.kt @@ -62,8 +62,8 @@ class UnaryOperator : Expression(), HasType.TypeObserver { if (operatorCode == "++" || operatorCode == "--") { access = AccessValues.READWRITE } - if (input is DeclaredReferenceExpression) { - (input as? DeclaredReferenceExpression)?.access = access + if (input is Reference) { + (input as? Reference)?.access = access } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/HasSecondaryTypeEdge.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/HasSecondaryTypeEdge.kt index 2f708cd20f..a4a47c667a 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/HasSecondaryTypeEdge.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/HasSecondaryTypeEdge.kt @@ -25,14 +25,15 @@ */ package de.fraunhofer.aisec.cpg.graph.types -import de.fraunhofer.aisec.cpg.graph.declarations.TypeParamDeclaration +import de.fraunhofer.aisec.cpg.graph.declarations.TypeParameterDeclaration import de.fraunhofer.aisec.cpg.passes.TypeResolver /** * The [TypeResolver] needs to be aware of all outgoing edges to types in order to merge equal types * to the same node. For the primary type edge, this is achieved through the [HasType] interface. If - * a node has additional type edges (e.g. [TypeParamDeclaration.default]) the node must implement - * the [updateType] method, so that the current type is always replaced with the merged one. + * a node has additional type edges (e.g. [TypeParameterDeclaration.default]) the node must + * implement the [updateType] method, so that the current type is always replaced with the merged + * one. */ fun interface HasSecondaryTypeEdge { fun updateType(typeState: Collection) 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 d5e2b6c78b..dccdae4da2 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 @@ -29,9 +29,9 @@ import de.fraunhofer.aisec.cpg.graph.ContextProvider import de.fraunhofer.aisec.cpg.graph.LanguageProvider import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.declarations.ValueDeclaration -import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression import de.fraunhofer.aisec.cpg.graph.statements.expressions.Literal +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Reference import de.fraunhofer.aisec.cpg.graph.statements.expressions.UnaryOperator /** @@ -47,8 +47,7 @@ interface HasType : ContextProvider, LanguageProvider { * following: * - the type declared by the [Node], e.g., by a [ValueDeclaration] * - intrinsically tied to the node, e.g. an [IntegerType] in an integer [Literal] - * - the [Type] of a declaration a node is referring to, e.g., in a - * [DeclaredReferenceExpression] + * - the [Type] of a declaration a node is referring to, e.g., in a [Reference] * * An implementation of this must be sure to invoke [informObservers]. */ diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/Util.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/Util.kt index ae9e080346..381005c1d0 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/Util.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/Util.kt @@ -374,19 +374,19 @@ object Util { * * @param n * @param branchingExp - * @param branchingDecl + * @param branchingDeclaration */ fun addDFGEdgesForMutuallyExclusiveBranchingExpression( n: Node, branchingExp: Node?, - branchingDecl: Node? + branchingDeclaration: Node? ) { var conditionNodes = mutableListOf() if (branchingExp != null) { conditionNodes = mutableListOf() conditionNodes.add(branchingExp) - } else if (branchingDecl != null) { - conditionNodes = getAdjacentDFGNodes(branchingDecl, true) + } else if (branchingDeclaration != null) { + conditionNodes = getAdjacentDFGNodes(branchingDeclaration, true) } conditionNodes.forEach { n.addPrevDFG(it) } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CXXCallResolverHelper.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CXXCallResolverHelper.kt index 17e807fd89..449416d5b4 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CXXCallResolverHelper.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CXXCallResolverHelper.kt @@ -304,7 +304,7 @@ fun resolveConstructorWithImplicitCast( * realizations of the Template 3. Set return type of the CallExpression and checks if it uses a * ParameterizedType and therefore has to be instantiated 4. Set Template Parameters Edge from the * CallExpression to all Instantiation Values 5. Set DFG Edges from instantiation to - * ParamVariableDeclaration in TemplateDeclaration + * ParameterDeclaration in TemplateDeclaration * * @param templateCall call to instantiate and invoke a function template * @param functionTemplateDeclaration functionTemplate we have identified that should be @@ -363,10 +363,10 @@ fun applyTemplateInstantiation( } } - // Add DFG edges from the instantiation Expression to the ParamVariableDeclaration in the + // Add DFG edges from the instantiation Expression to the ParameterDeclaration in the // Template. for ((declaration) in initializationSignature) { - if (declaration is ParamVariableDeclaration) { + if (declaration is ParameterDeclaration) { initializationSignature[declaration]?.let { declaration.addPrevDFG(it) } } } @@ -421,15 +421,15 @@ fun signatureWithImplicitCastTransformation( * * @param initialization mapping of the declaration of the template parameters to the explicit * values the template is instantiated with - * @return mapping of the parameterized types to the corresponding TypeParamDeclaration in the + * @return mapping of the parameterized types to the corresponding TypeParameterDeclaration in the * template */ fun getParameterizedSignaturesFromInitialization( initialization: Map -): Map { - val parameterizedSignature: MutableMap = HashMap() +): Map { + val parameterizedSignature: MutableMap = HashMap() for (templateParam in initialization.keys) { - if (templateParam is TypeParamDeclaration) { + if (templateParam is TypeParameterDeclaration) { parameterizedSignature[templateParam.type as ParameterizedType] = templateParam } } @@ -454,7 +454,7 @@ fun getParameterizedSignaturesFromInitialization( * @param explicitInstantiated list of all ParameterizedTypes which are explicitly instantiated * @return mapping containing the all elements of the signature of the TemplateDeclaration as key * and the Type/Expression the Parameter is initialized with. This function returns null if the - * {ParamVariableDeclaration, TypeParamDeclaration} do not match the provided value for + * {ParameterDeclaration, TypeParameterDeclaration} do not match the provided value for * initialization -> initialization not possible */ fun getTemplateInitializationSignature( @@ -516,7 +516,7 @@ fun getTemplateInitializationSignature( * @param explicitInstantiated list of all ParameterizedTypes which are explicitly instantiated * @return mapping containing the all elements of the signature of the TemplateDeclaration as key * and the Type/Expression the Parameter is initialized with. This function returns null if the - * {ParamVariableDeclaration, TypeParamDeclaration} do not match the provided value for + * {ParameterDeclaration, TypeParameterDeclaration} do not match the provided value for * initialization -> initialization not possible */ fun constructTemplateInitializationSignatureFromTemplateParameters( @@ -535,7 +535,7 @@ fun constructTemplateInitializationSignatureFromTemplateParameters( instantiationSignature[templateParameter] = callParameter instantiationType[callParameter] = TemplateDeclaration.TemplateInitialization.EXPLICIT - if (templateParameter is TypeParamDeclaration) { + if (templateParameter is TypeParameterDeclaration) { explicitInstantiated.add(templateParameter.type as ParameterizedType) } orderedInitializationSignature[templateParameter] = i @@ -561,19 +561,19 @@ fun constructTemplateInitializationSignatureFromTemplateParameters( * * @param callParameterArg * @param templateParameter - * @return If the TemplateParameter is an TypeParamDeclaration, the callParameter must be an - * ObjectType => returns true If the TemplateParameter is a ParamVariableDeclaration, the + * @return If the TemplateParameter is an TypeParameterDeclaration, the callParameter must be an + * ObjectType => returns true If the TemplateParameter is a ParameterDeclaration, the * callParameterArg must be an Expression and its type must match the type of the - * ParamVariableDeclaration (same type or subtype) => returns true Otherwise return false + * ParameterDeclaration (same type or subtype) => returns true Otherwise return false */ fun isInstantiated(callParameterArg: Node, templateParameter: Declaration?): Boolean { var callParameter = callParameterArg if (callParameter is TypeExpression) { callParameter = callParameter.type } - return if (callParameter is Type && templateParameter is TypeParamDeclaration) { + return if (callParameter is Type && templateParameter is TypeParameterDeclaration) { callParameter is ObjectType - } else if (callParameter is Expression && templateParameter is ParamVariableDeclaration) { + } else if (callParameter is Expression && templateParameter is ParameterDeclaration) { callParameter.type == templateParameter.type || callParameter.type.isDerivedFrom(templateParameter.type) } else { @@ -625,7 +625,7 @@ fun handleImplicitTemplateParameter( /** * @param function FunctionDeclaration realization of the template - * @param parameterizedTypeResolution mapping of ParameterizedTypes to the TypeParamDeclarations + * @param parameterizedTypeResolution mapping of ParameterizedTypes to the TypeParameterDeclarations * that define them, used to backwards resolve * @param initializationSignature mapping between the ParamDeclaration of the template and the * corresponding instantiations @@ -635,7 +635,7 @@ fun handleImplicitTemplateParameter( */ fun getCallSignature( function: FunctionDeclaration, - parameterizedTypeResolution: Map, + parameterizedTypeResolution: Map, initializationSignature: Map ): List { val templateCallSignature = mutableListOf() diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolver.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolver.kt index c951b2b510..b22aa025bd 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolver.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolver.kt @@ -140,8 +140,8 @@ open class CallResolver(ctx: TranslationContext) : SymbolResolverPass(ctx) { is TranslationUnitDeclaration -> { currentTU = node } - is ExplicitConstructorInvocation -> { - handleExplicitConstructorInvocation(node) + is ConstructorCallExpression -> { + handleConstructorCallExpression(node) } is ConstructExpression -> { // We might have call expressions inside our arguments, so in order to correctly @@ -223,7 +223,7 @@ open class CallResolver(ctx: TranslationContext) : SymbolResolverPass(ctx) { // Additionally, also set the REFERS_TO of the callee. In the future, we might make more // resolution decisions based on the callee itself. Unfortunately we can only set one here, // so we will take the first one - if (callee is DeclaredReferenceExpression) { + if (callee is Reference) { callee.refersTo = candidates.firstOrNull() } } @@ -241,7 +241,7 @@ open class CallResolver(ctx: TranslationContext) : SymbolResolverPass(ctx) { ): List? { return when (callee) { is MemberExpression -> resolveMemberCallee(callee, curClass, call) - is DeclaredReferenceExpression -> resolveReferenceCallee(callee, curClass, call) + is Reference -> resolveReferenceCallee(callee, curClass, call) null -> { Util.warnWithFileLocation( call, @@ -281,11 +281,11 @@ open class CallResolver(ctx: TranslationContext) : SymbolResolverPass(ctx) { } /** - * Resolves a [CallExpression.callee] of type [DeclaredReferenceExpression] to a possible list - * of [FunctionDeclaration] nodes. + * Resolves a [CallExpression.callee] of type [Reference] to a possible list of + * [FunctionDeclaration] nodes. */ protected fun resolveReferenceCallee( - callee: DeclaredReferenceExpression, + callee: Reference, curClass: RecordDeclaration?, call: CallExpression ): List { @@ -317,7 +317,7 @@ open class CallResolver(ctx: TranslationContext) : SymbolResolverPass(ctx) { * delegates resolving of regular function calls within classes to this function (meh!) */ fun resolveMemberCallee( - callee: DeclaredReferenceExpression, + callee: Reference, curClass: RecordDeclaration?, call: CallExpression ): List { @@ -326,8 +326,8 @@ open class CallResolver(ctx: TranslationContext) : SymbolResolverPass(ctx) { if ( curClass != null && callee is MemberExpression && - callee.base is DeclaredReferenceExpression && - isSuperclassReference(callee.base as DeclaredReferenceExpression) + callee.base is Reference && + isSuperclassReference(callee.base as Reference) ) { (callee.language as? HasSuperClasses)?.handleSuperCall( callee, @@ -429,7 +429,7 @@ open class CallResolver(ctx: TranslationContext) : SymbolResolverPass(ctx) { constructExpression.instantiates = recordDeclaration for (template in templateList) { if ( - template is ClassTemplateDeclaration && + template is RecordTemplateDeclaration && recordDeclaration != null && recordDeclaration in template.realizations && (constructExpression.templateParameters.size <= template.parameters.size) @@ -465,16 +465,18 @@ open class CallResolver(ctx: TranslationContext) : SymbolResolverPass(ctx) { } } - protected fun handleExplicitConstructorInvocation(eci: ExplicitConstructorInvocation) { - eci.containingClass?.let { containingClass -> - val recordDeclaration = recordMap[eci.parseName(containingClass)] - val signature = eci.arguments.map { it.type } + protected fun handleConstructorCallExpression( + constructorCallExpression: ConstructorCallExpression + ) { + constructorCallExpression.containingClass?.let { containingClass -> + val recordDeclaration = recordMap[constructorCallExpression.parseName(containingClass)] + val signature = constructorCallExpression.arguments.map { it.type } if (recordDeclaration != null) { val constructor = getConstructorDeclarationForExplicitInvocation(signature, recordDeclaration) val invokes = mutableListOf() invokes.add(constructor) - eci.invokes = invokes + constructorCallExpression.invokes = invokes } } } @@ -631,7 +633,7 @@ open class CallResolver(ctx: TranslationContext) : SymbolResolverPass(ctx) { /** * Adds implicit duplicates of the TemplateParams to the implicit ConstructExpression * - * @param templateParams of the VariableDeclaration/NewExpression + * @param templateParams of the VariableDeclaration/NewExpressionp * @param constructExpression duplicate TemplateParameters (implicit) to preserve AST, as * ConstructExpression uses AST as well as the VariableDeclaration/NewExpression */ diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPass.kt index 6ef8833b2d..a1e12fb5df 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPass.kt @@ -52,7 +52,7 @@ open class ControlDependenceGraphPass(ctx: TranslationContext) : TranslationUnit } /** - * Computes the CDG for the given [functionDecl]. It performs the following steps: + * Computes the CDG for the given [functionDeclaration]. It performs the following steps: * 1) Compute the "parent branching node" for each node and through which path the node is * reached * 2) Find out which branch of a [BranchingNode] is actually conditional. The other ones aren't. @@ -62,18 +62,19 @@ open class ControlDependenceGraphPass(ctx: TranslationContext) : TranslationUnit * parent node and the path(s) through which the [BranchingNode] node is reachable. 3.c) * Repeat step 3) until you cannot move the node upwards in the CDG anymore. */ - private fun handle(functionDecl: FunctionDeclaration) { + private fun handle(functionDeclaration: FunctionDeclaration) { // Maps nodes to their "cdg parent" (i.e. the dominator) and also has the information // through which path it is reached. If all outgoing paths of the node's dominator result in // the node, we use the dominator's state instead (i.e., we move the node one layer upwards) val startState = PrevEOGState() startState.push( - functionDecl, - PrevEOGLattice(mapOf(Pair(functionDecl, setOf(functionDecl)))) + functionDeclaration, + PrevEOGLattice(mapOf(Pair(functionDeclaration, setOf(functionDeclaration)))) ) - val finalState = iterateEOG(functionDecl.nextEOGEdges, startState, ::handleEdge) ?: return + val finalState = + iterateEOG(functionDeclaration.nextEOGEdges, startState, ::handleEdge) ?: return - val branchingNodeConditionals = getBranchingNodeConditions(functionDecl) + val branchingNodeConditionals = getBranchingNodeConditions(functionDeclaration) // Collect the information, identify merge points, etc. This is not really efficient yet :( for ((node, dominatorPaths) in finalState) { @@ -84,7 +85,10 @@ open class ControlDependenceGraphPass(ctx: TranslationContext) : TranslationUnit val finalDominators = mutableListOf>>() while (dominatorsList.isNotEmpty()) { val (k, v) = dominatorsList.removeFirst() - if (k != functionDecl && v.containsAll(branchingNodeConditionals[k] ?: setOf())) { + if ( + k != functionDeclaration && + v.containsAll(branchingNodeConditionals[k] ?: setOf()) + ) { // We are reachable from all the branches of branch. Add this parent to the // worklist or update an existing entry. Also consider already existing entries // in finalDominators list and update it (if necessary) @@ -125,12 +129,12 @@ open class ControlDependenceGraphPass(ctx: TranslationContext) : TranslationUnit * * This method collects the merging points. It also includes the function declaration itself. */ - private fun getBranchingNodeConditions(functionDecl: FunctionDeclaration) = + private fun getBranchingNodeConditions(functionDeclaration: FunctionDeclaration) = mapOf( // For the function declaration, there's only the path through the function declaration // itself. - Pair(functionDecl, setOf(functionDecl)), - *functionDecl + Pair(functionDeclaration, setOf(functionDeclaration)), + *functionDeclaration .allChildren() .map { branchingNode -> val mergingPoints = diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt index ab0e153547..72317c4760 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt @@ -38,10 +38,9 @@ import kotlin.contracts.ExperimentalContracts import kotlin.contracts.contract /** - * This pass determines the data flows of DeclaredReferenceExpressions which refer to a - * VariableDeclaration (not a field) while considering the control flow of a function. After this - * path, only such data flows are left which can occur when following the control flow (in terms of - * the EOG) of the program. + * This pass determines the data flows of References which refer to a VariableDeclaration (not a + * field) while considering the control flow of a function. After this path, only such data flows + * are left which can occur when following the control flow (in terms of the EOG) of the program. */ @OptIn(ExperimentalContracts::class) @DependsOn(EvaluationOrderGraphPass::class) @@ -108,10 +107,10 @@ open class ControlFlowSensitiveDFGPass(ctx: TranslationContext) : TranslationUni } /** - * Computes the previous write access of [currentEdge].end if it is a - * [DeclaredReferenceExpression] or [ValueDeclaration] based on the given [state] (which maps - * all variables to its last write instruction). It also updates the [state] if - * [currentEdge].end performs a write-operation to a variable. + * Computes the previous write access of [currentEdge].end if it is a [Reference] or + * [ValueDeclaration] based on the given [state] (which maps all variables to its last write + * instruction). It also updates the [state] if [currentEdge].end performs a write-operation to + * a variable. * * It further determines unnecessary implicit return statement which are added by some frontends * even if every path reaching this point already contains a return statement. @@ -122,7 +121,7 @@ open class ControlFlowSensitiveDFGPass(ctx: TranslationContext) : TranslationUni worklist: Worklist, Node, Set> ): State> { // We will set this if we write to a variable - var writtenDecl: Declaration? + var writtenDeclaration: Declaration? val currentNode = currentEdge.end val doubleState = state as DFGPassState @@ -153,8 +152,7 @@ open class ControlFlowSensitiveDFGPass(ctx: TranslationContext) : TranslationUni // correct mapping, we use the "assignments" property which already searches for us. currentNode.assignments.forEach { assignment -> // This was the last write to the respective declaration. - (assignment.target as? Declaration - ?: (assignment.target as? DeclaredReferenceExpression)?.refersTo) + (assignment.target as? Declaration ?: (assignment.target as? Reference)?.refersTo) ?.let { doubleState.declarationsState[it] = PowersetLattice(setOf(assignment.target as Node)) @@ -163,13 +161,13 @@ open class ControlFlowSensitiveDFGPass(ctx: TranslationContext) : TranslationUni } else if (isIncOrDec(currentNode)) { // Increment or decrement => Add the prevWrite of the input to the input. After the // operation, the prevWrite of the input's variable is this node. - val input = (currentNode as UnaryOperator).input as DeclaredReferenceExpression + val input = (currentNode as UnaryOperator).input as Reference // We write to the variable in the input - writtenDecl = input.refersTo + writtenDeclaration = input.refersTo - if (writtenDecl != null) { - state.push(input, doubleState.declarationsState[writtenDecl]) - doubleState.declarationsState[writtenDecl] = PowersetLattice(setOf(input)) + if (writtenDeclaration != null) { + state.push(input, doubleState.declarationsState[writtenDeclaration]) + doubleState.declarationsState[writtenDeclaration] = PowersetLattice(setOf(input)) } } else if (isCompoundAssignment(currentNode)) { // We write to the lhs, but it also serves as an input => We first get all previous @@ -177,17 +175,17 @@ open class ControlFlowSensitiveDFGPass(ctx: TranslationContext) : TranslationUni // The write operation goes to the variable in the lhs val lhs = currentNode.lhs.singleOrNull() - writtenDecl = (lhs as? DeclaredReferenceExpression)?.refersTo + writtenDeclaration = (lhs as? Reference)?.refersTo - if (writtenDecl != null && lhs != null) { + if (writtenDeclaration != null && lhs != null) { // Data flows from the last writes to the lhs variable to this node - state.push(lhs, doubleState.declarationsState[writtenDecl]) + state.push(lhs, doubleState.declarationsState[writtenDeclaration]) // The whole current node is the place of the last update, not (only) the lhs! - doubleState.declarationsState[writtenDecl] = PowersetLattice(setOf(lhs)) + doubleState.declarationsState[writtenDeclaration] = PowersetLattice(setOf(lhs)) } } else if ( - (currentNode as? DeclaredReferenceExpression)?.access == AccessValues.READ && + (currentNode as? Reference)?.access == AccessValues.READ && currentNode.refersTo is VariableDeclaration && currentNode.refersTo !is FieldDeclaration ) { @@ -211,10 +209,10 @@ open class ControlFlowSensitiveDFGPass(ctx: TranslationContext) : TranslationUni } // We wrote something to this variable declaration - writtenDecl = + writtenDeclaration = when (writtenTo) { is Declaration -> writtenTo - is DeclaredReferenceExpression -> writtenTo.refersTo + is Reference -> writtenTo.refersTo else -> { log.error( "The variable of type ${writtenTo?.javaClass} is not yet supported in the foreach loop" @@ -223,7 +221,7 @@ open class ControlFlowSensitiveDFGPass(ctx: TranslationContext) : TranslationUni } } - if (writtenTo is DeclaredReferenceExpression) { + if (writtenTo is Reference) { // This is a special case: We add the nextEOGEdge which goes out of the loop but // with the old previousWrites map. val nodesOutsideTheLoop = @@ -240,7 +238,7 @@ open class ControlFlowSensitiveDFGPass(ctx: TranslationContext) : TranslationUni state.push(writtenTo, PowersetLattice(setOf(iterable))) // Add the variable declaration (or the reference) to the list of previous // write nodes in this path - state.declarationsState[writtenDecl] = PowersetLattice(setOf(writtenTo)) + state.declarationsState[writtenDeclaration] = PowersetLattice(setOf(writtenTo)) } } } else if (currentNode is FunctionDeclaration) { @@ -268,7 +266,7 @@ open class ControlFlowSensitiveDFGPass(ctx: TranslationContext) : TranslationUni return currentNode is AssignExpression && currentNode.operatorCode in (currentNode.language?.compoundAssignmentOperators ?: setOf()) && - (currentNode.lhs.singleOrNull() as? DeclaredReferenceExpression)?.refersTo != null + (currentNode.lhs.singleOrNull() as? Reference)?.refersTo != null } protected fun isSimpleAssignment(currentNode: Node): Boolean { @@ -280,7 +278,7 @@ open class ControlFlowSensitiveDFGPass(ctx: TranslationContext) : TranslationUni protected fun isIncOrDec(currentNode: Node) = currentNode is UnaryOperator && (currentNode.operatorCode == "++" || currentNode.operatorCode == "--") && - (currentNode.input as? DeclaredReferenceExpression)?.refersTo != null + (currentNode.input as? Reference)?.refersTo != null /** * Removes the DFG edges for a potential implicit return statement if it is not in @@ -291,7 +289,7 @@ open class ControlFlowSensitiveDFGPass(ctx: TranslationContext) : TranslationUni reachableReturnStatements: Collection ) { val lastStatement = - ((node as? FunctionDeclaration)?.body as? CompoundStatement)?.statements?.lastOrNull() + ((node as? FunctionDeclaration)?.body as? Block)?.statements?.lastOrNull() if ( lastStatement is ReturnStatement && lastStatement.isImplicit && diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/DFGPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/DFGPass.kt index af73e32043..eb67dd0a3f 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/DFGPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/DFGPass.kt @@ -64,11 +64,11 @@ class DFGPass(ctx: TranslationContext) : ComponentPass(ctx) { is CastExpression -> handleCastExpression(node) is BinaryOperator -> handleBinaryOp(node, parent) is AssignExpression -> handleAssignExpression(node) - is ArrayCreationExpression -> handleArrayCreationExpression(node) - is ArraySubscriptionExpression -> handleArraySubscriptionExpression(node) + is NewArrayExpression -> handleNewArrayExpression(node) + is SubscriptExpression -> handleSubscriptExpression(node) is ConditionalExpression -> handleConditionalExpression(node) is MemberExpression -> handleMemberExpression(node, inferDfgForUnresolvedSymbols) - is DeclaredReferenceExpression -> handleDeclaredReferenceExpression(node) + is Reference -> handleReference(node) is ExpressionList -> handleExpressionList(node) is NewExpression -> handleNewExpression(node) // We keep the logic for the InitializerListExpression in that class because the @@ -118,7 +118,7 @@ class DFGPass(ctx: TranslationContext) : ComponentPass(ctx) { /** * For a [MemberExpression], the base flows to the expression if the field is not implemented in - * the code under analysis. Otherwise, it's handled as a [DeclaredReferenceExpression]. + * the code under analysis. Otherwise, it's handled as a [Reference]. */ protected fun handleMemberExpression( node: MemberExpression, @@ -127,7 +127,7 @@ class DFGPass(ctx: TranslationContext) : ComponentPass(ctx) { if (node.refersTo == null && inferDfgForUnresolvedCalls) { node.addPrevDFG(node.base) } else { - handleDeclaredReferenceExpression(node) + handleReference(node) } } @@ -309,12 +309,12 @@ class DFGPass(ctx: TranslationContext) : ComponentPass(ctx) { } /** - * Adds the DFG edges to a [DeclaredReferenceExpression] as follows: + * Adds the DFG edges to a [Reference] as follows: * - If the variable is written to, data flows from this node to the variable declaration. * - If the variable is read from, data flows from the variable declaration to this node. * - For a combined read and write, both edges for data flows are added. */ - protected fun handleDeclaredReferenceExpression(node: DeclaredReferenceExpression) { + protected fun handleReference(node: Reference) { node.refersTo?.let { when (node.access) { AccessValues.WRITE -> node.addNextDFG(it) @@ -332,22 +332,20 @@ class DFGPass(ctx: TranslationContext) : ComponentPass(ctx) { * expression to the whole expression. */ protected fun handleConditionalExpression(node: ConditionalExpression) { - node.thenExpr?.let { node.addPrevDFG(it) } - node.elseExpr?.let { node.addPrevDFG(it) } + node.thenExpression?.let { node.addPrevDFG(it) } + node.elseExpression?.let { node.addPrevDFG(it) } } /** - * Adds the DFG edge to an [ArraySubscriptionExpression]. The whole array `x` flows to the - * result `x[i]`. + * Adds the DFG edge to an [SubscriptExpression]. The whole array `x` flows to the result + * `x[i]`. */ - protected fun handleArraySubscriptionExpression(node: ArraySubscriptionExpression) { + protected fun handleSubscriptExpression(node: SubscriptExpression) { node.addPrevDFG(node.arrayExpression) } - /** - * Adds the DFG edge to an [ArrayCreationExpression]. The initializer flows to the expression. - */ - protected fun handleArrayCreationExpression(node: ArrayCreationExpression) { + /** Adds the DFG edge to an [NewArrayExpression]. The initializer flows to the expression. */ + protected fun handleNewArrayExpression(node: NewArrayExpression) { node.initializer?.let { node.addPrevDFG(it) } } @@ -364,7 +362,7 @@ class DFGPass(ctx: TranslationContext) : ComponentPass(ctx) { // Examples: a + (b = 1) or a = a == b ? b = 2: b = 3 // When the parent is a compound statement (or similar block of code), we can safely // assume that we're not in such a sub-expression - if (parent == null || parent !is CompoundStatement) { + if (parent == null || parent !is Block) { node.rhs.addNextDFG(node) } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt index 527a64a4b4..852cb314cc 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt @@ -60,8 +60,8 @@ import org.slf4j.LoggerFactory * * For methods without explicit return statement, EOF will have an edge to a virtual return node * with line number -1 which does not exist in the original code. A CFG will always end with the * last reachable statement(s) and not insert any virtual return statements. - * * EOG considers an opening blocking ("CompoundStatement", indicated by a "{") as a separate node. - * A CFG will rather use the first actual executable statement within the block. + * * EOG considers an opening blocking ("Block", indicated by a "{") as a separate node. A CFG will + * rather use the first actual executable statement within the block. * * For IF statements, EOG treats the "if" keyword and the condition as separate nodes. CFG treats * this as one "if" statement. * * EOG considers a method header as a node. CFG will consider the first executable statement of @@ -108,12 +108,10 @@ open class EvaluationOrderGraphPass(ctx: TranslationContext) : TranslationUnitPa } map[CallExpression::class.java] = { handleCallExpression(it as CallExpression) } map[MemberExpression::class.java] = { handleMemberExpression(it as MemberExpression) } - map[ArraySubscriptionExpression::class.java] = { - handleArraySubscriptionExpression(it as ArraySubscriptionExpression) - } - map[ArrayCreationExpression::class.java] = { - handleArrayCreationExpression(it as ArrayCreationExpression) + map[SubscriptExpression::class.java] = { + handleSubscriptExpression(it as SubscriptExpression) } + map[NewArrayExpression::class.java] = { handleNewArrayExpression(it as NewArrayExpression) } map[RangeExpression::class.java] = { handleRangeExpression(it as RangeExpression) } map[DeclarationStatement::class.java] = { handleDeclarationStatement(it as DeclarationStatement) @@ -122,10 +120,7 @@ open class EvaluationOrderGraphPass(ctx: TranslationContext) : TranslationUnitPa map[BinaryOperator::class.java] = { handleBinaryOperator(it as BinaryOperator) } map[AssignExpression::class.java] = { handleAssignExpression(it as AssignExpression) } map[UnaryOperator::class.java] = { handleUnaryOperator(it as UnaryOperator) } - map[CompoundStatement::class.java] = { handleCompoundStatement(it as CompoundStatement) } - map[CompoundStatementExpression::class.java] = { - handleCompoundStatementExpression(it as CompoundStatementExpression) - } + map[Block::class.java] = { handleBlock(it as Block) } map[IfStatement::class.java] = { handleIfStatement(it as IfStatement) } map[AssertStatement::class.java] = { handleAssertStatement(it as AssertStatement) } map[WhileStatement::class.java] = { handleWhileStatement(it as WhileStatement) } @@ -160,7 +155,7 @@ open class EvaluationOrderGraphPass(ctx: TranslationContext) : TranslationUnitPa map[Literal::class.java] = { handleDefault(it) } map[DefaultStatement::class.java] = { handleDefault(it) } map[TypeIdExpression::class.java] = { handleDefault(it) } - map[DeclaredReferenceExpression::class.java] = { handleDefault(it) } + map[Reference::class.java] = { handleDefault(it) } map[LambdaExpression::class.java] = { handleLambdaExpression(it as LambdaExpression) } } @@ -270,7 +265,7 @@ open class EvaluationOrderGraphPass(ctx: TranslationContext) : TranslationUnitPa // although they can be placed in the same enclosing declaration. val code = statementHolder.statements - val nonStaticCode = code.filter { (it as? CompoundStatement)?.isStaticBlock == false } + val nonStaticCode = code.filter { (it as? Block)?.isStaticBlock == false } val staticCode = code.filter { it !in nonStaticCode } pushToEOG(statementHolder as Node) @@ -421,7 +416,7 @@ open class EvaluationOrderGraphPass(ctx: TranslationContext) : TranslationUnitPa pushToEOG(node) } - protected fun handleArraySubscriptionExpression(node: ArraySubscriptionExpression) { + protected fun handleSubscriptExpression(node: SubscriptExpression) { // Connect according to evaluation order, first the array reference, then the contained // index. createEOG(node.arrayExpression) @@ -429,7 +424,7 @@ open class EvaluationOrderGraphPass(ctx: TranslationContext) : TranslationUnitPa pushToEOG(node) } - protected fun handleArrayCreationExpression(node: ArrayCreationExpression) { + protected fun handleNewArrayExpression(node: NewArrayExpression) { for (dimension in node.dimensions) { createEOG(dimension) } @@ -526,7 +521,7 @@ open class EvaluationOrderGraphPass(ctx: TranslationContext) : TranslationUnitPa pushToEOG(node) } - protected fun handleCompoundStatement(node: CompoundStatement) { + protected fun handleBlock(node: Block) { // not all language handle compound statements as scoping blocks, so we need to avoid // creating new scopes here scopeManager.enterScopeIfExists(node) @@ -581,11 +576,6 @@ open class EvaluationOrderGraphPass(ctx: TranslationContext) : TranslationUnitPa pushToEOG(node) } - protected fun handleCompoundStatementExpression(node: CompoundStatementExpression) { - createEOG(node.statement) - pushToEOG(node) - } - protected fun handleAssertStatement(node: AssertStatement) { createEOG(node.condition) val openConditionEOGs = ArrayList(currentPredecessors) @@ -819,7 +809,7 @@ open class EvaluationOrderGraphPass(ctx: TranslationContext) : TranslationUnitPa protected fun handleSynchronizedStatement(node: SynchronizedStatement) { createEOG(node.expression) pushToEOG(node) - createEOG(node.blockStatement) + createEOG(node.block) } protected fun handleConditionalExpression(node: ConditionalExpression) { @@ -829,11 +819,11 @@ open class EvaluationOrderGraphPass(ctx: TranslationContext) : TranslationUnitPa pushToEOG(node) val openConditionEOGs = ArrayList(currentPredecessors) nextEdgeProperties[Properties.BRANCH] = true - createEOG(node.thenExpr) + createEOG(node.thenExpression) openBranchNodes.addAll(currentPredecessors) setCurrentEOGs(openConditionEOGs) nextEdgeProperties[Properties.BRANCH] = false - createEOG(node.elseExpr) + createEOG(node.elseExpression) openBranchNodes.addAll(currentPredecessors) setCurrentEOGs(openBranchNodes) } @@ -935,9 +925,9 @@ open class EvaluationOrderGraphPass(ctx: TranslationContext) : TranslationUnitPa val compound = if (node.statement is DoStatement) { createEOG(node.statement) - (node.statement as DoStatement).statement as CompoundStatement + (node.statement as DoStatement).statement as Block } else { - node.statement as CompoundStatement + node.statement as Block } currentPredecessors = ArrayList() for (subStatement in compound.statements) { 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 65ae374e2e..85312e8555 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 @@ -57,9 +57,11 @@ open class ImportResolver(ctx: TranslationContext) : ComponentPass(ctx) { } } - protected fun getStaticImports(recordDecl: RecordDeclaration): MutableSet { + protected fun getStaticImports( + recordDeclaration: RecordDeclaration + ): MutableSet { val partitioned = - recordDecl.staticImportStatements.groupBy { it.endsWith("*") }.toMutableMap() + recordDeclaration.staticImportStatements.groupBy { it.endsWith("*") }.toMutableMap() val staticImports = mutableSetOf() val importPattern = Pattern.compile("(?.*)\\.(?.*)") diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/SymbolResolverPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/SymbolResolverPass.kt index 3f04655003..3746a4b45d 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/SymbolResolverPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/SymbolResolverPass.kt @@ -29,7 +29,7 @@ import de.fraunhofer.aisec.cpg.TranslationContext import de.fraunhofer.aisec.cpg.frontends.HasSuperClasses import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.declarations.* -import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Reference import de.fraunhofer.aisec.cpg.graph.types.* import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker import de.fraunhofer.aisec.cpg.passes.order.DependsOn @@ -99,7 +99,7 @@ abstract class SymbolResolverPass(ctx: TranslationContext) : ComponentPass(ctx) /** * Determines if the [reference] refers to the super class and we have to start searching there. */ - protected fun isSuperclassReference(reference: DeclaredReferenceExpression): Boolean { + protected fun isSuperclassReference(reference: Reference): Boolean { val language = reference.language return language is HasSuperClasses && reference.name.endsWith(language.superClassKeyword) } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TemplateCallResolverHelper.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TemplateCallResolverHelper.kt index ad5b11dacd..d0887d7cf2 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TemplateCallResolverHelper.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TemplateCallResolverHelper.kt @@ -26,13 +26,13 @@ package de.fraunhofer.aisec.cpg.passes import de.fraunhofer.aisec.cpg.graph.Node -import de.fraunhofer.aisec.cpg.graph.declarations.ClassTemplateDeclaration -import de.fraunhofer.aisec.cpg.graph.declarations.ParamVariableDeclaration +import de.fraunhofer.aisec.cpg.graph.declarations.ParameterDeclaration +import de.fraunhofer.aisec.cpg.graph.declarations.RecordTemplateDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.TemplateDeclaration -import de.fraunhofer.aisec.cpg.graph.declarations.TypeParamDeclaration +import de.fraunhofer.aisec.cpg.graph.declarations.TypeParameterDeclaration import de.fraunhofer.aisec.cpg.graph.objectType import de.fraunhofer.aisec.cpg.graph.statements.expressions.ConstructExpression -import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Reference import de.fraunhofer.aisec.cpg.graph.statements.expressions.TypeExpression import de.fraunhofer.aisec.cpg.graph.types.ObjectType import de.fraunhofer.aisec.cpg.graph.types.Type @@ -47,7 +47,7 @@ import de.fraunhofer.aisec.cpg.graph.types.Type */ fun addRecursiveDefaultTemplateArgs( constructExpression: ConstructExpression, - template: ClassTemplateDeclaration + template: RecordTemplateDeclaration ) { var templateParameters: Int do { @@ -84,15 +84,15 @@ fun addRecursiveDefaultTemplateArgs( */ fun handleExplicitTemplateParameters( constructExpression: ConstructExpression, - template: ClassTemplateDeclaration, + template: RecordTemplateDeclaration, templateParametersExplicitInitialization: MutableMap ) { for (i in constructExpression.templateParameters.indices) { val explicit = constructExpression.templateParameters[i] - if (template.parameters[i] is TypeParamDeclaration) { + if (template.parameters[i] is TypeParameterDeclaration) { templateParametersExplicitInitialization[ - (template.parameters[i] as TypeParamDeclaration).type] = explicit - } else if (template.parameters[i] is ParamVariableDeclaration) { + (template.parameters[i] as TypeParameterDeclaration).type] = explicit + } else if (template.parameters[i] is ParameterDeclaration) { templateParametersExplicitInitialization[template.parameters[i]] = explicit } } @@ -109,7 +109,7 @@ fun handleExplicitTemplateParameters( * default (no recursive) */ fun applyMissingParams( - template: ClassTemplateDeclaration, + template: RecordTemplateDeclaration, constructExpression: ConstructExpression, templateParametersExplicitInitialization: Map, templateParameterRealDefaultInitialization: Map @@ -122,7 +122,7 @@ fun applyMissingParams( ) for (m in missingParams) { var missingParam = m - if (missingParam is DeclaredReferenceExpression) { + if (missingParam is Reference) { missingParam = missingParam.refersTo } if (missingParam in templateParametersExplicitInitialization) { @@ -172,14 +172,14 @@ fun applyMissingParams( * default (no recursive) */ fun handleDefaultTemplateParameters( - template: ClassTemplateDeclaration, + template: RecordTemplateDeclaration, templateParameterRealDefaultInitialization: MutableMap ) { val declaredTemplateTypes = mutableListOf() - val declaredNonTypeTemplate = mutableListOf() + val declaredNonTypeTemplate = mutableListOf() val parametersWithDefaults = template.parametersWithDefaults for (declaration in template.parameters) { - if (declaration is TypeParamDeclaration) { + if (declaration is TypeParameterDeclaration) { declaredTemplateTypes.add(declaration.type) if ( declaration.default !in declaredTemplateTypes && @@ -187,13 +187,12 @@ fun handleDefaultTemplateParameters( ) { templateParameterRealDefaultInitialization[declaration.type] = declaration.default } - } else if (declaration is ParamVariableDeclaration) { + } else if (declaration is ParameterDeclaration) { declaredNonTypeTemplate.add(declaration) if ( declaration in parametersWithDefaults && - (declaration.default !is DeclaredReferenceExpression || - (declaration.default as DeclaredReferenceExpression?)?.refersTo !in - declaredNonTypeTemplate) + (declaration.default !is Reference || + (declaration.default as Reference?)?.refersTo !in declaredNonTypeTemplate) ) { templateParameterRealDefaultInitialization[declaration] = declaration.default } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TypeHierarchyResolver.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TypeHierarchyResolver.kt index 026e9bbb92..54c922dc08 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TypeHierarchyResolver.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TypeHierarchyResolver.kt @@ -101,10 +101,14 @@ open class TypeHierarchyResolver(ctx: TranslationContext) : ComponentPass(ctx) { return supertypeRecords.map { it.methods }.flatten() } - protected fun findSupertypeRecords(recordDecl: RecordDeclaration): Set { + protected fun findSupertypeRecords( + recordDeclaration: RecordDeclaration + ): Set { val superTypeDeclarations = - recordDecl.superTypes.mapNotNull { (it as? ObjectType)?.recordDeclaration }.toSet() - recordDecl.superTypeDeclarations = superTypeDeclarations + recordDeclaration.superTypes + .mapNotNull { (it as? ObjectType)?.recordDeclaration } + .toSet() + recordDeclaration.superTypeDeclarations = superTypeDeclarations return superTypeDeclarations } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/VariableUsageResolver.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/VariableUsageResolver.kt index d28e6c1e49..80c6b2a30a 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/VariableUsageResolver.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/VariableUsageResolver.kt @@ -31,9 +31,9 @@ import de.fraunhofer.aisec.cpg.frontends.HasSuperClasses import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.declarations.* import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression -import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberCallExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberExpression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Reference import de.fraunhofer.aisec.cpg.graph.types.* import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker.ScopedWalker import de.fraunhofer.aisec.cpg.helpers.Util @@ -46,17 +46,17 @@ import org.slf4j.LoggerFactory * Creates new connections between the place where a variable is declared and where it is used. * * A field access is modeled with a [MemberExpression]. After AST building, its base and member - * references are set to [DeclaredReferenceExpression] stubs. This pass resolves those references - * and makes the member point to the appropriate [FieldDeclaration] and the base to the "this" - * [FieldDeclaration] of the containing class. It is also capable of resolving references to fields - * that are inherited from a superclass and thus not declared in the actual base class. When base or - * member declarations are not found in the graph, a new "inferred" [FieldDeclaration] is being - * created that is then used to collect all usages to the same unknown declaration. - * [DeclaredReferenceExpression] stubs are removed from the graph after being resolved. + * references are set to [Reference] stubs. This pass resolves those references and makes the member + * point to the appropriate [FieldDeclaration] and the base to the "this" [FieldDeclaration] of the + * containing class. It is also capable of resolving references to fields that are inherited from a + * superclass and thus not declared in the actual base class. When base or member declarations are + * not found in the graph, a new "inferred" [FieldDeclaration] is being created that is then used to + * collect all usages to the same unknown declaration. [Reference] stubs are removed from the graph + * after being resolved. * - * Accessing a local variable is modeled directly with a [DeclaredReferenceExpression]. This step of - * the pass doesn't remove the [DeclaredReferenceExpression] nodes like in the field usage case but - * rather makes their "refersTo" point to the appropriate [ValueDeclaration]. + * Accessing a local variable is modeled directly with a [Reference]. This step of the pass doesn't + * remove the [Reference] nodes like in the field usage case but rather makes their "refersTo" point + * to the appropriate [ValueDeclaration]. */ @DependsOn(TypeHierarchyResolver::class) open class VariableUsageResolver(ctx: TranslationContext) : SymbolResolverPass(ctx) { @@ -89,7 +89,7 @@ open class VariableUsageResolver(ctx: TranslationContext) : SymbolResolverPass(c /** This function seems to resolve function pointers pointing to a [MethodDeclaration]. */ protected fun resolveMethodFunctionPointer( - reference: DeclaredReferenceExpression, + reference: Reference, type: FunctionPointerType ): ValueDeclaration { val parent = reference.name.parent @@ -112,7 +112,7 @@ open class VariableUsageResolver(ctx: TranslationContext) : SymbolResolverPass(c ) { val language = current?.language - if (current !is DeclaredReferenceExpression || current is MemberExpression) return + if (current !is Reference || current is MemberExpression) return // 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 @@ -223,8 +223,8 @@ open class VariableUsageResolver(ctx: TranslationContext) : SymbolResolverPass(c } var baseTarget: Declaration? = null - if (current.base is DeclaredReferenceExpression) { - val base = current.base as DeclaredReferenceExpression + if (current.base is Reference) { + val base = current.base as Reference if ( current.language is HasSuperClasses && base.name.toString() == (current.language as HasSuperClasses).superClassKeyword @@ -264,7 +264,7 @@ open class VariableUsageResolver(ctx: TranslationContext) : SymbolResolverPass(c base.type = objectType } } else { - baseTarget = resolveBase(current.base as DeclaredReferenceExpression) + baseTarget = resolveBase(current.base as Reference) base.refersTo = baseTarget } if (baseTarget is EnumDeclaration) { @@ -298,7 +298,7 @@ open class VariableUsageResolver(ctx: TranslationContext) : SymbolResolverPass(c current.refersTo = resolveMember(baseType, current) } - protected fun resolveBase(reference: DeclaredReferenceExpression): Declaration? { + protected fun resolveBase(reference: Reference): Declaration? { val declaration = scopeManager.resolveReference(reference) if (declaration != null) { return declaration @@ -314,10 +314,7 @@ open class VariableUsageResolver(ctx: TranslationContext) : SymbolResolverPass(c } } - protected fun resolveMember( - containingClass: Type, - reference: DeclaredReferenceExpression - ): ValueDeclaration? { + protected fun resolveMember(containingClass: Type, reference: Reference): ValueDeclaration? { if (isSuperclassReference(reference)) { // if we have a "super" on the member side, this is a member call. We need to resolve // this in the call resolver instead @@ -346,10 +343,7 @@ open class VariableUsageResolver(ctx: TranslationContext) : SymbolResolverPass(c } // TODO(oxisto): Move to inference class - protected fun handleUnknownField( - base: Type, - ref: DeclaredReferenceExpression - ): FieldDeclaration? { + protected fun handleUnknownField(base: Type, ref: Reference): FieldDeclaration? { val name = ref.name // unwrap a potential pointer-type diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/Inference.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/Inference.kt index eb40b5606e..d8783edbe6 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/Inference.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/Inference.kt @@ -168,7 +168,7 @@ class Inference(val start: Node, override val ctx: TranslationContext) : for (i in signature.indices) { val targetType = signature[i] ?: UnknownType.getUnknownType(function.language) val paramName = generateParamName(i, targetType) - val param = newParamVariableDeclaration(paramName, targetType, false, "") + val param = newParameterDeclaration(paramName, targetType, false, "") param.argumentIndex = i scopeManager.addDeclaration(param) @@ -222,7 +222,7 @@ class Inference(val start: Node, override val ctx: TranslationContext) : return paramName.toString() } - private fun inferNonTypeTemplateParameter(name: String): ParamVariableDeclaration { + private fun inferNonTypeTemplateParameter(name: String): ParameterDeclaration { val expr = start as? Expression ?: throw UnsupportedOperationException( @@ -230,16 +230,16 @@ class Inference(val start: Node, override val ctx: TranslationContext) : ) // Non-Type Template Parameter - return newParamVariableDeclaration(name, expr.type, false, name) + return newParameterDeclaration(name, expr.type, false, name) } private fun inferTemplateParameter( name: String, - ): TypeParamDeclaration { + ): TypeParameterDeclaration { val parameterizedType = ParameterizedType(name, language) typeManager.addTypeParameter(start as FunctionTemplateDeclaration, parameterizedType) - val decl = newTypeParamDeclaration(name, name) + val decl = newTypeParameterDeclaration(name, name) decl.type = parameterizedType return decl diff --git a/cpg-core/src/main/resources/META-INF/native-image/de.fraunhofer.aisec/cpg/reflect-config.json b/cpg-core/src/main/resources/META-INF/native-image/de.fraunhofer.aisec/cpg/reflect-config.json index b67c695ecf..5aadb249d7 100644 --- a/cpg-core/src/main/resources/META-INF/native-image/de.fraunhofer.aisec/cpg/reflect-config.json +++ b/cpg-core/src/main/resources/META-INF/native-image/de.fraunhofer.aisec/cpg/reflect-config.json @@ -80,7 +80,7 @@ "allDeclaredMethods": true }, { - "name": "de.fraunhofer.aisec.cpg.graph.ArrayCreationExpression", + "name": "de.fraunhofer.aisec.cpg.graph.NewArrayExpression", "allDeclaredFields": true, "allDeclaredMethods": true }, @@ -90,7 +90,7 @@ "allDeclaredMethods": true }, { - "name": "de.fraunhofer.aisec.cpg.graph.ArraySubscriptionExpression", + "name": "de.fraunhofer.aisec.cpg.graph.SubscriptExpression", "allDeclaredFields": true, "allDeclaredMethods": true }, @@ -130,12 +130,12 @@ "allDeclaredMethods": true }, { - "name": "de.fraunhofer.aisec.cpg.graph.CompoundStatement", + "name": "de.fraunhofer.aisec.cpg.graph.BlockStatement", "allDeclaredFields": true, "allDeclaredMethods": true }, { - "name": "de.fraunhofer.aisec.cpg.graph.CompoundStatementExpression", + "name": "de.fraunhofer.aisec.cpg.graph.BlockStatementExpression", "allDeclaredFields": true, "allDeclaredMethods": true }, @@ -170,7 +170,7 @@ "allDeclaredMethods": true }, { - "name": "de.fraunhofer.aisec.cpg.graph.DeclaredReferenceExpression", + "name": "de.fraunhofer.aisec.cpg.graph.Reference", "allDeclaredFields": true, "allDeclaredMethods": true }, @@ -213,7 +213,7 @@ "allDeclaredMethods": true }, { - "name": "de.fraunhofer.aisec.cpg.graph.ExplicitConstructorInvocation", + "name": "de.fraunhofer.aisec.cpg.graph.ConstructorCallExpression", "allDeclaredFields": true, "allDeclaredMethods": true }, @@ -323,7 +323,7 @@ "allDeclaredMethods": true }, { - "name": "de.fraunhofer.aisec.cpg.graph.ParamVariableDeclaration", + "name": "de.fraunhofer.aisec.cpg.graph.ParameterDeclaration", "allDeclaredFields": true, "allDeclaredMethods": true }, diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/FluentTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/FluentTest.kt index 63bb5d4eec..f9be5aa68a 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/FluentTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/FluentTest.kt @@ -33,7 +33,6 @@ import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration import de.fraunhofer.aisec.cpg.graph.scopes.BlockScope import de.fraunhofer.aisec.cpg.graph.scopes.FunctionScope import de.fraunhofer.aisec.cpg.graph.scopes.GlobalScope -import de.fraunhofer.aisec.cpg.graph.statements.CompoundStatement import de.fraunhofer.aisec.cpg.graph.statements.DeclarationStatement import de.fraunhofer.aisec.cpg.graph.statements.IfStatement import de.fraunhofer.aisec.cpg.graph.statements.ReturnStatement @@ -82,7 +81,7 @@ class FluentTest { assertLocalName("argc", argc) assertLocalName("int", argc.type) - val body = main.body as? CompoundStatement + val body = main.body as? Block assertNotNull(body) assertTrue { body.scope is FunctionScope @@ -130,7 +129,7 @@ class FluentTest { assertNotNull(printf) assertEquals("else", printf.arguments[0]>()?.value) - var ref = condition.lhs() + var ref = condition.lhs() assertNotNull(ref) assertLocalName("argc", ref) @@ -157,7 +156,7 @@ class FluentTest { assertNotNull(binOp.scope) assertEquals("+", binOp.operatorCode) - ref = binOp.lhs as? DeclaredReferenceExpression + ref = binOp.lhs as? Reference assertNotNull(ref) assertNotNull(ref.scope) assertNull(ref.refersTo) diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/ShortcutsTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/ShortcutsTest.kt index 49ba3c23a7..0663cc55eb 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/ShortcutsTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/ShortcutsTest.kt @@ -31,7 +31,6 @@ import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration 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.CompoundStatement import de.fraunhofer.aisec.cpg.graph.statements.DeclarationStatement import de.fraunhofer.aisec.cpg.graph.statements.IfStatement import de.fraunhofer.aisec.cpg.graph.statements.expressions.* @@ -69,15 +68,14 @@ class ShortcutsTest { val main = classDecl.byNameOrNull("main") assertNotNull(main) expected.add( - ((((main.body as CompoundStatement).statements[0] as DeclarationStatement) - .declarations[0] + ((((main.body as Block).statements[0] as DeclarationStatement).declarations[0] as VariableDeclaration) .initializer as NewExpression) .initializer as ConstructExpression ) - expected.add((main.body as CompoundStatement).statements[1] as MemberCallExpression) - expected.add((main.body as CompoundStatement).statements[2] as MemberCallExpression) - expected.add((main.body as CompoundStatement).statements[3] as MemberCallExpression) + expected.add((main.body as Block).statements[1] as MemberCallExpression) + expected.add((main.body as Block).statements[2] as MemberCallExpression) + expected.add((main.body as Block).statements[3] as MemberCallExpression) val print = classDecl.byNameOrNull("print") assertNotNull(print) @@ -88,7 +86,7 @@ class ShortcutsTest { assertTrue(actual.containsAll(expected)) assertEquals( - listOf((main.body as CompoundStatement).statements[1] as MemberCallExpression), + listOf((main.body as Block).statements[1] as MemberCallExpression), expected("print") ) } @@ -104,7 +102,7 @@ class ShortcutsTest { assertNotNull(classDecl) val main = classDecl.byNameOrNull("main") assertNotNull(main) - expected.add((main.body as CompoundStatement).statements[1] as MemberCallExpression) + expected.add((main.body as Block).statements[1] as MemberCallExpression) assertTrue(expected.containsAll(actual)) assertTrue(actual.containsAll(expected)) } @@ -131,8 +129,7 @@ class ShortcutsTest { val actual = main.callees expected.add( - (((((main.body as CompoundStatement).statements[0] as DeclarationStatement) - .declarations[0] + (((((main.body as Block).statements[0] as DeclarationStatement).declarations[0] as VariableDeclaration) .initializer as NewExpression) .initializer as ConstructExpression) @@ -177,18 +174,17 @@ class ShortcutsTest { assertNotNull(classDecl) val magic = classDecl.byNameOrNull("magic") assertNotNull(magic) - val ifStatement = (magic.body as CompoundStatement).statements[0] as IfStatement + val ifStatement = (magic.body as Block).statements[0] as IfStatement val actual = ifStatement.controls() ifStatement.thenStatement?.let { expected.add(it) } - val thenStatement = - (ifStatement.thenStatement as CompoundStatement).statements[0] as IfStatement + val thenStatement = (ifStatement.thenStatement as Block).statements[0] as IfStatement expected.add(thenStatement) thenStatement.condition?.let { expected.add(it) } expected.add((thenStatement.condition as BinaryOperator).lhs) expected.add(((thenStatement.condition as BinaryOperator).lhs as MemberExpression).base) expected.add((thenStatement.condition as BinaryOperator).rhs) - val nestedThen = thenStatement.thenStatement as CompoundStatement + val nestedThen = thenStatement.thenStatement as Block expected.add(nestedThen) expected.add(nestedThen.statements[0]) expected.add((nestedThen.statements[0] as AssignExpression).lhs.first()) @@ -196,7 +192,7 @@ class ShortcutsTest { ((nestedThen.statements[0] as AssignExpression).lhs.first() as MemberExpression).base ) expected.add((nestedThen.statements[0] as AssignExpression).rhs.first()) - val nestedElse = thenStatement.elseStatement as CompoundStatement + val nestedElse = thenStatement.elseStatement as Block expected.add(nestedElse) expected.add(nestedElse.statements[0]) expected.add((nestedElse.statements[0] as AssignExpression).lhs.first()) @@ -206,22 +202,17 @@ class ShortcutsTest { expected.add((nestedElse.statements[0] as AssignExpression).rhs.first()) ifStatement.elseStatement?.let { expected.add(it) } - expected.add((ifStatement.elseStatement as CompoundStatement).statements[0]) + expected.add((ifStatement.elseStatement as Block).statements[0]) expected.add( - ((ifStatement.elseStatement as CompoundStatement).statements[0] as AssignExpression) - .lhs - .first() + ((ifStatement.elseStatement as Block).statements[0] as AssignExpression).lhs.first() ) expected.add( - (((ifStatement.elseStatement as CompoundStatement).statements[0] as AssignExpression) - .lhs - .first() as MemberExpression) + (((ifStatement.elseStatement as Block).statements[0] as AssignExpression).lhs.first() + as MemberExpression) .base ) expected.add( - ((ifStatement.elseStatement as CompoundStatement).statements[0] as AssignExpression) - .rhs - .first() + ((ifStatement.elseStatement as Block).statements[0] as AssignExpression).rhs.first() ) assertTrue(expected.containsAll(actual)) @@ -246,10 +237,9 @@ class ShortcutsTest { assertNotNull(magic) // get the statement attr = 3; - val ifStatement = (magic.body as CompoundStatement).statements[0] as IfStatement - val thenStatement = - (ifStatement.thenStatement as CompoundStatement).statements[0] as IfStatement - val nestedThen = thenStatement.thenStatement as CompoundStatement + val ifStatement = (magic.body as Block).statements[0] as IfStatement + val thenStatement = (ifStatement.thenStatement as Block).statements[0] as IfStatement + val nestedThen = thenStatement.thenStatement as Block val interestingNode = nestedThen.statements[0] val actual = interestingNode.controlledBy() @@ -268,8 +258,7 @@ class ShortcutsTest { assertNotNull(magic2) val aAssignment2 = - ((((magic2.body as CompoundStatement).statements[1] as IfStatement).elseStatement - as CompoundStatement) + ((((magic2.body as Block).statements[1] as IfStatement).elseStatement as Block) .statements[0] as AssignExpression) .lhs @@ -284,8 +273,7 @@ class ShortcutsTest { assertNotNull(magic) val attrAssignment = - ((((magic.body as CompoundStatement).statements[0] as IfStatement).elseStatement - as CompoundStatement) + ((((magic.body as Block).statements[0] as IfStatement).elseStatement as Block) .statements[0] as AssignExpression) .lhs @@ -305,8 +293,7 @@ class ShortcutsTest { assertNotNull(magic) val attrAssignment = - ((((magic.body as CompoundStatement).statements[0] as IfStatement).elseStatement - as CompoundStatement) + ((((magic.body as Block).statements[0] as IfStatement).elseStatement as Block) .statements[0] as AssignExpression) .lhs @@ -329,15 +316,14 @@ class ShortcutsTest { assertNotNull(magic) val ifCondition = - ((magic.body as CompoundStatement).statements[0] as IfStatement).condition - as BinaryOperator + ((magic.body as Block).statements[0] as IfStatement).condition as BinaryOperator val paramPassed = ifCondition.followNextEOGEdgesUntilHit { it is AssignExpression && it.operatorCode == "=" && - (it.rhs.first() as? DeclaredReferenceExpression)?.refersTo == - (ifCondition.lhs as DeclaredReferenceExpression).refersTo + (it.rhs.first() as? Reference)?.refersTo == + (ifCondition.lhs as Reference).refersTo } assertEquals(1, paramPassed.fulfilled.size) assertEquals(2, paramPassed.failed.size) @@ -351,8 +337,7 @@ class ShortcutsTest { assertNotNull(magic) val attrAssignment = - ((((magic.body as CompoundStatement).statements[0] as IfStatement).elseStatement - as CompoundStatement) + ((((magic.body as Block).statements[0] as IfStatement).elseStatement as Block) .statements[0] as AssignExpression) .lhs diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/WalkerTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/WalkerTest.kt index 6fa8a8be57..e04a6d3e60 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/WalkerTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/WalkerTest.kt @@ -27,8 +27,8 @@ package de.fraunhofer.aisec.cpg.graph import de.fraunhofer.aisec.cpg.BaseTest import de.fraunhofer.aisec.cpg.graph.declarations.* -import de.fraunhofer.aisec.cpg.graph.statements.CompoundStatement import de.fraunhofer.aisec.cpg.graph.statements.DeclarationStatement +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Block import de.fraunhofer.aisec.cpg.graph.statements.expressions.Literal import de.fraunhofer.aisec.cpg.helpers.Benchmark import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker @@ -54,7 +54,7 @@ class WalkerTest : BaseTest() { val method = MethodDeclaration() method.name = Name("method${j}", record.name) - val comp = CompoundStatement() + val comp = Block() // Each method has a body with contains a fair amount of variable declarations for (k in 0..10) { diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/TupleDeclarationTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/TupleDeclarationTest.kt index db4d8e8d55..c452f6ee82 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/TupleDeclarationTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/TupleDeclarationTest.kt @@ -33,7 +33,7 @@ import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.builder.* import de.fraunhofer.aisec.cpg.graph.objectType import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression -import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Reference import de.fraunhofer.aisec.cpg.graph.types.TupleType import kotlin.test.Test import kotlin.test.assertContains @@ -66,7 +66,7 @@ class TupleDeclarationTest { val tuple = newTupleDeclaration( listOf(newVariableDeclaration("a"), newVariableDeclaration("b")), - newCallExpression(newDeclaredReferenceExpression("func")) + newCallExpression(newReference("func")) ) scopeManager.addDeclaration(tuple) tuple.elements.forEach { scopeManager.addDeclaration(it) } @@ -102,7 +102,7 @@ class TupleDeclarationTest { assertNotNull(callPrint) assertIs(callPrint) - val arg = callPrint.arguments(0) + val arg = callPrint.arguments(0) assertNotNull(arg) assertRefersTo(arg, a) assertContains(arg.prevDFG, a) @@ -141,9 +141,7 @@ class TupleDeclarationTest { newVariableDeclaration("a"), newVariableDeclaration("b") ), - newCallExpression( - newDeclaredReferenceExpression("func") - ) + newCallExpression(newReference("func")) ) this.addToPropertyEdgeDeclaration(tuple) scopeManager.addDeclaration(tuple) @@ -182,7 +180,7 @@ class TupleDeclarationTest { assertNotNull(callPrint) assertIs(callPrint) - val arg = callPrint.arguments(0) + val arg = callPrint.arguments(0) assertNotNull(arg) assertRefersTo(arg, a) assertContains(arg.prevDFG, a) diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/AssignExpressionTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/AssignExpressionTest.kt index a99c3f8647..7602cd87c0 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/AssignExpressionTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/AssignExpressionTest.kt @@ -30,7 +30,6 @@ import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.builder.function import de.fraunhofer.aisec.cpg.graph.builder.translationResult import de.fraunhofer.aisec.cpg.graph.builder.translationUnit -import de.fraunhofer.aisec.cpg.graph.statements.CompoundStatement import de.fraunhofer.aisec.cpg.graph.types.TupleType import de.fraunhofer.aisec.cpg.passes.DFGPass import kotlin.test.* @@ -39,8 +38,8 @@ class AssignExpressionTest { @Test fun propagateSimple() { with(TestLanguageFrontend()) { - val refA = newDeclaredReferenceExpression("a") - val refB = newDeclaredReferenceExpression("b") + val refA = newReference("a") + val refB = newReference("b") // Simple assignment from "b" to "a". Both types are unknown at this point val stmt = newAssignExpression(lhs = listOf(refA), rhs = listOf(refB)) @@ -71,9 +70,9 @@ class AssignExpressionTest { ) function("main") { - val refA = newDeclaredReferenceExpression("a") - val refErr = newDeclaredReferenceExpression("err") - val refFunc = newDeclaredReferenceExpression("func") + val refA = newReference("a") + val refErr = newReference("err") + val refFunc = newReference("func") refFunc.refersTo = func val call = newCallExpression(refFunc) @@ -81,8 +80,8 @@ class AssignExpressionTest { val stmt = newAssignExpression(lhs = listOf(refA, refErr), rhs = listOf(call)) - body = newCompoundStatement() - body as CompoundStatement += stmt + body = newBlock() + body as Block += stmt } } } diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/types/TypePropagationTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/types/TypePropagationTest.kt index 05f85022ef..45946b8b88 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/types/TypePropagationTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/types/TypePropagationTest.kt @@ -30,10 +30,10 @@ import de.fraunhofer.aisec.cpg.frontends.TestLanguageFrontend import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.builder.* import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration -import de.fraunhofer.aisec.cpg.graph.statements.CompoundStatement import de.fraunhofer.aisec.cpg.graph.statements.ReturnStatement import de.fraunhofer.aisec.cpg.graph.statements.expressions.AssignExpression -import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Block +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Reference import de.fraunhofer.aisec.cpg.passes.ControlFlowSensitiveDFGPass import de.fraunhofer.aisec.cpg.passes.EvaluationOrderGraphPass import de.fraunhofer.aisec.cpg.passes.VariableUsageResolver @@ -127,7 +127,7 @@ class TypePropagationTest { val main = result.functions["main"] assertNotNull(main) - val assign = (main.body as? CompoundStatement)?.statements?.get(2) as? AssignExpression + val assign = (main.body as? Block)?.statements?.get(2) as? AssignExpression assertNotNull(assign) val shortVar = main.variables["shortVar"] @@ -136,13 +136,13 @@ class TypePropagationTest { assertEquals(primitiveType("short"), shortVar.type) assertEquals(setOf(primitiveType("short")), shortVar.assignedTypes) - val rhs = assign.rhs.firstOrNull() as? DeclaredReferenceExpression + val rhs = assign.rhs.firstOrNull() as? Reference assertNotNull(rhs) assertIs(rhs.type) assertLocalName("int", rhs.type) assertEquals(32, (rhs.type as IntegerType).bitWidth) - val shortVarRefLhs = assign.lhs.firstOrNull() as? DeclaredReferenceExpression + val shortVarRefLhs = assign.lhs.firstOrNull() as? Reference assertNotNull(shortVarRefLhs) // At this point, shortVar was target of an assignment of an int variable, however, the // int gets truncated into a short, so only short is part of the assigned types. @@ -380,7 +380,7 @@ class TypePropagationTest { .commonType ) - val assign = (body as CompoundStatement).statements(1) + val assign = (body as Block).statements(1) assertNotNull(assign) val bb = variables["bb"] @@ -397,7 +397,7 @@ class TypePropagationTest { bb.assignedTypes ) - val returnStatement = (body as CompoundStatement).statements(3) + val returnStatement = (body as Block).statements(3) assertNotNull(returnStatement) val returnValue = returnStatement.returnValue diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPassTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPassTest.kt index f227e6a489..eac598e55b 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPassTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPassTest.kt @@ -33,7 +33,7 @@ import de.fraunhofer.aisec.cpg.frontends.TestLanguage import de.fraunhofer.aisec.cpg.frontends.TestLanguageFrontend import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.builder.* -import de.fraunhofer.aisec.cpg.graph.statements.CompoundStatement +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Block import de.fraunhofer.aisec.cpg.graph.statements.expressions.Literal import kotlin.test.Test import kotlin.test.assertEquals @@ -48,7 +48,7 @@ class ControlDependenceGraphPassTest { assertNotNull(result) val main = result.functions["main"] assertNotNull(main) - val if0 = (main.body as CompoundStatement).statements[1] + val if0 = (main.body as Block).statements[1] assertNotNull(if0) assertEquals(1, if0.prevCDG.size) assertTrue(main in if0.prevCDG) @@ -90,7 +90,7 @@ class ControlDependenceGraphPassTest { assertNotNull(result) val main = result.functions["main"] assertNotNull(main) - val forEachStmt = (main.body as CompoundStatement).statements[1] + val forEachStmt = (main.body as Block).statements[1] assertNotNull(forEachStmt) assertEquals(1, forEachStmt.prevCDG.size) assertTrue(main in forEachStmt.prevCDG) diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/DFGTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/DFGTest.kt index db88bc6721..5b3228ed42 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/DFGTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/DFGTest.kt @@ -138,8 +138,8 @@ class DFGTest { val varB = TestUtils.findByUniqueName(result.variables, "b") assertNotNull(varB) - val lhsA = binaryOperatorAssignment.lhs.first() as DeclaredReferenceExpression - val rhsA = binaryOperatorAddition.lhs as DeclaredReferenceExpression + val lhsA = binaryOperatorAssignment.lhs.first() as Reference + val rhsA = binaryOperatorAddition.lhs as Reference val b = TestUtils.findByUniqueName(result.refs, "b") assertNotNull(b) @@ -148,7 +148,7 @@ class DFGTest { val literal1 = result.literals[{ it.value == 1 }] assertNotNull(literal1) - // a and b flow to the DeclaredReferenceExpressions in (a+b) + // a and b flow to the References in (a+b) assertEquals(1, varA.nextDFG.size) assertEquals(1, varB.nextDFG.size) assertTrue(varA.nextDFG.contains(rhsA)) @@ -368,10 +368,10 @@ class DFGTest { val b = result.variables["b"] assertNotNull(b) - val ab = b.prevEOG[0] as DeclaredReferenceExpression + val ab = b.prevEOG[0] as Reference val literal4 = result.literals[{ it.value == 4 }] assertNotNull(literal4) - val a4 = ab.prevDFG.first { it is DeclaredReferenceExpression } + val a4 = ab.prevDFG.first { it is Reference } assertTrue(literal4.nextDFG.contains(a4)) assertEquals(1, ab.prevDFG.size) } @@ -394,7 +394,7 @@ class DFGTest { assertEquals( 1, a2.nextDFG.size - ) // Outgoing DFG Edges only to the DeclaredReferenceExpression in the assignment to b + ) // Outgoing DFG Edges only to the Reference in the assignment to b assertEquals( b.initializer!!, a2.nextDFG.first(), @@ -403,7 +403,7 @@ class DFGTest { val refersTo = a2.getRefersToAs(VariableDeclaration::class.java) assertNotNull(refersTo) assertEquals(2, refersTo.nextDFG.size) // The print and assignment to b - // Outgoing DFG Edge to the DeclaredReferenceExpression in the assignment of b + // Outgoing DFG Edge to the Reference in the assignment of b assertTrue(refersTo.nextDFG.contains(b.initializer!!)) // Test Else-Block with System.out.println() @@ -419,7 +419,7 @@ class DFGTest { assertEquals(1, aPrintln.nextEOG.size) assertEquals(println, aPrintln.nextEOG[0]) - val ab = b.prevEOG[0] as DeclaredReferenceExpression + val ab = b.prevEOG[0] as Reference assertTrue(refersTo.nextDFG.contains(ab)) assertTrue(a2.nextDFG.contains(ab)) } @@ -435,7 +435,7 @@ class DFGTest { val b = result.variables["b"] assertNotNull(b) - val ab = b.prevEOG[0] as DeclaredReferenceExpression + val ab = b.prevEOG[0] as Reference val a10 = result.refs[{ TestUtils.compareLineFromLocationIfExists(it, true, 8) }] val a11 = result.refs[{ TestUtils.compareLineFromLocationIfExists(it, true, 11) }] val a12 = result.refs[{ TestUtils.compareLineFromLocationIfExists(it, true, 14) }] diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/UnresolvedDFGPassTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/UnresolvedDFGPassTest.kt index ec3d9d661c..b4984cd78e 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/UnresolvedDFGPassTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/UnresolvedDFGPassTest.kt @@ -33,8 +33,8 @@ import de.fraunhofer.aisec.cpg.frontends.TestLanguage import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.builder.* import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration -import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.Literal +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Reference import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertTrue @@ -48,20 +48,14 @@ class UnresolvedDFGPassTest { val firstCall = result.calls { it.name.localName == "get" }[0] val osDecl = result.variables["os"] assertEquals(1, firstCall.prevDFG.size) - assertEquals( - osDecl, - (firstCall.prevDFG.firstOrNull() as? DeclaredReferenceExpression)?.refersTo - ) + assertEquals(osDecl, (firstCall.prevDFG.firstOrNull() as? Reference)?.refersTo) // Flow from base and argument to return value val callWithParam = result.calls { it.name.localName == "get" }[1] assertEquals(2, callWithParam.prevDFG.size) assertEquals( osDecl, - callWithParam.prevDFG - .filterIsInstance() - .firstOrNull() - ?.refersTo + callWithParam.prevDFG.filterIsInstance().firstOrNull()?.refersTo ) assertEquals(4, callWithParam.prevDFG.filterIsInstance>().firstOrNull()?.value) diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/VariableResolverTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/VariableResolverTest.kt index 117b701d3b..01f1534076 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/VariableResolverTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/VariableResolverTest.kt @@ -32,8 +32,8 @@ import de.fraunhofer.aisec.cpg.graph.allChildren import de.fraunhofer.aisec.cpg.graph.fields import de.fraunhofer.aisec.cpg.graph.methods import de.fraunhofer.aisec.cpg.graph.statements.ReturnStatement -import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberExpression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Reference import de.fraunhofer.aisec.cpg.graph.variables import kotlin.test.Test import kotlin.test.assertEquals @@ -74,7 +74,7 @@ internal class VariableResolverTest : BaseTest() { var local = getLocal.variables.firstOrNull { it.name.localName != "this" } - var returnValue = returnStatement.returnValue as DeclaredReferenceExpression + var returnValue = returnStatement.returnValue as Reference assertNotEquals(field, returnValue.refersTo) assertEquals(local, returnValue.refersTo) @@ -85,7 +85,7 @@ internal class VariableResolverTest : BaseTest() { local = getShadow.variables.firstOrNull { it.name.localName != "this" } - returnValue = returnStatement.returnValue as DeclaredReferenceExpression + returnValue = returnStatement.returnValue as Reference assertNotEquals(field, returnValue.refersTo) assertEquals(local, returnValue.refersTo) } diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/scopes/ScopeManagerTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/scopes/ScopeManagerTest.kt index a3674d7583..9cbade8b65 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/scopes/ScopeManagerTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/scopes/ScopeManagerTest.kt @@ -97,12 +97,7 @@ internal class ScopeManagerTest : BaseTest() { // resolve symbol val call = - frontend.newCallExpression( - frontend.newDeclaredReferenceExpression("A::func1"), - "A::func1", - null, - false - ) + frontend.newCallExpression(frontend.newReference("A::func1"), "A::func1", null, false) val func = final.resolveFunction(call).firstOrNull() assertEquals(func1, func) diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/processing/VisitorTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/processing/VisitorTest.kt index 3ea2a6942a..7141980bf6 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/processing/VisitorTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/processing/VisitorTest.kt @@ -30,7 +30,6 @@ import de.fraunhofer.aisec.cpg.GraphExamples import de.fraunhofer.aisec.cpg.frontends.TranslationException import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.bodyOrNull -import de.fraunhofer.aisec.cpg.graph.builder.* import de.fraunhofer.aisec.cpg.graph.byNameOrNull import de.fraunhofer.aisec.cpg.graph.declarations.* import de.fraunhofer.aisec.cpg.graph.records @@ -74,9 +73,9 @@ class VisitorTest : BaseTest() { fun testAllEogNodeVisitor() { val nodeList: MutableList = ArrayList() // val recordDeclaration = namespace?.getDeclarationAs(0, RecordDeclaration::class.java) - assertNotNull(recordDecl) + assertNotNull(recordDeclaration) - val method = recordDecl!!.byNameOrNull("method") + val method = recordDeclaration!!.byNameOrNull("method") assertNotNull(method) val firstStmt = method.bodyOrNull() @@ -97,10 +96,10 @@ class VisitorTest : BaseTest() { /** Visits all nodes along AST. */ @Test fun testAllAstNodeVisitor() { - assertNotNull(recordDecl) + assertNotNull(recordDeclaration) val nodeList = mutableListOf() - recordDecl!!.accept( + recordDeclaration!!.accept( Strategy::AST_FORWARD, object : IVisitor() { override fun visit(n: Node) { @@ -118,22 +117,22 @@ class VisitorTest : BaseTest() { /** Visits only ReturnStatement nodes. */ @Test fun testReturnStmtVisitor() { - val returnStmts: MutableList = ArrayList() - assertNotNull(recordDecl) + val returnStatements: MutableList = ArrayList() + assertNotNull(recordDeclaration) - recordDecl!!.accept( + recordDeclaration!!.accept( Strategy::AST_FORWARD, object : IVisitor() { fun visit(r: ReturnStatement) { - returnStmts.add(r) + returnStatements.add(r) } } ) - assertEquals(2, returnStmts.size) + assertEquals(2, returnStatements.size) } companion object { - private var recordDecl: RecordDeclaration? = null + private var recordDeclaration: RecordDeclaration? = null @BeforeAll @JvmStatic @@ -145,7 +144,7 @@ class VisitorTest : BaseTest() { ) fun setup() { val cpg = GraphExamples.getVisitorTest() - recordDecl = cpg.records.firstOrNull() + recordDeclaration = cpg.records.firstOrNull() } } } diff --git a/cpg-core/src/testFixtures/kotlin/de/fraunhofer/aisec/cpg/TestUtils.kt b/cpg-core/src/testFixtures/kotlin/de/fraunhofer/aisec/cpg/TestUtils.kt index f7f12b5811..7f58e472e1 100644 --- a/cpg-core/src/testFixtures/kotlin/de/fraunhofer/aisec/cpg/TestUtils.kt +++ b/cpg-core/src/testFixtures/kotlin/de/fraunhofer/aisec/cpg/TestUtils.kt @@ -210,8 +210,8 @@ object TestUtils { * Asserts, that the expression given in [expression] refers to the expected declaration [b]. */ fun assertRefersTo(expression: Expression?, b: Declaration?) { - if (expression is DeclaredReferenceExpression) { - assertEquals(b, (expression as DeclaredReferenceExpression?)?.refersTo) + if (expression is Reference) { + assertEquals(b, (expression as Reference?)?.refersTo) } else { fail("not a reference") } @@ -228,9 +228,9 @@ object TestUtils { /** * Asserts equality or containing of the expected usedNode in the usingNode. If - * [ENFORCE_REFERENCES] is true, `usingNode` must be a [DeclaredReferenceExpression] where - * [DeclaredReferenceExpression.refersTo] is or contains `usedNode`. If this is not the case, - * usage can also be interpreted as equality of the two. + * [ENFORCE_REFERENCES] is true, `usingNode` must be a [Reference] where [Reference.refersTo] is + * or contains `usedNode`. If this is not the case, usage can also be interpreted as equality of + * the two. * * @param usingNode * - The node that shows usage of another node. @@ -240,11 +240,11 @@ object TestUtils { */ fun assertUsageOf(usingNode: Node?, usedNode: Node?) { assertNotNull(usingNode) - if (usingNode !is DeclaredReferenceExpression && !ENFORCE_REFERENCES) { + if (usingNode !is Reference && !ENFORCE_REFERENCES) { assertSame(usedNode, usingNode) } else { - assertTrue(usingNode is DeclaredReferenceExpression) - val reference = usingNode as? DeclaredReferenceExpression + assertTrue(usingNode is Reference) + val reference = usingNode as? Reference assertEquals(usedNode, reference?.refersTo) } } @@ -272,12 +272,12 @@ object TestUtils { assertUsageOf(usingNode, usedMember) } else { assertTrue(usingNode is MemberExpression) - val memberExpression = usingNode as MemberExpression? - assertNotNull(memberExpression) + val memberExpressionExpression = usingNode as MemberExpression? + assertNotNull(memberExpressionExpression) - val base = memberExpression.base + val base = memberExpressionExpression.base assertUsageOf(base, usedBase) - assertUsageOf(memberExpression.refersTo, usedMember) + assertUsageOf(memberExpressionExpression.refersTo, usedMember) } } } diff --git a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXLanguageFrontend.kt b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXLanguageFrontend.kt index 69276887ca..ab0713f3d3 100644 --- a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXLanguageFrontend.kt +++ b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXLanguageFrontend.kt @@ -386,7 +386,7 @@ class CXXLanguageFrontend(language: Language, ctx: Translat val expression: Expression = when (token.tokenType) { 1 -> // a variable - newDeclaredReferenceExpression(code, unknownType(), code) + newReference(code, unknownType(), code) 2 -> // an integer newLiteral(code.toInt(), primitiveType("int"), code) 130 -> // a string diff --git a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/DeclarationHandler.kt b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/DeclarationHandler.kt index 0c7dc12bdb..95d4a92b9f 100644 --- a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/DeclarationHandler.kt +++ b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/DeclarationHandler.kt @@ -29,10 +29,10 @@ import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.declarations.* import de.fraunhofer.aisec.cpg.graph.scopes.NameScope import de.fraunhofer.aisec.cpg.graph.scopes.TemplateScope -import de.fraunhofer.aisec.cpg.graph.statements.CompoundStatement import de.fraunhofer.aisec.cpg.graph.statements.ReturnStatement import de.fraunhofer.aisec.cpg.graph.statements.Statement -import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Block +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Reference import de.fraunhofer.aisec.cpg.graph.statements.expressions.UnaryOperator import de.fraunhofer.aisec.cpg.graph.types.* import java.util.function.Supplier @@ -77,11 +77,11 @@ class DeclarationHandler(lang: CXXLanguageFrontend) : /** * Translates a C++ (using * directive)[https://en.cppreference.com/w/cpp/language/namespace#Using-directives] into a - * [UsingDirective]. However, currently, no actual adjustment of available names / scopes is + * [UsingDeclaration]. However, currently, no actual adjustment of available names / scopes is * done yet. */ private fun handleUsingDirective(using: CPPASTUsingDirective): Declaration { - return newUsingDirective(using.rawSignature, using.qualifiedName.toString()) + return newUsingDeclaration(using.rawSignature, using.qualifiedName.toString()) } /** @@ -195,7 +195,7 @@ class DeclarationHandler(lang: CXXLanguageFrontend) : // Let the statement handler take care of the function body. The outcome should (always) // be a compound statement, holding all other statements. val bodyStatement = frontend.statementHandler.handle(ctx.body) - if (bodyStatement is CompoundStatement) { + if (bodyStatement is Block) { val statements = bodyStatement.statementEdges // get the last statement @@ -273,7 +273,7 @@ class DeclarationHandler(lang: CXXLanguageFrontend) : if (ctx.declaration is CPPASTFunctionDefinition) { newFunctionTemplateDeclaration(name, frontend.codeOf(ctx)) } else { - newClassTemplateDeclaration(name, frontend.codeOf(ctx)) + newRecordTemplateDeclaration(name, frontend.codeOf(ctx)) } templateDeclaration.location = frontend.locationOf(ctx) @@ -310,27 +310,27 @@ class DeclarationHandler(lang: CXXLanguageFrontend) : for (templateParameter in ctx.templateParameters) { if (templateParameter is CPPASTSimpleTypeTemplateParameter) { // Handle Type Parameters - val typeParamDeclaration = - frontend.declaratorHandler.handle(templateParameter) as TypeParamDeclaration + val typeParamDecl = + frontend.declaratorHandler.handle(templateParameter) as TypeParameterDeclaration val parameterizedType = frontend.typeManager.createOrGetTypeParameter( templateDeclaration, templateParameter.name.toString(), language ) - typeParamDeclaration.type = parameterizedType + typeParamDecl.type = parameterizedType if (templateParameter.defaultType != null) { val defaultType = frontend.typeOf(templateParameter.defaultType) - typeParamDeclaration.default = defaultType + typeParamDecl.default = defaultType } - templateDeclaration.addParameter(typeParamDeclaration) + templateDeclaration.addParameter(typeParamDecl) } else if (templateParameter is CPPASTParameterDeclaration) { // Handle Value Parameters val nonTypeTemplateParamDeclaration = frontend.parameterDeclarationHandler.handle( templateParameter as IASTParameterDeclaration ) - if (nonTypeTemplateParamDeclaration is ParamVariableDeclaration) { + if (nonTypeTemplateParamDeclaration is ParameterDeclaration) { if (templateParameter.declarator.initializer != null) { val defaultExpression = frontend.initializerHandler.handle( @@ -502,7 +502,7 @@ class DeclarationHandler(lang: CXXLanguageFrontend) : // the type of this declaration). The typical (and only) scenario we // support here is the assignment of a `&ref` as initializer. initializer is UnaryOperator && type is FunctionPointerType -> { - val ref = initializer.input as? DeclaredReferenceExpression + val ref = initializer.input as? Reference ref?.resolutionHelper = declaration } } diff --git a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/DeclaratorHandler.kt b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/DeclaratorHandler.kt index af98a6d8eb..e66c277079 100644 --- a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/DeclaratorHandler.kt +++ b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/DeclaratorHandler.kt @@ -295,7 +295,7 @@ class DeclaratorHandler(lang: CXXLanguageFrontend) : for (param in ctx.parameters) { val arg = frontend.parameterDeclarationHandler.handle(param) - if (arg is ParamVariableDeclaration) { + if (arg is ParameterDeclaration) { // check for void type parameters if (arg.type is IncompleteType) { if (arg.name.isNotEmpty()) { @@ -334,7 +334,7 @@ class DeclaratorHandler(lang: CXXLanguageFrontend) : // is appended to the original ones. For coherent graph behaviour, we introduce an implicit // declaration that wraps this list if (ctx.takesVarArgs()) { - val varargs = newParamVariableDeclaration("va_args", unknownType(), true, "") + val varargs = newParameterDeclaration("va_args", unknownType(), true, "") varargs.isImplicit = true varargs.argumentIndex = i frontend.scopeManager.addDeclaration(varargs) @@ -478,12 +478,12 @@ class DeclaratorHandler(lang: CXXLanguageFrontend) : * Handles template parameters that are types * * @param ctx - * @return TypeParamDeclaration with its name + * @return TypeParameterDeclaration with its name */ private fun handleTemplateTypeParameter( ctx: CPPASTSimpleTypeTemplateParameter - ): TypeParamDeclaration { - return newTypeParamDeclaration(ctx.rawSignature, ctx.rawSignature, ctx) + ): TypeParameterDeclaration { + return newTypeParameterDeclaration(ctx.rawSignature, ctx.rawSignature, ctx) } private fun processMembers(ctx: IASTCompositeTypeSpecifier) { diff --git a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/ExpressionHandler.kt b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/ExpressionHandler.kt index 9e4e4e7bc4..babe8116ab 100644 --- a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/ExpressionHandler.kt +++ b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/ExpressionHandler.kt @@ -96,7 +96,7 @@ class ExpressionHandler(lang: CXXLanguageFrontend) : if (capture.isByReference) { val valueDeclaration = frontend.scopeManager.resolveReference( - newDeclaredReferenceExpression(capture?.identifier?.toString()) + newReference(capture?.identifier?.toString()) ) valueDeclaration?.let { lambda.mutableVariables.add(it) } } @@ -126,9 +126,7 @@ class ExpressionHandler(lang: CXXLanguageFrontend) : private fun handleCompoundStatementExpression( ctx: CPPASTCompoundStatementExpression ): Expression { - val cse = newCompoundStatementExpression(ctx.rawSignature) - cse.statement = frontend.statementHandler.handle(ctx.compoundStatement) - return cse + return frontend.statementHandler.handle(ctx.compoundStatement) as Expression } private fun handleTypeIdExpression(ctx: IASTTypeIdExpression): TypeIdExpression { @@ -167,7 +165,7 @@ class ExpressionHandler(lang: CXXLanguageFrontend) : } private fun handleArraySubscriptExpression(ctx: IASTArraySubscriptExpression): Expression { - val arraySubsExpression = newArraySubscriptionExpression(ctx.rawSignature) + val arraySubsExpression = newSubscriptExpression(ctx.rawSignature) handle(ctx.arrayExpression)?.let { arraySubsExpression.arrayExpression = it } handle(ctx.argument)?.let { arraySubsExpression.subscriptExpression = it } return arraySubsExpression @@ -182,7 +180,7 @@ class ExpressionHandler(lang: CXXLanguageFrontend) : return if (ctx.isArrayAllocation) { t.array() val arrayMods = (ctx.typeId.abstractDeclarator as IASTArrayDeclarator).arrayModifiers - val arrayCreate = newArrayCreationExpression(code) + val arrayCreate = newNewArrayExpression(code) arrayCreate.type = t for (arrayMod in arrayMods) { val constant = handle(arrayMod.constantExpression) @@ -427,7 +425,7 @@ class ExpressionHandler(lang: CXXLanguageFrontend) : ((ctx.functionNameExpression as IASTIdExpression).name as CPPASTTemplateId) .templateName .toString() - val ref = newDeclaredReferenceExpression(name) + val ref = newReference(name) callExpression = newCallExpression(ref, name, ctx.rawSignature, true) getTemplateArguments( (ctx.functionNameExpression as IASTIdExpression).name as CPPASTTemplateId @@ -461,11 +459,11 @@ class ExpressionHandler(lang: CXXLanguageFrontend) : return callExpression } - private fun handleIdExpression(ctx: IASTIdExpression): DeclaredReferenceExpression { + private fun handleIdExpression(ctx: IASTIdExpression): Reference { // this expression could actually be a field / member expression, but somehow CDT only // recognizes them as a member expression if it has an explicit 'this' // TODO: handle this? convert the declared reference expression into a member expression? - return newDeclaredReferenceExpression(ctx.name.toString(), unknownType(), ctx.rawSignature) + return newReference(ctx.name.toString(), unknownType(), ctx.rawSignature) } private fun handleExpressionList(exprList: IASTExpressionList): ExpressionList { @@ -524,8 +522,8 @@ class ExpressionHandler(lang: CXXLanguageFrontend) : val operatorCode = String(ASTStringUtil.getBinaryOperatorString(ctx)) val assign = newAssignExpression(operatorCode, listOf(lhs), listOf(rhs), rawNode = ctx) - if (rhs is UnaryOperator && rhs.input is DeclaredReferenceExpression) { - (rhs.input as DeclaredReferenceExpression).resolutionHelper = lhs + if (rhs is UnaryOperator && rhs.input is Reference) { + (rhs.input as Reference).resolutionHelper = lhs } return assign @@ -566,11 +564,7 @@ class ExpressionHandler(lang: CXXLanguageFrontend) : } is CPPASTFieldDesignator -> { oneLhs = - newDeclaredReferenceExpression( - des.name.toString(), - unknownType(), - des.getRawSignature() - ) + newReference(des.name.toString(), unknownType(), des.getRawSignature()) } is CPPASTArrayRangeDesignator -> { oneLhs = @@ -620,11 +614,7 @@ class ExpressionHandler(lang: CXXLanguageFrontend) : } is CPPASTFieldDesignator -> { oneLhs = - newDeclaredReferenceExpression( - des.name.toString(), - unknownType(), - des.getRawSignature() - ) + newReference(des.name.toString(), unknownType(), des.getRawSignature()) } is CPPASTArrayRangeDesignator -> { oneLhs = @@ -779,17 +769,17 @@ class ExpressionHandler(lang: CXXLanguageFrontend) : /** * In C++, the "this" expression is also modelled as a literal. In our case however, we want to - * return a [DeclaredReferenceExpression], which is then later connected to the current method's + * return a [Reference], which is then later connected to the current method's * [MethodDeclaration.receiver]. */ - private fun handleThisLiteral(ctx: IASTLiteralExpression): DeclaredReferenceExpression { + private fun handleThisLiteral(ctx: IASTLiteralExpression): Reference { // We should be in a record here. However since we are a fuzzy parser, maybe things went // wrong, so we might have an unknown type. val recordType = frontend.scopeManager.currentRecord?.toType() ?: unknownType() // We do want to make sure that the type of the expression is at least a pointer. val pointerType = recordType.pointer() - return newDeclaredReferenceExpression("this", pointerType, ctx.rawSignature, ctx) + return newReference("this", pointerType, ctx.rawSignature, ctx) } private val IASTLiteralExpression.valueWithSuffix: Pair diff --git a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/ParameterDeclarationHandler.kt b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/ParameterDeclarationHandler.kt index 94b990e2a8..79e0c240f2 100644 --- a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/ParameterDeclarationHandler.kt +++ b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/ParameterDeclarationHandler.kt @@ -26,9 +26,9 @@ package de.fraunhofer.aisec.cpg.frontends.cxx import de.fraunhofer.aisec.cpg.graph.declarations.Declaration -import de.fraunhofer.aisec.cpg.graph.declarations.ParamVariableDeclaration +import de.fraunhofer.aisec.cpg.graph.declarations.ParameterDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.ProblemDeclaration -import de.fraunhofer.aisec.cpg.graph.newParamVariableDeclaration +import de.fraunhofer.aisec.cpg.graph.newParameterDeclaration import java.util.function.Supplier import org.eclipse.cdt.core.dom.ast.IASTParameterDeclaration import org.eclipse.cdt.internal.core.dom.parser.c.CASTParameterDeclaration @@ -47,19 +47,12 @@ class ParameterDeclarationHandler(lang: CXXLanguageFrontend) : } } - private fun handleParameterDeclaration( - ctx: IASTParameterDeclaration - ): ParamVariableDeclaration { + private fun handleParameterDeclaration(ctx: IASTParameterDeclaration): ParameterDeclaration { // Parse the type val type = frontend.typeOf(ctx.declarator, ctx.declSpecifier) val paramVariableDeclaration = - newParamVariableDeclaration( - ctx.declarator.name.toString(), - type, - false, - ctx.rawSignature - ) + newParameterDeclaration(ctx.declarator.name.toString(), type, false, ctx.rawSignature) // Add default values if (ctx.declarator.initializer != null) { diff --git a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/StatementHandler.kt b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/StatementHandler.kt index 87d5ce18e9..dc759a1f01 100644 --- a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/StatementHandler.kt +++ b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/StatementHandler.kt @@ -30,6 +30,7 @@ import de.fraunhofer.aisec.cpg.graph.declarations.Declaration import de.fraunhofer.aisec.cpg.graph.declarations.DeclarationSequence import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration import de.fraunhofer.aisec.cpg.graph.statements.* +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Block import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression import de.fraunhofer.aisec.cpg.graph.statements.expressions.Literal import de.fraunhofer.aisec.cpg.graph.statements.expressions.ProblemExpression @@ -73,24 +74,29 @@ class StatementHandler(lang: CXXLanguageFrontend) : } } - private fun handleProblemStatement(problemStmt: IASTProblemStatement): ProblemExpression { - Util.errorWithFileLocation(frontend, problemStmt, log, problemStmt.problem.message) + private fun handleProblemStatement(problemStatement: IASTProblemStatement): ProblemExpression { + Util.errorWithFileLocation( + frontend, + problemStatement, + log, + problemStatement.problem.message + ) return newProblemExpression( - problemStmt.problem.message, + problemStatement.problem.message, ) } - private fun handleEmptyStatement(emptyStmt: IASTNullStatement): EmptyStatement { - return newEmptyStatement(emptyStmt.rawSignature) + private fun handleEmptyStatement(nullStatement: IASTNullStatement): EmptyStatement { + return newEmptyStatement(nullStatement.rawSignature) } - private fun handleTryBlockStatement(tryStmt: CPPASTTryBlockStatement): TryStatement { - val tryStatement = newTryStatement(tryStmt.toString()) + private fun handleTryBlockStatement(tryBlockStatement: CPPASTTryBlockStatement): TryStatement { + val tryStatement = newTryStatement(tryBlockStatement.toString()) frontend.scopeManager.enterScope(tryStatement) - val statement = handle(tryStmt.tryBody) as CompoundStatement? + val statement = handle(tryBlockStatement.tryBody) as Block? val catchClauses = - Arrays.stream(tryStmt.catchHandlers) + Arrays.stream(tryBlockStatement.catchHandlers) .map { handleCatchHandler(it) } .collect(Collectors.toList()) tryStatement.tryBlock = statement @@ -111,7 +117,7 @@ class StatementHandler(lang: CXXLanguageFrontend) : decl = frontend.declarationHandler.handle(catchHandler.declaration) } - catchClause.body = body as? CompoundStatement + catchClause.body = body as? Block if (decl != null) { catchClause.parameter = decl as? VariableDeclaration @@ -310,21 +316,21 @@ class StatementHandler(lang: CXXLanguageFrontend) : return returnStatement } - private fun handleCompoundStatement(ctx: IASTCompoundStatement): CompoundStatement { - val compoundStatement = newCompoundStatement(ctx.rawSignature) + private fun handleCompoundStatement(ctx: IASTCompoundStatement): Block { + val block = newBlock(ctx.rawSignature) - frontend.scopeManager.enterScope(compoundStatement) + frontend.scopeManager.enterScope(block) for (statement in ctx.statements) { val handled = handle(statement) if (handled != null) { - compoundStatement.addStatement(handled) + block.addStatement(handled) } } - frontend.scopeManager.leaveScope(compoundStatement) + frontend.scopeManager.leaveScope(block) - return compoundStatement + return block } private fun handleSwitchStatement(ctx: IASTSwitchStatement): SwitchStatement { diff --git a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/EOGTest.kt b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/EOGTest.kt index db2020ec57..faf20fe1ec 100644 --- a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/EOGTest.kt +++ b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/EOGTest.kt @@ -936,15 +936,12 @@ internal class EOGTest : BaseTest() { assertTrue(printFunctionCall in lambda.prevEOG) // The "inner" EOG is assembled correctly. - val body = (lambda.function?.body as? CompoundStatement) + val body = (lambda.function?.body as? Block) assertNotNull(body) assertEquals(1, lambda.function?.nextEOG?.size) - assertEquals( - "std::cout", - (lambda.function?.nextEOG?.get(0) as? DeclaredReferenceExpression)?.name.toString() - ) + assertEquals("std::cout", (lambda.function?.nextEOG?.get(0) as? Reference)?.name.toString()) - val cout = lambda.function?.nextEOG?.get(0) as? DeclaredReferenceExpression + val cout = lambda.function?.nextEOG?.get(0) as? Reference assertNotNull(cout) assertEquals(1, cout.nextEOG.size) assertEquals("Hello ", (cout.nextEOG[0] as? Literal<*>)?.value.toString()) @@ -957,12 +954,9 @@ internal class EOGTest : BaseTest() { val binOpLeft = hello.nextEOG[0] as? BinaryOperator assertNotNull(binOpLeft) assertEquals(1, binOpLeft.nextEOG.size) - assertEquals( - "number", - (binOpLeft.nextEOG[0] as? DeclaredReferenceExpression)?.name.toString() - ) + assertEquals("number", (binOpLeft.nextEOG[0] as? Reference)?.name.toString()) - val number = binOpLeft.nextEOG[0] as? DeclaredReferenceExpression + val number = binOpLeft.nextEOG[0] as? Reference assertNotNull(number) assertEquals(1, number.nextEOG.size) assertEquals("<<", (number.nextEOG[0] as? BinaryOperator)?.operatorCode) @@ -970,12 +964,9 @@ internal class EOGTest : BaseTest() { val binOpCenter = (number.nextEOG[0] as? BinaryOperator) assertNotNull(binOpCenter) assertEquals(1, binOpCenter.nextEOG.size) - assertEquals( - "std::endl", - (binOpCenter.nextEOG[0] as? DeclaredReferenceExpression)?.name.toString() - ) + assertEquals("std::endl", (binOpCenter.nextEOG[0] as? Reference)?.name.toString()) - val endl = (binOpCenter.nextEOG[0] as? DeclaredReferenceExpression) + val endl = (binOpCenter.nextEOG[0] as? Reference) assertNotNull(endl) assertEquals(1, endl.nextEOG.size) assertEquals("<<", (endl.nextEOG[0] as? BinaryOperator)?.operatorCode) @@ -983,9 +974,9 @@ internal class EOGTest : BaseTest() { val binOpRight = (endl.nextEOG[0] as? BinaryOperator) assertNotNull(binOpRight) assertEquals(1, binOpRight.nextEOG.size) - assertTrue(binOpRight.nextEOG.firstOrNull() is CompoundStatement) + assertTrue(binOpRight.nextEOG.firstOrNull() is Block) - assertEquals(0, (binOpRight.nextEOG.firstOrNull() as? CompoundStatement)?.nextEOG?.size) + assertEquals(0, (binOpRight.nextEOG.firstOrNull() as? Block)?.nextEOG?.size) } @Test diff --git a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/templates/ClassTemplateTest.kt b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/templates/ClassTemplateTest.kt index 20c00f0f24..a2a2d791c6 100644 --- a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/templates/ClassTemplateTest.kt +++ b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/templates/ClassTemplateTest.kt @@ -46,14 +46,14 @@ internal class ClassTemplateTest : BaseTest() { private val topLevel = Path.of("src", "test", "resources", "templates", "classtemplates") private fun testTemplateStructure( - template: ClassTemplateDeclaration, + template: RecordTemplateDeclaration, pair: RecordDeclaration?, - type1: TypeParamDeclaration?, - type2: TypeParamDeclaration? + type1: TypeParameterDeclaration?, + type2: TypeParameterDeclaration? ) { assertEquals(2, template.parameters.size) - assertEquals(type1, template.parameters[0] as TypeParamDeclaration?) - assertEquals(type2, template.parameters[1] as TypeParamDeclaration?) + assertEquals(type1, template.parameters[0] as TypeParameterDeclaration?) + assertEquals(type2, template.parameters[1] as TypeParameterDeclaration?) assertEquals(1, template.realizations.size) assertNotNull(pair) assertEquals(pair, template.realizations[0]) @@ -71,8 +71,8 @@ internal class ClassTemplateTest : BaseTest() { private fun testClassTemplatesTypes( pair: RecordDeclaration?, receiver: VariableDeclaration, - type1: TypeParamDeclaration, - type2: TypeParamDeclaration + type1: TypeParameterDeclaration, + type2: TypeParameterDeclaration ): ObjectType { assertLocalName("Pair*", receiver.type) assertTrue(receiver.type is PointerType) @@ -107,7 +107,7 @@ internal class ClassTemplateTest : BaseTest() { constructExpression: ConstructExpression, pair: RecordDeclaration?, pairType: ObjectType, - template: ClassTemplateDeclaration?, + template: RecordTemplateDeclaration?, point1: VariableDeclaration ) { assertEquals(pairConstructorDeclaration, constructExpression.constructor) @@ -144,15 +144,15 @@ internal class ClassTemplateTest : BaseTest() { fun testClassTemplateStructure() { val result = analyze(listOf(Path.of(topLevel.toString(), "pair.cpp").toFile()), topLevel, true) - val classTemplateDeclarations = result.allChildren() + val recordTemplateDeclarations = result.allChildren() val template = findByUniqueName( - classTemplateDeclarations, + recordTemplateDeclarations, "template class Pair" ) val pair = findByUniqueName(result.records, "Pair") - val type1 = findByUniqueName(result.allChildren(), "class Type1") - val type2 = findByUniqueName(result.allChildren(), "class Type2") + val type1 = findByUniqueName(result.allChildren(), "class Type1") + val type2 = findByUniqueName(result.allChildren(), "class Type2") val first = findByUniqueName(result.fields, "first") val second = findByUniqueName(result.fields, "second") val constructor = pair.constructors["Pair"] @@ -161,7 +161,7 @@ internal class ClassTemplateTest : BaseTest() { val receiver = constructor.receiver assertNotNull(receiver) - val pairConstructorDeclaration = + val pairConstructorDecl = findByUniqueName(result.allChildren(), "Pair") val constructExpression = findByUniquePredicate(result.allChildren()) { c: ConstructExpression -> @@ -179,11 +179,11 @@ internal class ClassTemplateTest : BaseTest() { val pairType = testClassTemplatesTypes(pair, receiver, type1, type2) // Test Constructor - testClassTemplateConstructor(pair, pairType, pairConstructorDeclaration) + testClassTemplateConstructor(pair, pairType, pairConstructorDecl) // Test Invocation testClassTemplateInvocation( - pairConstructorDeclaration, + pairConstructorDecl, constructExpression, pair, pairType, @@ -198,10 +198,10 @@ internal class ClassTemplateTest : BaseTest() { // Test pair2.cpp: Add Value Parameter to Template Instantiation val result = analyze(listOf(Path.of(topLevel.toString(), "pair2.cpp").toFile()), topLevel, true) - val classTemplateDeclarations = result.allChildren() + val recordTemplateDeclarations = result.allChildren() val template = findByUniqueName( - classTemplateDeclarations, + recordTemplateDeclarations, "template class Pair" ) val pair = findByUniqueName(result.records, "Pair") @@ -210,9 +210,9 @@ internal class ClassTemplateTest : BaseTest() { val receiver = pair.byNameOrNull("Pair")?.receiver assertNotNull(receiver) - val pairConstructorDeclaration = + val pairConstructorDecl = findByUniqueName(result.allChildren(), "Pair") - val constructExpression = + val constructExpr = findByUniquePredicate(result.allChildren()) { it.code == "Pair()" } val literal3 = findByUniquePredicate(result.literals) { it.value == 3 && !it.isImplicit } val literal3Implicit = @@ -221,20 +221,17 @@ internal class ClassTemplateTest : BaseTest() { assertEquals(3, template.parameters.size) assertEquals(paramN, template.parameters[2]) assertTrue(pair.fields.contains(n)) - assertEquals(paramN, (n.initializer as? DeclaredReferenceExpression)?.refersTo) + assertEquals(paramN, (n.initializer as? Reference)?.refersTo) // Test Type val type = ((receiver.type as? PointerType)?.elementType as? ObjectType) assertNotNull(type) - assertEquals( - (pairConstructorDeclaration.type as? FunctionType)?.returnTypes?.firstOrNull(), - type - ) + assertEquals((pairConstructorDecl.type as? FunctionType)?.returnTypes?.firstOrNull(), type) assertEquals(pair, type.recordDeclaration) assertEquals(2, type.generics.size) assertLocalName("Type1", type.generics[0]) assertLocalName("Type2", type.generics[1]) - val instantiatedType = constructExpression.type as ObjectType + val instantiatedType = constructExpr.type as ObjectType assertEquals(instantiatedType, point1.type) assertEquals(2, instantiatedType.generics.size) assertLocalName("int", instantiatedType.generics[0]) @@ -245,22 +242,20 @@ internal class ClassTemplateTest : BaseTest() { assertEquals(literal3, point1.templateParameters?.get(2)) // Test Invocation - val templateParameters = constructExpression.templateParameters + val templateParameters = constructExpr.templateParameters assertNotNull(templateParameters) assertEquals(3, templateParameters.size) assertEquals(literal3Implicit, templateParameters[2]) assertEquals( TemplateDeclaration.TemplateInitialization.EXPLICIT, - constructExpression.templateParameterEdges - ?.get(2) - ?.getProperty(Properties.INSTANTIATION) + constructExpr.templateParameterEdges?.get(2)?.getProperty(Properties.INSTANTIATION) ) - assertEquals(pair, constructExpression.instantiates) - assertEquals(template, constructExpression.templateInstantiation) + assertEquals(pair, constructExpr.instantiates) + assertEquals(template, constructExpr.templateInstantiation) } private fun testStructTemplateWithSameDefaultTypeInvocation( - template: ClassTemplateDeclaration?, + template: RecordTemplateDeclaration?, pair: RecordDeclaration?, pairConstructorDeclaration: ConstructorDeclaration?, constructExpression: ConstructExpression, @@ -301,19 +296,19 @@ internal class ClassTemplateTest : BaseTest() { analyze(listOf(Path.of(topLevel.toString(), "pair3.cpp").toFile()), topLevel, true) val template = findByUniqueName( - result.allChildren(), + result.allChildren(), "template struct Pair" ) val pair = findByUniqueName(result.records, "Pair") - val pairConstructorDeclaration = + val pairConstructorDecl = findByUniqueName(result.allChildren(), "Pair") - val type1 = findByUniqueName(result.allChildren(), "class Type1") + val type1 = findByUniqueName(result.allChildren(), "class Type1") val type2 = - findByUniqueName(result.allChildren(), "class Type2 = Type1") + findByUniqueName(result.allChildren(), "class Type2 = Type1") val first = findByUniqueName(result.fields, "first") val second = findByUniqueName(result.fields, "second") val point1 = findByUniqueName(result.variables, "point1") - val constructExpression = + val constructExpr = findByUniquePredicate(result.allChildren()) { it.code == "Pair()" } assertEquals(1, template.realizations.size) assertEquals(pair, template.realizations[0]) @@ -331,8 +326,7 @@ internal class ClassTemplateTest : BaseTest() { assertEquals(type1ParameterizedType, type2.default) val pairType = - (pairConstructorDeclaration.type as FunctionType).returnTypes.firstOrNull() - as? ObjectType + (pairConstructorDecl.type as FunctionType).returnTypes.firstOrNull() as? ObjectType assertNotNull(pairType) assertEquals(2, pairType.generics.size) assertEquals(type1ParameterizedType, pairType.generics[0]) @@ -345,8 +339,8 @@ internal class ClassTemplateTest : BaseTest() { testStructTemplateWithSameDefaultTypeInvocation( template, pair, - pairConstructorDeclaration, - constructExpression, + pairConstructorDecl, + constructExpr, point1 ) } @@ -359,49 +353,41 @@ internal class ClassTemplateTest : BaseTest() { analyze(listOf(Path.of(topLevel.toString(), "pair3-1.cpp").toFile()), topLevel, true) val template = findByUniqueName( - result.allChildren(), + result.allChildren(), "template struct Pair" ) val pair = findByUniqueName(result.records, "Pair") - val constructExpression = + val constructExpr = findByUniquePredicate(result.allChildren()) { it.code == "Pair()" } val literal2 = findByUniquePredicate(result.literals) { it.value == 2 && !it.isImplicit } assertNotNull(literal2) val literal2Implicit = findByUniquePredicate(result.literals) { it.value == 2 && it.isImplicit } - assertEquals(pair, constructExpression.instantiates) - assertEquals(template, constructExpression.templateInstantiation) - assertEquals(4, constructExpression.templateParameters.size) - assertLocalName("int", constructExpression.templateParameters[0]) + assertEquals(pair, constructExpr.instantiates) + assertEquals(template, constructExpr.templateInstantiation) + assertEquals(4, constructExpr.templateParameters.size) + assertLocalName("int", constructExpr.templateParameters[0]) assertEquals( TemplateDeclaration.TemplateInitialization.EXPLICIT, - constructExpression.templateParameterEdges - ?.get(0) - ?.getProperty(Properties.INSTANTIATION) + constructExpr.templateParameterEdges?.get(0)?.getProperty(Properties.INSTANTIATION) ) - assertLocalName("int", constructExpression.templateParameters[1]) + assertLocalName("int", constructExpr.templateParameters[1]) assertEquals( TemplateDeclaration.TemplateInitialization.EXPLICIT, - constructExpression.templateParameterEdges - ?.get(1) - ?.getProperty(Properties.INSTANTIATION) + constructExpr.templateParameterEdges?.get(1)?.getProperty(Properties.INSTANTIATION) ) - assertEquals(literal2Implicit, constructExpression.templateParameters[2]) + assertEquals(literal2Implicit, constructExpr.templateParameters[2]) assertEquals( TemplateDeclaration.TemplateInitialization.EXPLICIT, - constructExpression.templateParameterEdges - ?.get(2) - ?.getProperty(Properties.INSTANTIATION) + constructExpr.templateParameterEdges?.get(2)?.getProperty(Properties.INSTANTIATION) ) - assertEquals(literal2Implicit, constructExpression.templateParameters[3]) + assertEquals(literal2Implicit, constructExpr.templateParameters[3]) assertEquals( TemplateDeclaration.TemplateInitialization.DEFAULT, - constructExpression.templateParameterEdges - ?.get(3) - ?.getProperty(Properties.INSTANTIATION) + constructExpr.templateParameterEdges?.get(3)?.getProperty(Properties.INSTANTIATION) ) - val type = constructExpression.type as ObjectType + val type = constructExpr.type as ObjectType assertEquals(pair, type.recordDeclaration) assertEquals(2, type.generics.size) assertLocalName("int", type.generics[0]) @@ -416,7 +402,7 @@ internal class ClassTemplateTest : BaseTest() { analyze(listOf(Path.of(topLevel.toString(), "pair3-2.cpp").toFile()), topLevel, true) val template = findByUniqueName( - result.allChildren(), + result.allChildren(), "template struct Pair" ) val pair = findByUniqueName(result.records, "Pair") @@ -431,7 +417,7 @@ internal class ClassTemplateTest : BaseTest() { assertEquals(paramA, template.parameters[2]) assertEquals(literal1, paramA.default) assertEquals(paramB, template.parameters[3]) - assertEquals(paramA, (paramB.default as DeclaredReferenceExpression).refersTo) + assertEquals(paramA, (paramB.default as Reference).refersTo) assertEquals(pair, constructExpression.instantiates) assertEquals(template, constructExpression.templateInstantiation) assertEquals(4, constructExpression.templateParameters.size) @@ -495,12 +481,12 @@ internal class ClassTemplateTest : BaseTest() { analyze(listOf(Path.of(topLevel.toString(), "array.cpp").toFile()), topLevel, true) val template = findByUniqueName( - result.allChildren(), + result.allChildren(), "template class Array" ) val array = findByUniqueName(result.records, "Array") val paramN = findByUniqueName(result.parameters, "N") - val paramT = findByUniqueName(result.allChildren(), "typename T") + val paramT = findByUniqueName(result.allChildren(), "typename T") val literal10 = findByUniquePredicate(result.literals) { it.value == 10 } val mArray = findByUniqueName(result.fields, "m_Array") assertEquals(2, template.parameters.size) @@ -525,17 +511,17 @@ internal class ClassTemplateTest : BaseTest() { val tArray = mArray.type as PointerType assertEquals(typeT, tArray.elementType) - val constructExpression = + val constructExpr = findByUniquePredicate(result.allChildren()) { it.code == "Array()" } - assertEquals(template, constructExpression.templateInstantiation) - assertEquals(array, constructExpression.instantiates) - assertLocalName("int", constructExpression.templateParameters[0]) - assertEquals(literal10, constructExpression.templateParameters[1]) - assertLocalName("Array", constructExpression.type) + assertEquals(template, constructExpr.templateInstantiation) + assertEquals(array, constructExpr.instantiates) + assertLocalName("int", constructExpr.templateParameters[0]) + assertEquals(literal10, constructExpr.templateParameters[1]) + assertLocalName("Array", constructExpr.type) - val instantiatedType = constructExpression.type as ObjectType + val instantiatedType = constructExpr.type as ObjectType assertEquals(1, instantiatedType.generics.size) assertLocalName("int", instantiatedType.generics[0]) } @@ -548,7 +534,7 @@ internal class ClassTemplateTest : BaseTest() { analyze(listOf(Path.of(topLevel.toString(), "array2.cpp").toFile()), topLevel, true) val template = findByUniqueName( - result.allChildren(), + result.allChildren(), "template class Array" ) val array = findByUniqueName(result.records, "Array") diff --git a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/templates/FunctionTemplateTest.kt b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/templates/FunctionTemplateTest.kt index a9dfa42cd6..62b9ce738c 100644 --- a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/templates/FunctionTemplateTest.kt +++ b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/templates/FunctionTemplateTest.kt @@ -95,14 +95,14 @@ internal class FunctionTemplateTest : BaseTest() { true ) // This test checks the structure of FunctionTemplates without the TemplateExpansionPass - val functionTemplateDeclaration = result.allChildren()[0] + val functionTemplateDecl = result.allChildren()[0] // Check FunctionTemplate Parameters - val typeParamDeclarations = result.allChildren() - assertEquals(1, typeParamDeclarations.size) + val typeParamDecls = result.allChildren() + assertEquals(1, typeParamDecls.size) - val typeParamDeclaration = typeParamDeclarations[0] - assertEquals(typeParamDeclaration, functionTemplateDeclaration.parameters[0]) + val typeParamDeclaration = typeParamDecls[0] + assertEquals(typeParamDeclaration, functionTemplateDecl.parameters[0]) val typeT = ParameterizedType("T", CPPLanguage()) val intType = IntegerType("int", 32, CPPLanguage(), NumericType.Modifier.SIGNED) @@ -114,7 +114,7 @@ internal class FunctionTemplateTest : BaseTest() { val int2 = findByUniquePredicate(result.literals { it.value == 2 }) { it.value == 2 } val int3 = findByUniquePredicate(result.literals) { it.value == 3 } val int5 = findByUniquePredicate(result.literals) { it.value == 5 } - assertEquals(N, functionTemplateDeclaration.parameters[1]) + assertEquals(N, functionTemplateDecl.parameters[1]) assertEquals(intType, N.type) assertEquals(5, (N.default as Literal<*>).value) assertTrue(N.prevDFG.contains(int5)) @@ -122,9 +122,9 @@ internal class FunctionTemplateTest : BaseTest() { assertTrue(N.prevDFG.contains(int2)) // Check the realization - assertEquals(1, functionTemplateDeclaration.realization.size) + assertEquals(1, functionTemplateDecl.realization.size) - val fixedMultiply = functionTemplateDeclaration.realization[0] + val fixedMultiply = functionTemplateDecl.realization[0] val funcType = fixedMultiply.type as? FunctionType assertNotNull(funcType) assertEquals(typeT, funcType.returnTypes.firstOrNull()) @@ -539,8 +539,8 @@ internal class FunctionTemplateTest : BaseTest() { assertEquals(1, templateDeclaration.realization.size) assertEquals(fixedDivision, templateDeclaration.realization[0]) assertEquals(2, templateDeclaration.parameters.size) - assertTrue(templateDeclaration.parameters[0] is TypeParamDeclaration) - assertTrue(templateDeclaration.parameters[1] is ParamVariableDeclaration) + assertTrue(templateDeclaration.parameters[0] is TypeParameterDeclaration) + assertTrue(templateDeclaration.parameters[1] is ParameterDeclaration) assertEquals(1, fixedDivision.parameters.size) val callInt2 = findByUniquePredicate(result.calls) { c: CallExpression -> @@ -565,8 +565,8 @@ internal class FunctionTemplateTest : BaseTest() { assertEquals(1, templateDeclaration.realization.size) assertEquals(fixedDivision, templateDeclaration.realization[0]) assertEquals(2, templateDeclaration.parameters.size) - assertTrue(templateDeclaration.parameters[0] is TypeParamDeclaration) - assertTrue(templateDeclaration.parameters[1] is ParamVariableDeclaration) + assertTrue(templateDeclaration.parameters[0] is TypeParameterDeclaration) + assertTrue(templateDeclaration.parameters[1] is ParameterDeclaration) assertEquals(1, fixedDivision.parameters.size) val callDouble3 = findByUniquePredicate(result.calls) { c: CallExpression -> diff --git a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/variable_resolution/VariableResolverCppTest.kt b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/variable_resolution/VariableResolverCppTest.kt index cef82e7c9b..5a0ea31a7c 100644 --- a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/variable_resolution/VariableResolverCppTest.kt +++ b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/variable_resolution/VariableResolverCppTest.kt @@ -32,12 +32,12 @@ import de.fraunhofer.aisec.cpg.TestUtils.assertUsageOfMemberAndBase import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.declarations.* import de.fraunhofer.aisec.cpg.graph.statements.CatchClause -import de.fraunhofer.aisec.cpg.graph.statements.CompoundStatement import de.fraunhofer.aisec.cpg.graph.statements.ForStatement import de.fraunhofer.aisec.cpg.graph.statements.IfStatement -import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Block import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression import de.fraunhofer.aisec.cpg.graph.statements.expressions.Literal +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Reference import java.nio.file.Path import java.util.concurrent.ExecutionException import kotlin.test.Test @@ -125,7 +125,7 @@ internal class VariableResolverCppTest : BaseTest() { @Test fun testVarNameOfFirstLoopAccessed() { - val asReference = callParamMap["func1_first_loop_varName"] as? DeclaredReferenceExpression + val asReference = callParamMap["func1_first_loop_varName"] as? Reference assertNotNull(asReference) val vDeclaration = forStatements?.first().variables["varName"] assertUsageOf(callParamMap["func1_first_loop_varName"], vDeclaration) @@ -133,7 +133,7 @@ internal class VariableResolverCppTest : BaseTest() { @Test fun testAccessLocalVarNameInNestedBlock() { - val innerBlock = forStatements?.get(1).allChildren()[""] + val innerBlock = forStatements?.get(1).allChildren()[""] val nestedDeclaration = innerBlock.variables["varName"] assertUsageOf(callParamMap["func1_nested_block_shadowed_local_varName"], nestedDeclaration) } diff --git a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXIncludeTest.kt b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXIncludeTest.kt index 0e4a987c74..291c29a372 100644 --- a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXIncludeTest.kt +++ b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXIncludeTest.kt @@ -34,7 +34,7 @@ import de.fraunhofer.aisec.cpg.graph.get import de.fraunhofer.aisec.cpg.graph.methods import de.fraunhofer.aisec.cpg.graph.records import de.fraunhofer.aisec.cpg.graph.statements.ReturnStatement -import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Reference import de.fraunhofer.aisec.cpg.sarif.Region import java.io.File import kotlin.test.* @@ -75,7 +75,7 @@ internal class CXXIncludeTest : BaseTest() { val returnStatement = doSomething.getBodyStatementAs(0, ReturnStatement::class.java) assertNotNull(returnStatement) - val ref = returnStatement.returnValue as DeclaredReferenceExpression + val ref = returnStatement.returnValue as Reference assertNotNull(ref) val someField = someClass.fields["someField"] diff --git a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXLanguageFrontendTest.kt b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXLanguageFrontendTest.kt index b618ca00f9..c2efd1901e 100644 --- a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXLanguageFrontendTest.kt +++ b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXLanguageFrontendTest.kt @@ -69,7 +69,7 @@ internal class CXXLanguageFrontendTest : BaseTest() { assertNotNull(forEachStatement) // should loop over ls - assertEquals(ls, (forEachStatement.iterable as DeclaredReferenceExpression).refersTo) + assertEquals(ls, (forEachStatement.iterable as Reference).refersTo) // should declare auto i (so far no concrete type inferrable) val stmt = forEachStatement.variable @@ -206,7 +206,7 @@ internal class CXXLanguageFrontendTest : BaseTest() { with(tu) { assertNotNull(main) - val statement = main.body as CompoundStatement + val statement = main.body as Block // first statement is the variable declaration val x = @@ -224,9 +224,9 @@ internal class CXXLanguageFrontendTest : BaseTest() { assertEquals(3, initializers.size) // second statement is an expression directly - val ase = statement.statements[1] as ArraySubscriptionExpression + val ase = statement.statements[1] as SubscriptExpression assertNotNull(ase) - assertEquals(x, (ase.arrayExpression as DeclaredReferenceExpression).refersTo) + assertEquals(x, (ase.arrayExpression as Reference).refersTo) assertEquals(0, (ase.subscriptExpression as Literal<*>).value) // third statement declares a pointer to an array @@ -267,7 +267,7 @@ internal class CXXLanguageFrontendTest : BaseTest() { method = declaration.getDeclarationAs(2, FunctionDeclaration::class.java) assertEquals("function0(int)void", method!!.signature) - var statements = (method.body as CompoundStatement).statements + var statements = (method.body as Block).statements assertFalse(statements.isEmpty()) assertEquals(2, statements.size) @@ -279,7 +279,7 @@ internal class CXXLanguageFrontendTest : BaseTest() { method = declaration.getDeclarationAs(3, FunctionDeclaration::class.java) assertEquals("function2()void*", method!!.signature) - statements = (method.body as CompoundStatement).statements + statements = (method.body as Block).statements assertFalse(statements.isEmpty()) assertEquals(1, statements.size) @@ -316,7 +316,7 @@ internal class CXXLanguageFrontendTest : BaseTest() { @Test @Throws(Exception::class) - fun testCompoundStatement() { + fun testBlock() { val file = File("src/test/resources/compoundstmt.cpp") val declaration = analyzeAndGetFirstTU(listOf(file), file.parentFile.toPath(), true) val function = declaration.getDeclarationAs(0, FunctionDeclaration::class.java) @@ -325,7 +325,7 @@ internal class CXXLanguageFrontendTest : BaseTest() { val functionBody = function.body assertNotNull(functionBody) - val statements = (functionBody as CompoundStatement).statements + val statements = (functionBody as Block).statements assertEquals(1, statements.size) val returnStatement = statements[0] as ReturnStatement @@ -362,9 +362,9 @@ internal class CXXLanguageFrontendTest : BaseTest() { assertTrue(unaryOperatorMinus.isPostfix) // 4th statement is not yet parsed correctly - val memberCallExpression = statements[4] as MemberCallExpression - assertLocalName("test", memberCallExpression.base) - assertLocalName("c_str", memberCallExpression) + val memberCallExpr = statements[4] as MemberCallExpression + assertLocalName("test", memberCallExpr.base) + assertLocalName("c_str", memberCallExpr) } @Test @@ -381,12 +381,8 @@ internal class CXXLanguageFrontendTest : BaseTest() { assertNotNull(ifStatement.condition) assertEquals("bool", ifStatement.condition!!.type.typeName) assertEquals(true, (ifStatement.condition as Literal<*>).value) - assertTrue( - (ifStatement.thenStatement as CompoundStatement).statements[0] is ReturnStatement - ) - assertTrue( - (ifStatement.elseStatement as CompoundStatement).statements[0] is ReturnStatement - ) + assertTrue((ifStatement.thenStatement as Block).statements[0] is ReturnStatement) + assertTrue((ifStatement.elseStatement as Block).statements[0] is ReturnStatement) } @Test @@ -401,7 +397,7 @@ internal class CXXLanguageFrontendTest : BaseTest() { assertTrue(switchStatements.size == 3) val switchStatement = switchStatements[0] - assertTrue((switchStatement.statement as CompoundStatement).statements.size == 11) + assertTrue((switchStatement.statement as Block).statements.size == 11) val caseStatements = switchStatement.allChildren() assertTrue(caseStatements.size == 4) @@ -531,7 +527,7 @@ internal class CXXLanguageFrontendTest : BaseTest() { lhs = assignB.lhs() rhs = assignB.rhs() assertLocalName("a", lhs) - assertTrue(rhs is DeclaredReferenceExpression) + assertTrue(rhs is Reference) assertLocalName("b", rhs) assertRefersTo(rhs, b) @@ -550,8 +546,8 @@ internal class CXXLanguageFrontendTest : BaseTest() { fun testShiftExpression() { val file = File("src/test/resources/shiftexpression.cpp") val declaration = analyzeAndGetFirstTU(listOf(file), file.parentFile.toPath(), true) - val functionDeclaration = declaration.getDeclarationAs(0, FunctionDeclaration::class.java) - val statements = functionDeclaration?.statements + val functionDecl = declaration.getDeclarationAs(0, FunctionDeclaration::class.java) + val statements = functionDecl?.statements assertNotNull(statements) assertTrue(statements[1] is BinaryOperator) } @@ -639,14 +635,14 @@ internal class CXXLanguageFrontendTest : BaseTest() { var assign = statements[2] as? AssignExpression assertNotNull(assign) - var ref = assign.lhs() + var ref = assign.lhs() assertNotNull(ref) assertLocalName("a", ref) var binOp = assign.rhs() assertNotNull(binOp) - assertTrue(binOp.lhs is DeclaredReferenceExpression) + assertTrue(binOp.lhs is Reference) assertLocalName("b", binOp.lhs) assertTrue(binOp.rhs is Literal<*>) assertEquals(2, (binOp.rhs as Literal<*>).value) @@ -655,7 +651,7 @@ internal class CXXLanguageFrontendTest : BaseTest() { assign = statements[3] as? AssignExpression assertNotNull(assign) - ref = assign.lhs() + ref = assign.lhs() assertNotNull(ref) assertLocalName("a", ref) @@ -791,7 +787,7 @@ internal class CXXLanguageFrontendTest : BaseTest() { assertNotNull(methodCallWithConstant) val arg = methodCallWithConstant.arguments[0] - assertSame(constant, (arg as DeclaredReferenceExpression).refersTo) + assertSame(constant, (arg as Reference).refersTo) val anotherMethod = tu.methods["anotherMethod"] assertNotNull(anotherMethod) @@ -917,7 +913,7 @@ internal class CXXLanguageFrontendTest : BaseTest() { with(tu) { // get the main method val main = tu.getDeclarationAs(3, FunctionDeclaration::class.java) - val statement = main!!.body as CompoundStatement + val statement = main!!.body as Block // Integer i val i = @@ -927,10 +923,10 @@ internal class CXXLanguageFrontendTest : BaseTest() { assertEquals(tu.objectType("Integer"), i.type) // initializer should be a construct expression - var constructExpression = i.initializer as? ConstructExpression - assertNotNull(constructExpression) + var constructExpr = i.initializer as? ConstructExpression + assertNotNull(constructExpr) // type of the construct expression should also be Integer - assertEquals(tu.objectType("Integer"), constructExpression.type) + assertEquals(tu.objectType("Integer"), constructExpr.type) // auto (Integer) m val m = @@ -939,7 +935,7 @@ internal class CXXLanguageFrontendTest : BaseTest() { // type should be Integer* assertEquals(objectType("Integer").pointer(), m.type) - val constructor = constructExpression.constructor + val constructor = constructExpr.constructor assertNotNull(constructor) assertLocalName("Integer", constructor) assertFalse(constructor.isImplicit) @@ -951,13 +947,13 @@ internal class CXXLanguageFrontendTest : BaseTest() { assertEquals(objectType("Integer").pointer(), newExpression.type) // initializer should be a construct expression - constructExpression = newExpression.initializer as? ConstructExpression - assertNotNull(constructExpression) + constructExpr = newExpression.initializer as? ConstructExpression + assertNotNull(constructExpr) // type of the construct expression should be Integer - assertEquals(objectType("Integer"), constructExpression.type) + assertEquals(objectType("Integer"), constructExpr.type) // argument should be named k and of type m - val k = constructExpression.arguments[0] as DeclaredReferenceExpression + val k = constructExpr.arguments[0] as Reference assertLocalName("k", k) // type of the construct expression should also be Integer assertEquals(primitiveType("int"), k.type) @@ -966,7 +962,7 @@ internal class CXXLanguageFrontendTest : BaseTest() { private val FunctionDeclaration.statements: List? get() { - return (this.body as? CompoundStatement)?.statements + return (this.body as? Block)?.statements } @Test @@ -975,7 +971,7 @@ internal class CXXLanguageFrontendTest : BaseTest() { val file = File("src/test/resources/cfg.cpp") val declaration = analyzeAndGetFirstTU(listOf(file), file.parentFile.toPath(), true) val fdecl = declaration.getDeclarationAs(0, FunctionDeclaration::class.java) - val body = fdecl!!.body as CompoundStatement + val body = fdecl!!.body as Block val expected: MutableMap = HashMap() expected["cout << \"bla\";"] = Region(4, 3, 4, 17) expected["cout << \"blubb\";"] = Region(5, 3, 5, 19) @@ -1001,9 +997,9 @@ internal class CXXLanguageFrontendTest : BaseTest() { val method = declaration.getDeclarationAs(1, FunctionDeclaration::class.java) assertEquals("main()int", method!!.signature) - assertTrue(method.body is CompoundStatement) + assertTrue(method.body is Block) - val statements = (method.body as CompoundStatement).statements + val statements = (method.body as Block).statements assertEquals(4, statements.size) assertTrue(statements[0] is DeclarationStatement) assertTrue(statements[1] is DeclarationStatement) @@ -1020,19 +1016,19 @@ internal class CXXLanguageFrontendTest : BaseTest() { assertTrue(initializer.initializers[2] is DesignatedInitializerExpression) var die = initializer.initializers[0] as DesignatedInitializerExpression - assertTrue(die.lhs[0] is DeclaredReferenceExpression) + assertTrue(die.lhs[0] is Reference) assertTrue(die.rhs is Literal<*>) assertLocalName("y", die.lhs[0]) assertEquals(0, (die.rhs as Literal<*>).value) die = initializer.initializers[1] as DesignatedInitializerExpression - assertTrue(die.lhs[0] is DeclaredReferenceExpression) + assertTrue(die.lhs[0] is Reference) assertTrue(die.rhs is Literal<*>) assertLocalName("z", die.lhs[0]) assertEquals(1, (die.rhs as Literal<*>).value) die = initializer.initializers[2] as DesignatedInitializerExpression - assertTrue(die.lhs[0] is DeclaredReferenceExpression) + assertTrue(die.lhs[0] is Reference) assertTrue(die.rhs is Literal<*>) assertLocalName("x", die.lhs[0]) assertEquals(2, (die.rhs as Literal<*>).value) @@ -1045,7 +1041,7 @@ internal class CXXLanguageFrontendTest : BaseTest() { assertTrue(initializer.initializers[0] is DesignatedInitializerExpression) die = initializer.initializers[0] as DesignatedInitializerExpression - assertTrue(die.lhs[0] is DeclaredReferenceExpression) + assertTrue(die.lhs[0] is Reference) assertTrue(die.rhs is Literal<*>) assertLocalName("x", die.lhs[0]) assertEquals(20, (die.rhs as Literal<*>).value) @@ -1219,7 +1215,7 @@ internal class CXXLanguageFrontendTest : BaseTest() { tu.getDeclarationsByName("main", FunctionDeclaration::class.java).iterator().next() assertNotNull(main) - val body = main.body as CompoundStatement + val body = main.body as Block assertNotNull(body) val returnStatement = body.statements[body.statements.size - 1] @@ -1283,10 +1279,10 @@ internal class CXXLanguageFrontendTest : BaseTest() { val classTReturn = classTFoo.bodyOrNull() assertNotNull(classTReturn) - val classTReturnMember = classTReturn.returnValue as? MemberExpression - assertNotNull(classTReturnMember) + val classTReturnMemberExpression = classTReturn.returnValue as? MemberExpression + assertNotNull(classTReturnMemberExpression) - val classTThisExpression = classTReturnMember.base as? DeclaredReferenceExpression + val classTThisExpression = classTReturnMemberExpression.base as? Reference assertEquals(classTThisExpression?.refersTo, classTFoo.receiver) val classS = tu.byNameOrNull("S") @@ -1298,10 +1294,10 @@ internal class CXXLanguageFrontendTest : BaseTest() { val classSReturn = classSFoo.bodyOrNull() assertNotNull(classSReturn) - val classSReturnMember = classSReturn.returnValue as? MemberExpression - assertNotNull(classSReturnMember) + val classSReturnMemberExpression = classSReturn.returnValue as? MemberExpression + assertNotNull(classSReturnMemberExpression) - val classSThisExpression = classSReturnMember.base as? DeclaredReferenceExpression + val classSThisExpression = classSReturnMemberExpression.base as? Reference assertEquals(classSThisExpression?.refersTo, classSFoo.receiver) assertNotEquals(classTFoo, classSFoo) assertNotEquals(classTFoo.receiver, classSFoo.receiver) @@ -1320,9 +1316,9 @@ internal class CXXLanguageFrontendTest : BaseTest() { tu.getDeclarationsByName("main", FunctionDeclaration::class.java).iterator().next() assertNotNull(main) - val returnStmt = main.bodyOrNull() - assertNotNull(returnStmt) - assertNotNull((returnStmt.returnValue as? DeclaredReferenceExpression)?.refersTo) + val returnStatement = main.bodyOrNull() + assertNotNull(returnStatement) + assertNotNull((returnStatement.returnValue as? Reference)?.refersTo) } @Test @@ -1470,14 +1466,11 @@ internal class CXXLanguageFrontendTest : BaseTest() { tu.calls("no_param_uninitialized").firstOrNull { it.callee is UnaryOperator } assertInvokes(assertNotNull(noParamNoInitPointerCall), target) - val noParamCall = - tu.calls("no_param").firstOrNull { it.callee is DeclaredReferenceExpression } + val noParamCall = tu.calls("no_param").firstOrNull { it.callee is Reference } assertInvokes(assertNotNull(noParamCall), target) val noParamNoInitCall = - tu.calls("no_param_uninitialized").firstOrNull { - it.callee is DeclaredReferenceExpression - } + tu.calls("no_param_uninitialized").firstOrNull { it.callee is Reference } assertInvokes(assertNotNull(noParamNoInitCall), target) val targetCall = tu.calls["target"] @@ -1518,14 +1511,11 @@ internal class CXXLanguageFrontendTest : BaseTest() { tu.calls("no_param_uninitialized").firstOrNull { it.callee is UnaryOperator } assertInvokes(assertNotNull(noParamNoInitPointerCall), target) - val noParamCall = - tu.calls("no_param").firstOrNull { it.callee is DeclaredReferenceExpression } + val noParamCall = tu.calls("no_param").firstOrNull { it.callee is Reference } assertInvokes(assertNotNull(noParamCall), target) val noParamNoInitCall = - tu.calls("no_param_uninitialized").firstOrNull { - it.callee is DeclaredReferenceExpression - } + tu.calls("no_param_uninitialized").firstOrNull { it.callee is Reference } assertInvokes(assertNotNull(noParamNoInitCall), target) val targetCall = tu.calls["target"] diff --git a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXSymbolConfigurationTest.kt b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXSymbolConfigurationTest.kt index 8c9b894742..ccd406702c 100644 --- a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXSymbolConfigurationTest.kt +++ b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXSymbolConfigurationTest.kt @@ -31,8 +31,8 @@ import de.fraunhofer.aisec.cpg.frontends.TranslationException import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration 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.DeclaredReferenceExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.Literal +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Reference import java.io.File import kotlin.test.Test import kotlin.test.assertEquals @@ -63,7 +63,7 @@ internal class CXXSymbolConfigurationTest : BaseTest() { // without additional symbols, the first line will look like a reference (to something we do // not know) - val dre = binaryOperator.getRhsAs(DeclaredReferenceExpression::class.java) + val dre = binaryOperator.getRhsAs(Reference::class.java) assertNotNull(dre) assertLocalName("HELLO_WORLD", dre) diff --git a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolverTest.kt b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolverTest.kt index 21b9b6eaac..4e9ea44441 100644 --- a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolverTest.kt +++ b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolverTest.kt @@ -287,7 +287,7 @@ class CallResolverTest : BaseTest() { // Check defines edge assertEquals(displayDefinition, displayDeclaration.definition) - // Check defaults edge of ParamVariableDeclaration + // Check defaults edge of ParameterDeclaration assertEquals(displayDeclaration.defaultParameters, displayDefinition.defaultParameters) // Check call display(1); @@ -368,7 +368,7 @@ class CallResolverTest : BaseTest() { } val literalStar = findByUniquePredicate(result.literals) { it.value == '*' } val literal3 = findByUniquePredicate(result.literals) { it.value == 3 } - // Check defaults edge of ParamVariableDeclaration + // Check defaults edge of ParameterDeclaration assertTrue(displayFunction.defaultParameters[0] is Literal<*>) assertTrue(displayFunction.defaultParameters[1] is Literal<*>) assertEquals('*', (displayFunction.defaultParameters[0] as Literal<*>).value) diff --git a/cpg-language-go/src/main/golang/go.sum b/cpg-language-go/src/main/golang/go.sum index 9333aa5928..4ffefdd5d6 100644 --- a/cpg-language-go/src/main/golang/go.sum +++ b/cpg-language-go/src/main/golang/go.sum @@ -1,4 +1,27 @@ github.com/mattn/go-pointer v0.0.1 h1:n+XhsuGeVO6MEAp7xyEukFINEa+Quek5psIR/ylA6o0= github.com/mattn/go-pointer v0.0.1/go.mod h1:2zXcozF6qYGgmsG+SeTZz3oAbFLdD3OWqnUbNvJZAlc= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/DeclarationHandler.kt b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/DeclarationHandler.kt index 3d102dc966..5aa878b488 100644 --- a/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/DeclarationHandler.kt +++ b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/DeclarationHandler.kt @@ -29,8 +29,8 @@ 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.CompoundStatement import de.fraunhofer.aisec.cpg.graph.statements.ReturnStatement +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Block import de.fraunhofer.aisec.cpg.graph.types.Type import de.fraunhofer.aisec.cpg.graph.types.UnknownType @@ -171,7 +171,7 @@ class DeclarationHandler(frontend: GoLanguageFrontend) : frontend.typeOf(param.type) } - val p = newParamVariableDeclaration(name, type, variadic, rawNode = param) + val p = newParameterDeclaration(name, type, variadic, rawNode = param) frontend.scopeManager.addDeclaration(p) @@ -180,7 +180,7 @@ class DeclarationHandler(frontend: GoLanguageFrontend) : // Check, if the last statement is a return statement, otherwise we insert an implicit one val body = frontend.statementHandler.handle(funcDecl.body) - if (body is CompoundStatement) { + if (body is Block) { val last = body.statements.lastOrNull() if (last !is ReturnStatement) { val ret = newReturnStatement() diff --git a/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/ExpressionHandler.kt b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/ExpressionHandler.kt index 6d268e6b2e..ee5c702d98 100644 --- a/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/ExpressionHandler.kt +++ b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/ExpressionHandler.kt @@ -109,7 +109,7 @@ class ExpressionHandler(frontend: GoLanguageFrontend) : return literal } - val ref = newDeclaredReferenceExpression(ident.name, rawNode = ident) + val ref = newReference(ident.name, rawNode = ident) // Check, if this refers to a package import val import = frontend.currentTU?.getIncludeByName(ident.name) @@ -121,10 +121,8 @@ class ExpressionHandler(frontend: GoLanguageFrontend) : return ref } - private fun handleIndexExpr( - indexExpr: GoStandardLibrary.Ast.IndexExpr - ): ArraySubscriptionExpression { - val ase = newArraySubscriptionExpression(rawNode = indexExpr) + private fun handleIndexExpr(indexExpr: GoStandardLibrary.Ast.IndexExpr): SubscriptExpression { + val ase = newSubscriptExpression(rawNode = indexExpr) ase.arrayExpression = frontend.expressionHandler.handle(indexExpr.x) ase.subscriptExpression = frontend.expressionHandler.handle(indexExpr.index) @@ -224,7 +222,7 @@ class ExpressionHandler(frontend: GoLanguageFrontend) : val expression = // Actually make() can make more than just arrays, i.e. channels and maps if (args[0] is GoStandardLibrary.Ast.ArrayType) { - val array = newArrayCreationExpression(rawNode = callExpr) + val array = newNewArrayExpression(rawNode = callExpr) // second argument is a dimension (if this is an array), usually a literal if (args.size > 1) { @@ -250,9 +248,7 @@ class ExpressionHandler(frontend: GoLanguageFrontend) : return expression } - private fun handleSelectorExpr( - selectorExpr: GoStandardLibrary.Ast.SelectorExpr - ): DeclaredReferenceExpression { + private fun handleSelectorExpr(selectorExpr: GoStandardLibrary.Ast.SelectorExpr): Reference { val base = handle(selectorExpr.x) ?: newProblemExpression("missing base") // Check, if this just a regular reference to a variable with a package scope and not a @@ -273,7 +269,7 @@ class ExpressionHandler(frontend: GoLanguageFrontend) : // resolver will then resolve this val fqn = "${base.name}.${selectorExpr.sel.name}" - newDeclaredReferenceExpression(fqn, rawNode = selectorExpr) + newReference(fqn, rawNode = selectorExpr) } return ref @@ -281,14 +277,11 @@ class ExpressionHandler(frontend: GoLanguageFrontend) : /** * This function handles a ast.SliceExpr, which is an extended version of ast.IndexExpr. We are - * modelling this as a combination of a [ArraySubscriptionExpression] that contains a - * [RangeExpression] as its subscriptExpression to share some code between this and an index - * expression. + * modelling this as a combination of a [SubscriptExpression] that contains a [RangeExpression] + * as its subscriptExpression to share some code between this and an index expression. */ - private fun handleSliceExpr( - sliceExpr: GoStandardLibrary.Ast.SliceExpr - ): ArraySubscriptionExpression { - val ase = newArraySubscriptionExpression(rawNode = sliceExpr) + private fun handleSliceExpr(sliceExpr: GoStandardLibrary.Ast.SliceExpr): SubscriptExpression { + val ase = newSubscriptExpression(rawNode = sliceExpr) ase.arrayExpression = frontend.expressionHandler.handle(sliceExpr.x) ?: newProblemExpression("missing array expression") diff --git a/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/StatementHandler.kt b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/StatementHandler.kt index 0159cf2d42..3d84a4a80f 100644 --- a/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/StatementHandler.kt +++ b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/StatementHandler.kt @@ -96,7 +96,7 @@ class StatementHandler(frontend: GoLanguageFrontend) : } private fun handleBlockStmt(blockStmt: GoStandardLibrary.Ast.BlockStmt): Statement { - val compound = newCompoundStatement(rawNode = blockStmt) + val compound = newBlock(rawNode = blockStmt) frontend.scopeManager.enterScope(compound) @@ -180,7 +180,7 @@ class StatementHandler(frontend: GoLanguageFrontend) : * we create a call expression to a built-in call. */ private fun handleGoStmt(goStmt: GoStandardLibrary.Ast.GoStmt): CallExpression { - val ref = newDeclaredReferenceExpression("go") + val ref = newReference("go") val call = newCallExpression(ref, "go", rawNode = goStmt) call += frontend.expressionHandler.handle(goStmt.call) @@ -252,7 +252,7 @@ class StatementHandler(frontend: GoLanguageFrontend) : // TODO: not really the best way to deal with this // TODO: key type is always int. we could set this var ref = rangeStmt.key?.let { frontend.expressionHandler.handle(it) } - if (ref is DeclaredReferenceExpression) { + if (ref is Reference) { val key = newVariableDeclaration(ref.name, rawNode = rangeStmt.key) frontend.scopeManager.addDeclaration(key) stmt.addToPropertyEdgeDeclaration(key) @@ -260,7 +260,7 @@ class StatementHandler(frontend: GoLanguageFrontend) : // TODO: not really the best way to deal with this ref = rangeStmt.value?.let { frontend.expressionHandler.handle(it) } - if (ref is DeclaredReferenceExpression) { + if (ref is Reference) { val key = newVariableDeclaration(ref.name, rawNode = rangeStmt.key) frontend.scopeManager.addDeclaration(key) stmt.addToPropertyEdgeDeclaration(key) @@ -304,8 +304,7 @@ class StatementHandler(frontend: GoLanguageFrontend) : switchStmt.tag?.let { switch.selector = frontend.expressionHandler.handle(it) } val block = - handle(switchStmt.body) as? CompoundStatement - ?: return newProblemExpression("missing switch body") + handle(switchStmt.body) as? Block ?: return newProblemExpression("missing switch body") // Because of the way we parse the statements, the case statement turns out to be the last // statement. However, we need it to be the first statement, so we need to switch first and diff --git a/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/GoExtraPass.kt b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/GoExtraPass.kt index de3dd2c9b6..f9c20d952e 100644 --- a/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/GoExtraPass.kt +++ b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/GoExtraPass.kt @@ -37,7 +37,7 @@ import de.fraunhofer.aisec.cpg.graph.statements.ForEachStatement import de.fraunhofer.aisec.cpg.graph.statements.expressions.AssignExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.CastExpression -import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Reference import de.fraunhofer.aisec.cpg.graph.types.HasType import de.fraunhofer.aisec.cpg.graph.types.PointerType import de.fraunhofer.aisec.cpg.graph.types.Type @@ -174,7 +174,7 @@ class GoExtraPass(ctx: TranslationContext) : ComponentPass(ctx), ScopeProvider { // Loop through the target variables (left-hand side) for (expr in assign.lhs) { - if (expr is DeclaredReferenceExpression) { + if (expr is Reference) { // And try to resolve it val ref = scopeManager.resolveReference(expr) if (ref == null) { @@ -231,7 +231,7 @@ class GoExtraPass(ctx: TranslationContext) : ComponentPass(ctx), ScopeProvider { // cast expression. And this is only really necessary, if the function call has a single // argument. val callee = call.callee - if (parent != null && callee is DeclaredReferenceExpression && call.arguments.size == 1) { + if (parent != null && callee is Reference && call.arguments.size == 1) { val language = parent.language ?: GoLanguage() // First, check if this is a built-in type diff --git a/cpg-language-go/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/DeclarationTest.kt b/cpg-language-go/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/DeclarationTest.kt index 5e379141c1..ff07d6aa1a 100644 --- a/cpg-language-go/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/DeclarationTest.kt +++ b/cpg-language-go/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/DeclarationTest.kt @@ -34,7 +34,7 @@ import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.NamespaceDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration -import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Reference import de.fraunhofer.aisec.cpg.graph.variables import java.nio.file.Path import kotlin.test.* @@ -163,7 +163,7 @@ class DeclarationTest { assertNotNull(printf) printf.arguments.drop(1).forEach { - val ref = assertIs(it) + val ref = assertIs(it) assertNotNull(ref.refersTo) } diff --git a/cpg-language-go/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/ExpressionTest.kt b/cpg-language-go/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/ExpressionTest.kt index 7921cec047..3bcdab33be 100644 --- a/cpg-language-go/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/ExpressionTest.kt +++ b/cpg-language-go/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/ExpressionTest.kt @@ -70,7 +70,7 @@ class ExpressionTest { val cast = s.initializer as? CastExpression assertNotNull(cast) assertFullName("main.MyStruct", cast.castType) - assertSame(f, (cast.expression as? DeclaredReferenceExpression)?.refersTo) + assertSame(f, (cast.expression as? Reference)?.refersTo) val ignored = main.variables("_") ignored.forEach { assertIs(it.initializer) } @@ -100,7 +100,7 @@ class ExpressionTest { // [:1] var slice = assertIs( - assertIs(b.initializer).subscriptExpression + assertIs(b.initializer).subscriptExpression ) assertNull(slice.floor) assertLiteralValue(1, slice.ceiling) @@ -111,7 +111,7 @@ class ExpressionTest { assertLocalName("int[]", c.type) // [1:] - slice = assertIs(assertIs(c.initializer).subscriptExpression) + slice = assertIs(assertIs(c.initializer).subscriptExpression) assertLiteralValue(1, slice.floor) assertNull(slice.ceiling) assertNull(slice.third) @@ -121,7 +121,7 @@ class ExpressionTest { assertLocalName("int[]", d.type) // [0:1] - slice = assertIs(assertIs(d.initializer).subscriptExpression) + slice = assertIs(assertIs(d.initializer).subscriptExpression) assertLiteralValue(0, slice.floor) assertLiteralValue(1, slice.ceiling) assertNull(slice.third) @@ -131,7 +131,7 @@ class ExpressionTest { assertLocalName("int[]", e.type) // [0:1:1] - slice = assertIs(assertIs(e.initializer).subscriptExpression) + slice = assertIs(assertIs(e.initializer).subscriptExpression) assertLiteralValue(0, slice.floor) assertLiteralValue(1, slice.ceiling) assertLiteralValue(1, slice.third) diff --git a/cpg-language-go/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoLanguageFrontendTest.kt b/cpg-language-go/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoLanguageFrontendTest.kt index 37776c62a3..c778028686 100644 --- a/cpg-language-go/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoLanguageFrontendTest.kt +++ b/cpg-language-go/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoLanguageFrontendTest.kt @@ -121,7 +121,7 @@ class GoLanguageFrontendTest : BaseTest() { val main = p.functions["main"] assertNotNull(main) - val body = main.body as? CompoundStatement + val body = main.body as? Block assertNotNull(body) var decl = main.variables["o"] @@ -143,7 +143,7 @@ class GoLanguageFrontendTest : BaseTest() { assertNotNull(make) with(tu) { assertEquals(tu.primitiveType("int").array(), make.type) } - assertTrue(make is ArrayCreationExpression) + assertTrue(make is NewArrayExpression) val dimension = make.dimensions.first() as? Literal<*> assertNotNull(dimension) @@ -266,7 +266,7 @@ class GoLanguageFrontendTest : BaseTest() { assertEquals(myTest.returnTypes.size, type.returnTypes.size) assertEquals(listOf("int", "error"), type.returnTypes.map { it.name.localName }) - var body = main.body as? CompoundStatement + var body = main.body as? Block assertNotNull(body) var callExpression = body.calls.firstOrNull() @@ -282,7 +282,7 @@ class GoLanguageFrontendTest : BaseTest() { assertLocalName("myTest", myTest) - body = myTest.body as? CompoundStatement + body = myTest.body as? Block assertNotNull(body) callExpression = body.statements.first() as? CallExpression @@ -297,7 +297,7 @@ class GoLanguageFrontendTest : BaseTest() { assertEquals("%s", literal.value) assertEquals(tu.primitiveType("string"), literal.type) - val ref = callExpression.arguments[1] as? DeclaredReferenceExpression + val ref = callExpression.arguments[1] as? Reference assertNotNull(ref) assertLocalName("s", ref) @@ -306,7 +306,7 @@ class GoLanguageFrontendTest : BaseTest() { val stmt = body.statements[1] as? AssignExpression assertNotNull(stmt) - val a = stmt.lhs.firstOrNull() as? DeclaredReferenceExpression + val a = stmt.lhs.firstOrNull() as? Reference assertNotNull(a) assertLocalName("a", a) @@ -382,7 +382,7 @@ class GoLanguageFrontendTest : BaseTest() { val newMyStruct = p.functions["NewMyStruct"] assertNotNull(newMyStruct) - val body = newMyStruct.body as? CompoundStatement + val body = newMyStruct.body as? Block assertNotNull(body) @@ -416,7 +416,7 @@ class GoLanguageFrontendTest : BaseTest() { assertNotNull(myFunc) assertLocalName("MyFunc", myFunc) - val body = myFunc.body as? CompoundStatement + val body = myFunc.body as? Block assertNotNull(body) @@ -432,7 +432,7 @@ class GoLanguageFrontendTest : BaseTest() { assertLocalName("myOtherFunc", arg1) assertFullName("p.MyStruct.myOtherFunc", arg1) - assertEquals(myFunc.receiver, (arg1.base as? DeclaredReferenceExpression)?.refersTo) + assertEquals(myFunc.receiver, (arg1.base as? Reference)?.refersTo) } @Test @@ -451,7 +451,7 @@ class GoLanguageFrontendTest : BaseTest() { val myFunc = p.methods["myFunc"] assertNotNull(myFunc) - val body = myFunc.body as? CompoundStatement + val body = myFunc.body as? Block assertNotNull(body) val assign = body.statements.first() as? AssignExpression @@ -459,11 +459,11 @@ class GoLanguageFrontendTest : BaseTest() { val lhs = assign.lhs.firstOrNull() as? MemberExpression assertNotNull(lhs) - assertEquals(myFunc.receiver, (lhs.base as? DeclaredReferenceExpression)?.refersTo) + assertEquals(myFunc.receiver, (lhs.base as? Reference)?.refersTo) assertLocalName("Field", lhs) assertEquals(tu.primitiveType("int"), lhs.type) - val rhs = assign.rhs.firstOrNull() as? DeclaredReferenceExpression + val rhs = assign.rhs.firstOrNull() as? Reference assertNotNull(rhs) assertFullName("otherPackage.OtherField", rhs) } @@ -480,7 +480,7 @@ class GoLanguageFrontendTest : BaseTest() { val main = tu.functions["p.main"] assertNotNull(main) - val body = main.body as? CompoundStatement + val body = main.body as? Block assertNotNull(body) val b = @@ -493,7 +493,7 @@ class GoLanguageFrontendTest : BaseTest() { // true, false are builtin variables, NOT literals in Golang // we might need to parse this special case differently - val initializer = b.initializer as? DeclaredReferenceExpression + val initializer = b.initializer as? Reference assertNotNull(initializer) assertLocalName("true", initializer) @@ -516,13 +516,13 @@ class GoLanguageFrontendTest : BaseTest() { val myFunc = tu.functions["p.myFunc"] assertNotNull(myFunc) - val body = myFunc.body as? CompoundStatement + val body = myFunc.body as? Block assertNotNull(body) val switch = body.statements.first() as? SwitchStatement assertNotNull(switch) - val list = switch.statement as? CompoundStatement + val list = switch.statement as? Block assertNotNull(list) val case1 = list.statements[0] as? CaseStatement @@ -581,7 +581,7 @@ class GoLanguageFrontendTest : BaseTest() { val main = p.functions["main"] assertNotNull(main) - val body = main.body as? CompoundStatement + val body = main.body as? Block assertNotNull(body) val c = body.variables["c"] @@ -606,7 +606,7 @@ class GoLanguageFrontendTest : BaseTest() { val call = body.statements[1] as? MemberCallExpression assertNotNull(call) - val base = call.base as? DeclaredReferenceExpression + val base = call.base as? Reference assertNotNull(base) assertEquals(c, base.refersTo) @@ -634,14 +634,14 @@ class GoLanguageFrontendTest : BaseTest() { val f = main.bodyOrNull() assertNotNull(f) assertTrue(f.condition is BinaryOperator) - assertTrue(f.statement is CompoundStatement) + assertTrue(f.statement is Block) assertTrue(f.initializerStatement is AssignExpression) assertTrue(f.iterationStatement is UnaryOperator) val each = main.bodyOrNull() assertNotNull(each) - val bytes = assertIs(each.iterable) + val bytes = assertIs(each.iterable) assertLocalName("bytes", bytes) assertNotNull(bytes.refersTo) @@ -782,7 +782,7 @@ class GoLanguageFrontendTest : BaseTest() { val assign = tu.functions["main"].assignments.firstOrNull { - (it.target as? DeclaredReferenceExpression)?.refersTo == i + (it.target as? Reference)?.refersTo == i } assertNotNull(assign) diff --git a/cpg-language-go/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/StatementTest.kt b/cpg-language-go/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/StatementTest.kt index 3e698bc905..1abacc2c4c 100644 --- a/cpg-language-go/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/StatementTest.kt +++ b/cpg-language-go/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/StatementTest.kt @@ -29,8 +29,8 @@ import de.fraunhofer.aisec.cpg.TestUtils.analyzeAndGetFirstTU import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.statements.* import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression -import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.Literal +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Reference import de.fraunhofer.aisec.cpg.graph.statements.expressions.UnaryOperator import java.nio.file.Path import kotlin.test.Test @@ -102,7 +102,7 @@ class StatementTest { // The EOG for the defer statement itself should be in the regular EOG path op.prevEOG.any { it is CallExpression && it.name.localName == "do" } - op.nextEOG.any { it is DeclaredReferenceExpression && it.name.localName == "that" } + op.nextEOG.any { it is Reference && it.name.localName == "that" } // It should NOT connect to the call expression op.nextEOG.none { it is CallExpression } diff --git a/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/DeclarationHandler.kt b/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/DeclarationHandler.kt index c837e27051..285cacf5b9 100644 --- a/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/DeclarationHandler.kt +++ b/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/DeclarationHandler.kt @@ -55,25 +55,27 @@ open class DeclarationHandler(lang: JavaLanguageFrontend) : lang ) { fun handleConstructorDeclaration( - constructorDecl: ConstructorDeclaration + constructorDeclaration: ConstructorDeclaration ): de.fraunhofer.aisec.cpg.graph.declarations.ConstructorDeclaration { - val resolvedConstructor = constructorDecl.resolve() + val resolvedConstructor = constructorDeclaration.resolve() val currentRecordDecl = frontend.scopeManager.currentRecord val declaration = this.newConstructorDeclaration( resolvedConstructor.name, - constructorDecl.toString(), + constructorDeclaration.toString(), currentRecordDecl ) frontend.scopeManager.addDeclaration(declaration) frontend.scopeManager.enterScope(declaration) createMethodReceiver(currentRecordDecl, declaration) declaration.addThrowTypes( - constructorDecl.thrownExceptions.map { type: ReferenceType -> frontend.typeOf(type) } + constructorDeclaration.thrownExceptions.map { type: ReferenceType -> + frontend.typeOf(type) + } ) - for (parameter in constructorDecl.parameters) { + for (parameter in constructorDeclaration.parameters) { val param = - this.newParamVariableDeclaration( + this.newParameterDeclaration( parameter.nameAsString, frontend.getTypeAsGoodAsPossible(parameter, parameter.resolve()), parameter.isVarArgs @@ -92,10 +94,10 @@ open class DeclarationHandler(lang: JavaLanguageFrontend) : } // check, if constructor has body (i.e. it's not abstract or something) - val body = constructorDecl.body + val body = constructorDeclaration.body addImplicitReturn(body) declaration.body = frontend.statementHandler.handle(body) - frontend.processAnnotations(declaration, constructorDecl) + frontend.processAnnotations(declaration, constructorDeclaration) frontend.scopeManager.leaveScope(declaration) return declaration } @@ -127,7 +129,7 @@ open class DeclarationHandler(lang: JavaLanguageFrontend) : resolvedType = frontend.getTypeAsGoodAsPossible(parameter, parameter.resolve()) } val param = - this.newParamVariableDeclaration( + this.newParameterDeclaration( parameter.nameAsString, resolvedType, parameter.isVarArgs @@ -157,14 +159,14 @@ open class DeclarationHandler(lang: JavaLanguageFrontend) : } private fun createMethodReceiver( - recordDecl: RecordDeclaration?, + recordDeclaration: RecordDeclaration?, functionDeclaration: de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration ) { // create the receiver val receiver = this.newVariableDeclaration( "this", - recordDecl?.toType() ?: unknownType(), + recordDeclaration?.toType() ?: unknownType(), "this", false ) 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 5950b651d6..3f2aed1749 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 @@ -29,8 +29,30 @@ 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.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.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 @@ -40,6 +62,8 @@ import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration import de.fraunhofer.aisec.cpg.graph.statements.* 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 java.util.function.Supplier import kotlin.collections.set @@ -57,7 +81,7 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : for (parameter in lambdaExpr.parameters) { val resolvedType = frontend.getTypeAsGoodAsPossible(parameter.type) val param = - this.newParamVariableDeclaration( + this.newParameterDeclaration( parameter.nameAsString, resolvedType, parameter.isVarArgs @@ -103,15 +127,15 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : } /** - * Creates a new [ArrayCreationExpression], which is usually used as an initializer of a + * Creates a new [NewArrayExpression], which is usually used as an initializer of a * [VariableDeclaration]. * * @param expr the expression - * @return the [ArrayCreationExpression] + * @return the [NewArrayExpression] */ private fun handleArrayCreationExpr(expr: Expression): Statement { val arrayCreationExpr = expr as ArrayCreationExpr - val creationExpression = this.newArrayCreationExpression(expr.toString()) + val creationExpression = this.newNewArrayExpression(expr.toString()) // in Java, an array creation expression either specifies an initializer or dimensions @@ -156,9 +180,9 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : return initList } - private fun handleArrayAccessExpr(expr: Expression): ArraySubscriptionExpression { + private fun handleArrayAccessExpr(expr: Expression): SubscriptExpression { val arrayAccessExpr = expr as ArrayAccessExpr - val arraySubsExpression = this.newArraySubscriptionExpression(expr.toString()) + val arraySubsExpression = this.newSubscriptExpression(expr.toString()) (handle(arrayAccessExpr.name) as de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression?) ?.let { arraySubsExpression.arrayExpression = it } @@ -255,7 +279,7 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : val initializer = handle(oInitializer.get()) as de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression? - if (initializer is ArrayCreationExpression) { + if (initializer is NewArrayExpression) { declaration.isArray = true } declaration.initializer = initializer @@ -334,12 +358,7 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : } } } - base = - this.newDeclaredReferenceExpression( - scope.asNameExpr().nameAsString, - baseType, - scope.toString() - ) + base = this.newReference(scope.asNameExpr().nameAsString, baseType, scope.toString()) base.isStaticAccess = isStaticAccess frontend.setCodeAndLocation(base, fieldAccessExpr.scope) } else if (scope.isFieldAccessExpr) { @@ -353,7 +372,7 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : // to this base tester = tester.base } - if (tester is DeclaredReferenceExpression && tester.isStaticAccess) { + if (tester is Reference && tester.isStaticAccess) { // try to get the name val name: String val tokenRange = scope.asFieldAccessExpr().tokenRange @@ -372,7 +391,7 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : unknownType() } base = - this.newDeclaredReferenceExpression( + this.newReference( scope.asFieldAccessExpr().nameAsString, baseType, scope.toString() @@ -483,11 +502,11 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : } } - private fun handleClassExpression(expr: Expression): DeclaredReferenceExpression { + private fun handleClassExpression(expr: Expression): Reference { val classExpr = expr.asClassExpr() val type = frontend.typeOf(classExpr.type) val thisExpression = - this.newDeclaredReferenceExpression( + this.newReference( classExpr.toString().substring(classExpr.toString().lastIndexOf('.') + 1), type, classExpr.toString() @@ -497,7 +516,7 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : return thisExpression } - private fun handleThisExpression(expr: Expression): DeclaredReferenceExpression { + private fun handleThisExpression(expr: Expression): Reference { val thisExpr = expr.asThisExpr() val resolvedValueDeclaration = thisExpr.resolve() val type = this.objectType(resolvedValueDeclaration.qualifiedName) @@ -512,17 +531,16 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : if (typeName.isPresent) { name = "this$" + typeName.get().identifier } - val thisExpression = this.newDeclaredReferenceExpression(name, type, thisExpr.toString()) + val thisExpression = this.newReference(name, type, thisExpr.toString()) frontend.setCodeAndLocation(thisExpression, thisExpr) return thisExpression } - private fun handleSuperExpression(expr: Expression): DeclaredReferenceExpression { + private fun handleSuperExpression(expr: Expression): Reference { // The actual type is hard to determine at this point, as we may not have full information // about the inheritance structure. Thus, we delay the resolving to the variable resolving // process - val superExpression = - this.newDeclaredReferenceExpression(expr.toString(), unknownType(), expr.toString()) + val superExpression = this.newReference(expr.toString(), unknownType(), expr.toString()) frontend.setCodeAndLocation(superExpression, expr) return superExpression } @@ -539,7 +557,7 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : // try { // ResolvedType resolvedType = nameExpr.calculateResolvedType(); // if (resolvedType.isReferenceType()) { - // return newDeclaredReferenceExpression( + // return newReference( // nameExpr.getNameAsString(), // new Type(((ReferenceTypeImpl) resolvedType).getQualifiedName()), // nameExpr.toString()); @@ -548,7 +566,7 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : // UnsolvedSymbolException // e) { // this might throw, e.g. if the type is simply not defined (i.e., syntax // error) - // return newDeclaredReferenceExpression( + // return newReference( // nameExpr.getNameAsString(), new Type(UNKNOWN_TYPE), nameExpr.toString()); // } val name = this.parseName(nameExpr.nameAsString) @@ -603,7 +621,7 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : if (type == null) { type = frontend.typeOf(symbol.type) } - this.newDeclaredReferenceExpression(symbol.name, type, nameExpr.toString()) + this.newReference(symbol.name, type, nameExpr.toString()) } } catch (ex: UnsolvedSymbolException) { val typeString: String? = @@ -623,8 +641,7 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : t = this.objectType(typeString) t.typeOrigin = Type.Origin.GUESSED } - val declaredReferenceExpression = - this.newDeclaredReferenceExpression(name, t, nameExpr.toString()) + val declaredReferenceExpression = this.newReference(name, t, nameExpr.toString()) val recordDeclaration = frontend.scopeManager.currentRecord if (recordDeclaration != null && recordDeclaration.name.lastPartsMatch(name)) { declaredReferenceExpression.refersTo = recordDeclaration @@ -633,11 +650,11 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : } catch (ex: RuntimeException) { val t = unknownType() log.info("Unresolved symbol: {}", nameExpr.nameAsString) - this.newDeclaredReferenceExpression(nameExpr.nameAsString, t, nameExpr.toString()) + this.newReference(nameExpr.nameAsString, t, nameExpr.toString()) } catch (ex: NoClassDefFoundError) { val t = unknownType() log.info("Unresolved symbol: {}", nameExpr.nameAsString) - this.newDeclaredReferenceExpression(nameExpr.nameAsString, t, nameExpr.toString()) + this.newReference(nameExpr.nameAsString, t, nameExpr.toString()) } } @@ -745,7 +762,7 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : ?: newProblemExpression("Could not parse base") // If the base directly refers to a record, then this is a static call - if (base is DeclaredReferenceExpression && base.refersTo is RecordDeclaration) { + if (base is Reference && base.refersTo is RecordDeclaration) { isStatic = true } } else { @@ -767,7 +784,7 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : this.parseName(qualifiedName).parent } baseType = this.objectType(baseName ?: Type.UNKNOWN_TYPE_STRING) - base = this.newDeclaredReferenceExpression(baseName, baseType) + base = this.newReference(baseName, baseType) } else { // Since it is possible to omit the "this" keyword, some methods in java do not have // a base. @@ -811,7 +828,7 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : val thisType = (frontend.scopeManager.currentFunction as MethodDeclaration?)?.receiver?.type ?: unknownType() - base = this.newDeclaredReferenceExpression("this", thisType, "this") + base = this.newReference("this", thisType, "this") base.isImplicit = true return base } @@ -881,7 +898,7 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : ctor.arguments.forEachIndexed { i, arg -> constructorDeclaration.addParameter( - newParamVariableDeclaration("arg${i}", arg.type) + newParameterDeclaration("arg${i}", arg.type) ) } anonymousRecord.addConstructor(constructorDeclaration) @@ -900,7 +917,9 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : } init { - map[AssignExpr::class.java] = HandlerInterface { handleAssignmentExpression(it) } + map[com.github.javaparser.ast.expr.AssignExpr::class.java] = HandlerInterface { + handleAssignmentExpression(it) + } map[FieldAccessExpr::class.java] = HandlerInterface { handleFieldAccessExpression(it) } map[LiteralExpr::class.java] = HandlerInterface { handleLiteralExpression(it) } map[ThisExpr::class.java] = HandlerInterface { handleThisExpression(it) } @@ -915,12 +934,18 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : } map[MethodCallExpr::class.java] = HandlerInterface { handleMethodCallExpression(it) } map[ObjectCreationExpr::class.java] = HandlerInterface { handleObjectCreationExpr(it) } - map[ConditionalExpr::class.java] = HandlerInterface { handleConditionalExpression(it) } + map[com.github.javaparser.ast.expr.ConditionalExpr::class.java] = HandlerInterface { + handleConditionalExpression(it) + } map[EnclosedExpr::class.java] = HandlerInterface { handleEnclosedExpression(it) } map[ArrayAccessExpr::class.java] = HandlerInterface { handleArrayAccessExpr(it) } map[ArrayCreationExpr::class.java] = HandlerInterface { handleArrayCreationExpr(it) } map[ArrayInitializerExpr::class.java] = HandlerInterface { handleArrayInitializerExpr(it) } - map[CastExpr::class.java] = HandlerInterface { handleCastExpr(it) } - map[LambdaExpr::class.java] = HandlerInterface { handleLambdaExpr(it) } + map[com.github.javaparser.ast.expr.CastExpr::class.java] = HandlerInterface { + handleCastExpr(it) + } + map[com.github.javaparser.ast.expr.LambdaExpr::class.java] = HandlerInterface { + handleLambdaExpr(it) + } } } diff --git a/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/StatementHandler.kt b/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/StatementHandler.kt index 4dd85bb37e..70c79305ad 100644 --- a/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/StatementHandler.kt +++ b/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/StatementHandler.kt @@ -38,7 +38,21 @@ import de.fraunhofer.aisec.cpg.frontends.HandlerInterface import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration import de.fraunhofer.aisec.cpg.graph.statements.* -import de.fraunhofer.aisec.cpg.graph.statements.expressions.ExplicitConstructorInvocation +import de.fraunhofer.aisec.cpg.graph.statements.AssertStatement +import de.fraunhofer.aisec.cpg.graph.statements.BreakStatement +import de.fraunhofer.aisec.cpg.graph.statements.ContinueStatement +import de.fraunhofer.aisec.cpg.graph.statements.DoStatement +import de.fraunhofer.aisec.cpg.graph.statements.EmptyStatement +import de.fraunhofer.aisec.cpg.graph.statements.ForEachStatement +import de.fraunhofer.aisec.cpg.graph.statements.ForStatement +import de.fraunhofer.aisec.cpg.graph.statements.IfStatement +import de.fraunhofer.aisec.cpg.graph.statements.ReturnStatement +import de.fraunhofer.aisec.cpg.graph.statements.SwitchStatement +import de.fraunhofer.aisec.cpg.graph.statements.SynchronizedStatement +import de.fraunhofer.aisec.cpg.graph.statements.TryStatement +import de.fraunhofer.aisec.cpg.graph.statements.WhileStatement +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Block +import de.fraunhofer.aisec.cpg.graph.statements.expressions.ConstructorCallExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.Literal import de.fraunhofer.aisec.cpg.graph.statements.expressions.ProblemExpression import de.fraunhofer.aisec.cpg.graph.types.Type @@ -306,7 +320,7 @@ class StatementHandler(lang: JavaLanguageFrontend?) : synchronizedCPG.expression = frontend.expressionHandler.handle(synchronizedJava.expression) as de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression - synchronizedCPG.blockStatement = handle(synchronizedJava.body) as CompoundStatement? + synchronizedCPG.block = handle(synchronizedJava.body) as Block? return synchronizedCPG } @@ -336,11 +350,11 @@ class StatementHandler(lang: JavaLanguageFrontend?) : return continueStatement } - fun handleBlockStatement(stmt: Statement): CompoundStatement { + fun handleBlockStatement(stmt: Statement): Block { val blockStmt = stmt.asBlockStmt() // first of, all we need a compound statement - val compoundStatement = this.newCompoundStatement(stmt.toString()) + val compoundStatement = this.newBlock(stmt.toString()) frontend.scopeManager.enterScope(compoundStatement) for (child in blockStmt.statements) { val statement = handle(child) @@ -483,7 +497,7 @@ class StatementHandler(lang: JavaLanguageFrontend?) : start = getNextTokenWith("{", tokenRangeSelector.get().end) end = getPreviousTokenWith("}", tokenRange.get().end) } - val compoundStatement = this.newCompoundStatement(getCodeBetweenTokens(start, end)) + val compoundStatement = this.newBlock(getCodeBetweenTokens(start, end)) compoundStatement.location = getLocationsFromTokens(switchStatement.location, start, end) for (sentry in switchStmt.entries) { if (sentry.labels.isEmpty()) { @@ -503,10 +517,8 @@ class StatementHandler(lang: JavaLanguageFrontend?) : return switchStatement } - private fun handleExplicitConstructorInvocation( - stmt: Statement - ): ExplicitConstructorInvocation { - val eciStatement = stmt.asExplicitConstructorInvocationStmt() + private fun handleExplicitConstructorInvocation(stmt: Statement): ConstructorCallExpression { + val explicitConstructorInvocationStmt = stmt.asExplicitConstructorInvocationStmt() var containingClass = "" val currentRecord = frontend.scopeManager.currentRecord if (currentRecord == null) { @@ -516,9 +528,13 @@ class StatementHandler(lang: JavaLanguageFrontend?) : } else { containingClass = currentRecord.name.toString() } - val node = this.newExplicitConstructorInvocation(containingClass, eciStatement.toString()) + val node = + this.newConstructorCallExpression( + containingClass, + explicitConstructorInvocationStmt.toString() + ) val arguments = - eciStatement.arguments + explicitConstructorInvocationStmt.arguments .stream() .map { ctx: Expression -> frontend.expressionHandler.handle(ctx) } .map { obj: de.fraunhofer.aisec.cpg.graph.statements.Statement? -> @@ -604,27 +620,42 @@ class StatementHandler(lang: JavaLanguageFrontend?) : } init { - map[IfStmt::class.java] = HandlerInterface { stmt: Statement -> handleIfStatement(stmt) } - map[AssertStmt::class.java] = HandlerInterface { stmt: Statement -> - handleAssertStatement(stmt) - } - map[WhileStmt::class.java] = HandlerInterface { stmt: Statement -> - handleWhileStatement(stmt) - } - map[DoStmt::class.java] = HandlerInterface { stmt: Statement -> handleDoStatement(stmt) } - map[ForEachStmt::class.java] = HandlerInterface { stmt: Statement -> - handleForEachStatement(stmt) - } - map[ForStmt::class.java] = HandlerInterface { stmt: Statement -> handleForStatement(stmt) } - map[BreakStmt::class.java] = HandlerInterface { stmt: Statement -> - handleBreakStatement(stmt) - } - map[ContinueStmt::class.java] = HandlerInterface { stmt: Statement -> - handleContinueStatement(stmt) - } - map[ReturnStmt::class.java] = HandlerInterface { stmt: Statement -> - handleReturnStatement(stmt) - } + map[com.github.javaparser.ast.stmt.IfStmt::class.java] = + HandlerInterface { stmt: Statement -> + handleIfStatement(stmt) + } + map[com.github.javaparser.ast.stmt.AssertStmt::class.java] = + HandlerInterface { stmt: Statement -> + handleAssertStatement(stmt) + } + map[com.github.javaparser.ast.stmt.WhileStmt::class.java] = + HandlerInterface { stmt: Statement -> + handleWhileStatement(stmt) + } + map[com.github.javaparser.ast.stmt.DoStmt::class.java] = + HandlerInterface { stmt: Statement -> + handleDoStatement(stmt) + } + map[com.github.javaparser.ast.stmt.ForEachStmt::class.java] = + HandlerInterface { stmt: Statement -> + handleForEachStatement(stmt) + } + map[com.github.javaparser.ast.stmt.ForStmt::class.java] = + HandlerInterface { stmt: Statement -> + handleForStatement(stmt) + } + map[com.github.javaparser.ast.stmt.BreakStmt::class.java] = + HandlerInterface { stmt: Statement -> + handleBreakStatement(stmt) + } + map[com.github.javaparser.ast.stmt.ContinueStmt::class.java] = + HandlerInterface { stmt: Statement -> + handleContinueStatement(stmt) + } + map[com.github.javaparser.ast.stmt.ReturnStmt::class.java] = + HandlerInterface { stmt: Statement -> + handleReturnStatement(stmt) + } map[BlockStmt::class.java] = HandlerInterface { stmt: Statement -> handleBlockStatement(stmt) } @@ -637,16 +668,22 @@ class StatementHandler(lang: JavaLanguageFrontend?) : map[ExpressionStmt::class.java] = HandlerInterface { stmt: Statement -> handleExpressionStatement(stmt) } - map[SwitchStmt::class.java] = HandlerInterface { stmt: Statement -> - handleSwitchStatement(stmt) - } - map[EmptyStmt::class.java] = HandlerInterface { stmt: Statement -> - handleEmptyStatement(stmt) - } - map[SynchronizedStmt::class.java] = HandlerInterface { stmt: Statement -> - handleSynchronizedStatement(stmt) - } - map[TryStmt::class.java] = HandlerInterface { stmt: Statement -> handleTryStatement(stmt) } + map[com.github.javaparser.ast.stmt.SwitchStmt::class.java] = + HandlerInterface { stmt: Statement -> + handleSwitchStatement(stmt) + } + map[com.github.javaparser.ast.stmt.EmptyStmt::class.java] = + HandlerInterface { stmt: Statement -> + handleEmptyStatement(stmt) + } + map[com.github.javaparser.ast.stmt.SynchronizedStmt::class.java] = + HandlerInterface { stmt: Statement -> + handleSynchronizedStatement(stmt) + } + map[com.github.javaparser.ast.stmt.TryStmt::class.java] = + HandlerInterface { stmt: Statement -> + handleTryStatement(stmt) + } map[ThrowStmt::class.java] = HandlerInterface { stmt: Statement -> handleThrowStmt(stmt) } } } diff --git a/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/JavaCallResolverHelper.kt b/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/JavaCallResolverHelper.kt index d1ddc7973a..e556311e66 100644 --- a/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/JavaCallResolverHelper.kt +++ b/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/JavaCallResolverHelper.kt @@ -31,9 +31,9 @@ import de.fraunhofer.aisec.cpg.graph.Name import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration import de.fraunhofer.aisec.cpg.graph.objectType -import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberCallExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberExpression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Reference import de.fraunhofer.aisec.cpg.graph.types.HasType import de.fraunhofer.aisec.cpg.helpers.Util import de.fraunhofer.aisec.cpg.passes.CallResolver.Companion.LOGGER @@ -45,9 +45,9 @@ class JavaCallResolverHelper { * Handle calls in the form of `super.call()` or `ClassName.super.call()`, conforming to * JLS13 §15.12.1. * - * This function basically sets the correct type of the [DeclaredReferenceExpression] - * containing the "super" keyword. Afterwards, we can use the regular - * [CallResolver.resolveMemberCallee] to resolve the [MemberCallExpression]. + * This function basically sets the correct type of the [Reference] containing the "super" + * keyword. Afterwards, we can use the regular [CallResolver.resolveMemberCallee] to resolve + * the [MemberCallExpression]. * * @param callee The callee of the call expression that needs to be adjusted * @param curClass The class containing the call @@ -63,7 +63,7 @@ class JavaCallResolverHelper { // need to connect the super reference to the receiver of this method. val func = scopeManager.currentFunction if (func is MethodDeclaration) { - (callee.base as DeclaredReferenceExpression?)?.refersTo = func.receiver + (callee.base as Reference?)?.refersTo = func.receiver } // In the next step we can "cast" the base to the correct type, by setting the base @@ -95,7 +95,7 @@ class JavaCallResolverHelper { // the "this" object to the super class callee.base.type = superType - val refersTo = (callee.base as? DeclaredReferenceExpression)?.refersTo + val refersTo = (callee.base as? Reference)?.refersTo if (refersTo is HasType) { refersTo.type = superType refersTo.assignedTypes = mutableSetOf(superType) diff --git a/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/EOGTest.kt b/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/EOGTest.kt index 543adfc00d..50179304b3 100644 --- a/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/EOGTest.kt +++ b/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/EOGTest.kt @@ -37,7 +37,7 @@ import de.fraunhofer.aisec.cpg.graph.edge.Properties import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge import de.fraunhofer.aisec.cpg.graph.statements.* import de.fraunhofer.aisec.cpg.graph.statements.expressions.BinaryOperator -import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Reference import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker import de.fraunhofer.aisec.cpg.helpers.Util import de.fraunhofer.aisec.cpg.helpers.Util.Connect @@ -759,7 +759,7 @@ internal class EOGTest : BaseTest() { result.allChildren().filter { l -> l.location?.region?.startLine == 6 }[0] val a = result.refs[ - { l: DeclaredReferenceExpression -> + { l: Reference -> l.location?.region?.startLine == 8 && l.name.localName == "a" }] assertNotNull(a) 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 4e21ae590d..7c7e31fde1 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 @@ -33,8 +33,8 @@ import de.fraunhofer.aisec.cpg.TestUtils.findByUniqueName import de.fraunhofer.aisec.cpg.TestUtils.findByUniquePredicate import de.fraunhofer.aisec.cpg.frontends.java.JavaLanguage import de.fraunhofer.aisec.cpg.graph.* -import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberExpression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Reference import java.nio.file.Path import kotlin.test.Test import kotlin.test.assertEquals @@ -99,10 +99,10 @@ internal class SuperCallTest : BaseTest() { val getSuperField = findByUniqueName(methods, "getSuperField") refs = getSuperField.allChildren() val superFieldRef = findByUniquePredicate(refs) { "super.field" == it.code } - assertTrue(fieldRef.base is DeclaredReferenceExpression) + assertTrue(fieldRef.base is Reference) assertRefersTo(fieldRef.base, getField.receiver) assertEquals(field, fieldRef.refersTo) - assertTrue(superFieldRef.base is DeclaredReferenceExpression) + assertTrue(superFieldRef.base is Reference) assertRefersTo(superFieldRef.base, getSuperField.receiver) assertEquals(superField, superFieldRef.refersTo) } diff --git a/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/FrontendHelperTest.kt b/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/FrontendHelperTest.kt index 4f360d1c95..d84509c91f 100644 --- a/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/FrontendHelperTest.kt +++ b/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/FrontendHelperTest.kt @@ -31,8 +31,8 @@ import de.fraunhofer.aisec.cpg.frontends.java.JavaLanguage import de.fraunhofer.aisec.cpg.graph.declarations.FieldDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration -import de.fraunhofer.aisec.cpg.graph.statements.CompoundStatement import de.fraunhofer.aisec.cpg.graph.statements.ForStatement +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Block import de.fraunhofer.aisec.cpg.sarif.Region import java.io.File import kotlin.test.* @@ -82,7 +82,7 @@ class FrontendHelperTest { // 2 line comment in the constructor val constructor = classDeclaration.constructors.first() - val constructorAssignment = (constructor.body as CompoundStatement).statements[0] + val constructorAssignment = (constructor.body as Block).statements[0] assertNull(constructor.comment) constructorAssignment.comment = "" @@ -96,7 +96,7 @@ class FrontendHelperTest { val mainMethod = classDeclaration.declarations[1] as MethodDeclaration assertNull(mainMethod.comment) - val forLoop = (mainMethod.body as CompoundStatement).statements[0] as ForStatement + val forLoop = (mainMethod.body as Block).statements[0] as ForStatement forLoop.comment = null val comment6 = "for loop" @@ -112,7 +112,7 @@ class FrontendHelperTest { FrontendUtils.matchCommentToNode(comment7, Region(16, 26, 16, 32), tu) // assertEquals(comment7, forLoop.initializerStatement.comment) - val printStatement = (forLoop.statement as CompoundStatement).statements.first() + val printStatement = (forLoop.statement as Block).statements.first() printStatement.comment = null val comment8 = "Crazy print" val comment9 = "Comment which belongs to nothing" @@ -120,7 +120,7 @@ class FrontendHelperTest { FrontendUtils.matchCommentToNode(comment9, Region(18, 16, 18, 48), tu) assertTrue(printStatement.comment?.contains(comment8) == true) // TODO The second comment doesn't belong to the print but to the loop body - assertTrue((forLoop.statement as? CompoundStatement)?.comment?.contains(comment9) == true) + assertTrue((forLoop.statement as? Block)?.comment?.contains(comment9) == true) assertNull(mainMethod.comment) } @@ -148,8 +148,8 @@ class FrontendHelperTest { val tu = result.translationUnits.first() val classDeclaration = tu.declarations.first() as RecordDeclaration val mainMethod = classDeclaration.declarations[1] as MethodDeclaration - val forLoop = (mainMethod.body as CompoundStatement).statements[0] as ForStatement - val printStatement = (forLoop.statement as CompoundStatement).statements.first() + val forLoop = (mainMethod.body as Block).statements[0] as ForStatement + val printStatement = (forLoop.statement as Block).statements.first() val regionSysout = FrontendUtils.parseColumnPositionsFromFile(tu.code!!, 20, 13, 17, 17) diff --git a/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/java/JavaLanguageFrontendTest.kt b/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/java/JavaLanguageFrontendTest.kt index ccc0f737ad..db9856a8bb 100644 --- a/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/java/JavaLanguageFrontendTest.kt +++ b/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/java/JavaLanguageFrontendTest.kt @@ -131,7 +131,7 @@ internal class JavaLanguageFrontendTest : BaseTest() { assertNotNull(forEachStatement) // should loop over ls - assertEquals(ls, (forEachStatement.iterable as? DeclaredReferenceExpression)?.refersTo) + assertEquals(ls, (forEachStatement.iterable as? Reference)?.refersTo) // should declare String s val s = forEachStatement.variable @@ -153,7 +153,7 @@ internal class JavaLanguageFrontendTest : BaseTest() { // Check the flow from the iterable to the variable s assertEquals(1, sDecl.prevDFG.size) - assertTrue(forEachStatement.iterable as DeclaredReferenceExpression in sDecl.prevDFG) + assertTrue(forEachStatement.iterable as Reference in sDecl.prevDFG) // Check the flow from the variable s to the print assertTrue(sDecl in sce.arguments.first().prevDFG) } @@ -339,7 +339,7 @@ internal class JavaLanguageFrontendTest : BaseTest() { assertEquals(3, switchStatements.size) val switchStatement = switchStatements[0] - assertEquals(11, (switchStatement.statement as? CompoundStatement)?.statements?.size) + assertEquals(11, (switchStatement.statement as? Block)?.statements?.size) val caseStatements = switchStatement.allChildren() assertEquals(4, caseStatements.size) @@ -395,7 +395,7 @@ internal class JavaLanguageFrontendTest : BaseTest() { ) // expression itself should be a reference - val ref = cast.expression as? DeclaredReferenceExpression + val ref = cast.expression as? Reference assertNotNull(ref) assertEquals(e, ref.refersTo) } @@ -418,7 +418,7 @@ internal class JavaLanguageFrontendTest : BaseTest() { val main = record.methods[0] assertNotNull(main) - val statements = (main.body as? CompoundStatement)?.statements + val statements = (main.body as? Block)?.statements assertNotNull(statements) val a = (statements[0] as? DeclarationStatement)?.singleDeclaration as? VariableDeclaration @@ -430,7 +430,7 @@ internal class JavaLanguageFrontendTest : BaseTest() { } // it has an array creation initializer - val ace = a.initializer as? ArrayCreationExpression + val ace = a.initializer as? NewArrayExpression assertNotNull(ace) // which has a initializer list (1 entry) @@ -446,9 +446,9 @@ internal class JavaLanguageFrontendTest : BaseTest() { val b = (statements[1] as? DeclarationStatement)?.singleDeclaration as? VariableDeclaration // initializer is array subscription - val ase = b?.initializer as? ArraySubscriptionExpression + val ase = b?.initializer as? SubscriptExpression assertNotNull(ase) - assertEquals(a, (ase.arrayExpression as? DeclaredReferenceExpression)?.refersTo) + assertEquals(a, (ase.arrayExpression as? Reference)?.refersTo) assertEquals(0, (ase.subscriptExpression as? Literal<*>)?.value) } @@ -470,7 +470,7 @@ internal class JavaLanguageFrontendTest : BaseTest() { val main = record.methods[0] assertNotNull(main) - val statements = (main.body as? CompoundStatement)?.statements + val statements = (main.body as? Block)?.statements assertNotNull(statements) val l = (statements[1] as? DeclarationStatement)?.singleDeclaration as? VariableDeclaration @@ -631,8 +631,7 @@ internal class JavaLanguageFrontendTest : BaseTest() { assertNotNull(op) val lhs = op.lhs() - val receiver = - (lhs?.base as? DeclaredReferenceExpression)?.refersTo as? VariableDeclaration? + val receiver = (lhs?.base as? Reference)?.refersTo as? VariableDeclaration? assertNotNull(receiver) assertLocalName("this", receiver) assertEquals(tu.objectType("my.Animal"), receiver.type) @@ -771,7 +770,7 @@ internal class JavaLanguageFrontendTest : BaseTest() { val assign = doSomething.bodyOrNull() assertNotNull(assign) - val ref = ((assign.rhs())?.base as DeclaredReferenceExpression).refersTo + val ref = ((assign.rhs())?.base as Reference).refersTo assertNotNull(ref) assertSame(ref, thisOuterClass) } diff --git a/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/java/LambdaTest.kt b/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/java/LambdaTest.kt index 19ca5ff223..0f2fa225f2 100644 --- a/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/java/LambdaTest.kt +++ b/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/java/LambdaTest.kt @@ -32,7 +32,6 @@ import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration import de.fraunhofer.aisec.cpg.graph.get import de.fraunhofer.aisec.cpg.graph.invoke -import de.fraunhofer.aisec.cpg.graph.statements.CompoundStatement import de.fraunhofer.aisec.cpg.graph.statements.ReturnStatement import de.fraunhofer.aisec.cpg.graph.statements.expressions.* import de.fraunhofer.aisec.cpg.graph.variables @@ -73,25 +72,25 @@ class JavaLambdaTest { assertNotNull(mapBody) val outerVar = result.variables["outerVar"] assertNotNull(outerVar) - assertEquals(outerVar, (mapBody.lhs as? DeclaredReferenceExpression)?.refersTo) + assertEquals(outerVar, (mapBody.lhs as? Reference)?.refersTo) val testfunctionArg = result.calls { it.name.localName == "testFunction" }[0].arguments.first() - assertTrue(testfunctionArg is DeclaredReferenceExpression) + assertTrue(testfunctionArg is Reference) assertTrue( (testfunctionArg.refersTo as? VariableDeclaration)?.initializer is LambdaExpression ) val testfunctionBody = mapArg.function?.body as? BinaryOperator assertNotNull(testfunctionBody) - assertEquals(outerVar, (testfunctionBody.lhs as? DeclaredReferenceExpression)?.refersTo) + assertEquals(outerVar, (testfunctionBody.lhs as? Reference)?.refersTo) val lambdaVar = result.variables["lambdaVar"] assertNotNull(lambdaVar) - val constructExpression = + val constructExpr = (lambdaVar.initializer as? NewExpression)?.initializer as? ConstructExpression - assertNotNull(constructExpression) - val anonymousRecord = constructExpression.instantiates as? RecordDeclaration + assertNotNull(constructExpr) + val anonymousRecord = constructExpr.instantiates as? RecordDeclaration assertNotNull(anonymousRecord) assertTrue(anonymousRecord.isImplicit) assertEquals(1, anonymousRecord.superClasses.size) @@ -101,13 +100,13 @@ class JavaLambdaTest { val applyMethod = anonymousRecord.methods["apply"] assertNotNull(applyMethod) - val returnStmt = - (applyMethod.body as? CompoundStatement)?.statements?.firstOrNull() as? ReturnStatement - assertNotNull(returnStmt) + val returnStatement = + (applyMethod.body as? Block)?.statements?.firstOrNull() as? ReturnStatement + assertNotNull(returnStatement) assertEquals( outerVar, - (((returnStmt.returnValue as? BinaryOperator)?.lhs as? BinaryOperator)?.lhs - as? DeclaredReferenceExpression) + (((returnStatement.returnValue as? BinaryOperator)?.lhs as? BinaryOperator)?.lhs + as? Reference) ?.refersTo ) } 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 19e796ba2e..b95108e88b 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 @@ -68,12 +68,12 @@ internal class StaticImportsTest : BaseTest() { assertNotNull(staticField) assertTrue(staticField.modifiers.contains("static")) - val memberExpressions = main.allChildren() + val memberExpressionExpressions = main.allChildren() // we have two member expressions, one to the field and one to the method - assertEquals(2, memberExpressions.size) + assertEquals(2, memberExpressionExpressions.size) // we want the one to the field - val usage = memberExpressions[{ it.type.name.localName == "int" }] + val usage = memberExpressionExpressions[{ it.type.name.localName == "int" }] assertNotNull(usage) assertEquals(staticField, usage.refersTo) } diff --git a/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/helpers/CommentMatcherTest.kt b/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/helpers/CommentMatcherTest.kt index 8673c8c951..a94dbb6f50 100644 --- a/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/helpers/CommentMatcherTest.kt +++ b/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/helpers/CommentMatcherTest.kt @@ -31,8 +31,8 @@ import de.fraunhofer.aisec.cpg.frontends.java.JavaLanguage import de.fraunhofer.aisec.cpg.graph.declarations.FieldDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration -import de.fraunhofer.aisec.cpg.graph.statements.CompoundStatement import de.fraunhofer.aisec.cpg.graph.statements.ForStatement +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Block import de.fraunhofer.aisec.cpg.sarif.Region import java.io.File import kotlin.test.* @@ -79,7 +79,7 @@ class CommentMatcherTest { // 2 line comment in the constructor val constructor = classDeclaration.constructors.first() - val constructorAssignment = (constructor.body as CompoundStatement).statements[0] + val constructorAssignment = (constructor.body as Block).statements[0] assertNull(constructor.comment) constructorAssignment.comment = "" @@ -93,7 +93,7 @@ class CommentMatcherTest { val mainMethod = classDeclaration.declarations[1] as MethodDeclaration assertNull(mainMethod.comment) - val forLoop = (mainMethod.body as CompoundStatement).statements[0] as ForStatement + val forLoop = (mainMethod.body as Block).statements[0] as ForStatement forLoop.comment = null val comment6 = "for loop" @@ -109,7 +109,7 @@ class CommentMatcherTest { CommentMatcher().matchCommentToNode(comment7, Region(16, 26, 16, 32), tu) // assertEquals(comment7, forLoop.initializerStatement.comment) - val printStatement = (forLoop.statement as CompoundStatement).statements.first() + val printStatement = (forLoop.statement as Block).statements.first() printStatement.comment = null val comment8 = "Crazy print" val comment9 = "Comment which belongs to nothing" @@ -117,7 +117,7 @@ class CommentMatcherTest { CommentMatcher().matchCommentToNode(comment9, Region(18, 16, 18, 48), tu) assertTrue(printStatement.comment?.contains(comment8) == true) // TODO The second comment doesn't belong to the print but to the loop body - assertTrue((forLoop.statement as? CompoundStatement)?.comment?.contains(comment9) == true) + assertTrue((forLoop.statement as? Block)?.comment?.contains(comment9) == true) assertNull(mainMethod.comment) } diff --git a/cpg-language-java/src/test/resources/components/ExplicitConstructorInvocationStmt.java b/cpg-language-java/src/test/resources/components/ExplicitConstructorInvocationStmt.java index ba38273b78..35584e231a 100644 --- a/cpg-language-java/src/test/resources/components/ExplicitConstructorInvocationStmt.java +++ b/cpg-language-java/src/test/resources/components/ExplicitConstructorInvocationStmt.java @@ -1,8 +1,8 @@ import java.util.Arrays; -class ExplicitConstructorInvocationStmt { +class ConstructorCallExpressionStmt { - public ExplicitConstructorInvocationStmt(){ + public ConstructorCallExpressionStmt(){ super(); } diff --git a/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/DeclarationHandler.kt b/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/DeclarationHandler.kt index 69754747d4..8e5b03b417 100644 --- a/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/DeclarationHandler.kt +++ b/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/DeclarationHandler.kt @@ -31,7 +31,7 @@ import de.fraunhofer.aisec.cpg.graph.declarations.Declaration import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.ProblemDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration -import de.fraunhofer.aisec.cpg.graph.statements.CompoundStatement +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Block import de.fraunhofer.aisec.cpg.graph.types.Type import org.bytedeco.javacpp.Pointer import org.bytedeco.llvm.LLVM.LLVMTypeRef @@ -119,7 +119,7 @@ class DeclarationHandler(lang: LLVMIRLanguageFrontend) : val type = frontend.typeOf(param) // TODO: support variardic - val decl = newParamVariableDeclaration(paramName, type, false, frontend.codeOf(param)) + val decl = newParameterDeclaration(paramName, type, false, frontend.codeOf(param)) frontend.scopeManager.addDeclaration(decl) frontend.bindingsCache[paramSymbolName] = decl @@ -148,18 +148,18 @@ class DeclarationHandler(lang: LLVMIRLanguageFrontend) : // as a compound statement // Take the entry block as our body - if (LLVMGetEntryBasicBlock(func) == bb && stmt is CompoundStatement) { + if (LLVMGetEntryBasicBlock(func) == bb && stmt is Block) { functionDeclaration.body = stmt } else if (LLVMGetEntryBasicBlock(func) == bb) { - functionDeclaration.body = newCompoundStatement() + functionDeclaration.body = newBlock() if (stmt != null) { - (functionDeclaration.body as CompoundStatement).addStatement(stmt) + (functionDeclaration.body as Block).addStatement(stmt) } } else { // add the label statement, containing this basic block as a compound statement to // our body (if we have none, which we should) if (stmt != null) { - (functionDeclaration.body as? CompoundStatement)?.addStatement(stmt) + (functionDeclaration.body as? Block)?.addStatement(stmt) } } diff --git a/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/ExpressionHandler.kt b/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/ExpressionHandler.kt index f38f01ff39..a6a6e72817 100644 --- a/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/ExpressionHandler.kt +++ b/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/ExpressionHandler.kt @@ -61,7 +61,7 @@ class ExpressionHandler(lang: LLVMIRLanguageFrontend) : LLVMConstantFPValueKind -> handleConstantFP(value) LLVMConstantPointerNullValueKind -> handleNullPointer(value) LLVMPoisonValueValueKind -> { - newDeclaredReferenceExpression("poison", frontend.typeOf(value), "poison") + newReference("poison", frontend.typeOf(value), "poison") } LLVMConstantTokenNoneValueKind -> newLiteral(null, unknownType(), frontend.codeOf(value)) @@ -80,7 +80,7 @@ class ExpressionHandler(lang: LLVMIRLanguageFrontend) : LLVMFunctionValueKind -> handleFunction(value) LLVMGlobalAliasValueKind -> { val name = frontend.getNameOf(value).first - newDeclaredReferenceExpression(name, frontend.typeOf(value), frontend.codeOf(value)) + newReference(name, frontend.typeOf(value), frontend.codeOf(value)) } LLVMMetadataAsValueValueKind, LLVMInlineAsmValueKind -> { @@ -113,9 +113,9 @@ class ExpressionHandler(lang: LLVMIRLanguageFrontend) : } newLiteral(operandName, cpgType, operandName) } else if (LLVMIsUndef(value) == 1) { - newDeclaredReferenceExpression("undef", cpgType, "undef") + newReference("undef", cpgType, "undef") } else if (LLVMIsPoison(value) == 1) { - newDeclaredReferenceExpression("poison", cpgType, "poison") + newReference("poison", cpgType, "poison") } else { log.error("Unknown expression {}", kind) newProblemExpression( @@ -128,13 +128,9 @@ class ExpressionHandler(lang: LLVMIRLanguageFrontend) : } } - /** Returns a [DeclaredReferenceExpression] for a function (pointer). */ + /** Returns a [Reference] for a function (pointer). */ private fun handleFunction(valueRef: LLVMValueRef): Expression { - return newDeclaredReferenceExpression( - valueRef.name, - frontend.typeOf(valueRef), - frontend.codeOf(valueRef) - ) + return newReference(valueRef.name, frontend.typeOf(valueRef), frontend.codeOf(valueRef)) } /** @@ -152,7 +148,7 @@ class ExpressionHandler(lang: LLVMIRLanguageFrontend) : val type = frontend.typeOf(valueRef) - val ref = newDeclaredReferenceExpression(name, type, "${type.typeName} $name") + val ref = newReference(name, type, "${type.typeName} $name") // try to resolve the reference. actually the valueRef is already referring to the resolved // variable because we obtain it using LLVMGetOperand, so we just need to look it up in the @@ -402,10 +398,10 @@ class ExpressionHandler(lang: LLVMIRLanguageFrontend) : * [`extractvalue`](https://llvm.org/docs/LangRef.html#extractvalue-instruction) instruction * which works in a similar way. * - * We try to convert it either into an [ArraySubscriptionExpression] or an [MemberExpression], - * depending on whether the accessed variable is a struct or an array. Furthermore, since - * `getelementptr` allows an (infinite) chain of sub-element access within a single instruction, - * we need to unwrap those into individual expressions. + * We try to convert it either into an [SubscriptExpression] or an [MemberExpression], depending + * on whether the accessed variable is a struct or an array. Furthermore, since `getelementptr` + * allows an (infinite) chain of sub-element access within a single instruction, we need to + * unwrap those into individual expressions. */ internal fun handleGetElementPtr(instr: LLVMValueRef): Expression { val isGetElementPtr = @@ -452,7 +448,7 @@ class ExpressionHandler(lang: LLVMIRLanguageFrontend) : (operand.value as Long).toInt() } else { // The index is some variable and thus unknown. - operand as DeclaredReferenceExpression + operand as Reference } } else { indices.get(idx.toLong()) @@ -461,7 +457,7 @@ class ExpressionHandler(lang: LLVMIRLanguageFrontend) : // check, if the current base type is a pointer -> then we need to handle this as an // array access if (baseType is PointerType) { - val arrayExpr = newArraySubscriptionExpression("") + val arrayExpr = newSubscriptExpression("") arrayExpr.arrayExpression = base arrayExpr.name = Name(index.toString()) arrayExpr.subscriptExpression = operand @@ -512,7 +508,7 @@ class ExpressionHandler(lang: LLVMIRLanguageFrontend) : // We won't find a field because it's accessed by a variable index. // We indicate this with this array-like notation for now. field = null - "[${(operand as DeclaredReferenceExpression).name.localName}]" + "[${(operand as Reference).name.localName}]" } // our new base-type is the type of the field diff --git a/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/LLVMIRLanguageFrontend.kt b/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/LLVMIRLanguageFrontend.kt index 65642a8d5d..b420810825 100644 --- a/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/LLVMIRLanguageFrontend.kt +++ b/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/LLVMIRLanguageFrontend.kt @@ -33,8 +33,8 @@ import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.declarations.Declaration import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration -import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Reference import de.fraunhofer.aisec.cpg.graph.types.* import de.fraunhofer.aisec.cpg.helpers.Benchmark import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker @@ -70,9 +70,8 @@ class LLVMIRLanguageFrontend(language: Language, ctx: Tr /** * This contains a cache binding between an LLVMValueRef (representing a variable) and its * [Declaration] in the graph. We need this, because this way we can look up and connect a - * [DeclaredReferenceExpression] to its [Declaration] already in the language frontend. This in - * turn is needed because of the local/global system we cannot rely on the - * [VariableUsageResolver]. + * [Reference] to its [Declaration] already in the language frontend. This in turn is needed + * because of the local/global system we cannot rely on the [VariableUsageResolver]. */ var bindingsCache = mutableMapOf() diff --git a/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/StatementHandler.kt b/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/StatementHandler.kt index 93f6c86170..6383b95613 100644 --- a/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/StatementHandler.kt +++ b/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/StatementHandler.kt @@ -271,7 +271,7 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : val parent = frontend.getOperandValueAtIndex(instr, 0) - val compoundStatement = newCompoundStatement(nodeCode) + val compoundStatement = newBlock(nodeCode) val dummyCall = newCallExpression( @@ -484,10 +484,10 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : /** * Handles the ['alloca'](https://llvm.org/docs/LangRef.html#alloca-instruction) instruction, * which allocates a defined block of memory. The closest what we have in the graph is the - * [ArrayCreationExpression], which creates a fixed sized array, i.e., a block of memory. + * [NewArrayExpression], which creates a fixed sized array, i.e., a block of memory. */ private fun handleAlloca(instr: LLVMValueRef): Statement { - val array = newArrayCreationExpression(frontend.codeOf(instr)) + val array = newNewArrayExpression(frontend.codeOf(instr)) array.type = frontend.typeOf(instr) @@ -643,7 +643,7 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : copy = declarationOrNot(operand, instr) if (copy is DeclarationStatement) { base = - newDeclaredReferenceExpression( + newReference( copy.singleDeclaration?.name?.localName, (copy.singleDeclaration as? VariableDeclaration)?.type ?: unknownType(), frontend.codeOf(instr) @@ -662,7 +662,7 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : } base = base.arguments[index] } else if (baseType is PointerType) { - val arrayExpr = newArraySubscriptionExpression("") + val arrayExpr = newSubscriptExpression("") arrayExpr.arrayExpression = base arrayExpr.name = Name(index.toString()) arrayExpr.subscriptExpression = operand @@ -706,7 +706,7 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : } } - val compoundStatement = newCompoundStatement(frontend.codeOf(instr)) + val compoundStatement = newBlock(frontend.codeOf(instr)) val assignment = newAssignExpression("=", listOf(base), listOf(valueToSet), frontend.codeOf(instr)) compoundStatement.addStatement(copy) @@ -792,19 +792,19 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : /** * Parses the [`cmpxchg`](https://llvm.org/docs/LangRef.html#cmpxchg-instruction) instruction. - * It returns a single [Statement] or a [CompoundStatement] if the value is assigned to another - * variable. Performs the following operation atomically: + * It returns a single [Statement] or a [Block] if the value is assigned to another variable. + * Performs the following operation atomically: * ``` * lhs = {*pointer, *pointer == cmp} // A struct of {T, i1} * if(*pointer == cmp) { *pointer = new } * ``` * - * Returns a [CompoundStatement] with those two instructions or, if `lhs` doesn't exist, only - * the if-then statement. + * Returns a [Block] with those two instructions or, if `lhs` doesn't exist, only the if-then + * statement. */ private fun handleAtomiccmpxchg(instr: LLVMValueRef): Statement { val instrStr = frontend.codeOf(instr) - val compoundStatement = newCompoundStatement(instrStr) + val compoundStatement = newBlock(instrStr) compoundStatement.name = Name("atomiccmpxchg") val ptr = frontend.getOperandValueAtIndex(instr, 0) val cmp = frontend.getOperandValueAtIndex(instr, 1) @@ -859,7 +859,7 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : /** * Parses the `atomicrmw` instruction. It returns either a single [Statement] or a - * [CompoundStatement] if the value is assigned to another variable. + * [BlockStatement] if the value is assigned to another variable. */ private fun handleAtomicrmw(instr: LLVMValueRef): Statement { val lhs = LLVMGetValueName(instr).string @@ -982,7 +982,7 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : return if (lhs != "") { // set lhs = *ptr, then perform the replacement - val compoundStatement = newCompoundStatement(instrStr) + val compoundStatement = newBlock(instrStr) val ptrDerefAssignment = newUnaryOperator("*", false, true, instrStr) ptrDerefAssignment.input = frontend.getOperandValueAtIndex(instr, 0) @@ -1013,7 +1013,7 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : val switchStatement = newSwitchStatement(nodeCode) switchStatement.selector = address - val caseStatements = newCompoundStatement(nodeCode) + val caseStatements = newBlock(nodeCode) var idx = 1 while (idx < numOps) { @@ -1076,7 +1076,7 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : val switchStatement = newSwitchStatement(nodeCode) switchStatement.selector = operand - val caseStatements = newCompoundStatement(nodeCode) + val caseStatements = newBlock(nodeCode) var idx = 2 while (idx < numOps) { @@ -1139,11 +1139,7 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : } val callee = - newDeclaredReferenceExpression( - calledFuncName, - frontend.typeOf(calledFunc), - frontend.codeOf(calledFunc) - ) + newReference(calledFuncName, frontend.typeOf(calledFunc), frontend.codeOf(calledFunc)) val callExpr = newCallExpression(callee, calledFuncName, instrStr, false) @@ -1158,7 +1154,7 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : // contains a goto statement after the call. val tryStatement = newTryStatement(instrStr) frontend.scopeManager.enterScope(tryStatement) - val tryBlock = newCompoundStatement(instrStr) + val tryBlock = newBlock(instrStr) tryBlock.addStatement(declarationOrNot(callExpr, instr)) tryBlock.addStatement(tryContinue) tryStatement.tryBlock = tryBlock @@ -1175,9 +1171,9 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : instr ) - val catchCompoundStatement = newCompoundStatement(instrStr) - catchCompoundStatement.addStatement(gotoCatch) - catchClause.body = catchCompoundStatement + val catchBlockStatement = newBlock(instrStr) + catchBlockStatement.addStatement(gotoCatch) + catchClause.body = catchBlockStatement tryStatement.catchClauses = mutableListOf(catchClause) return tryStatement @@ -1240,16 +1236,16 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : */ private fun handleInsertelement(instr: LLVMValueRef): Statement { val instrStr = frontend.codeOf(instr) - val compoundStatement = newCompoundStatement(instrStr) + val compoundStatement = newBlock(instrStr) // TODO: Probably we should make a proper copy of the array val newArrayDecl = declarationOrNot(frontend.getOperandValueAtIndex(instr, 0), instr) compoundStatement.addStatement(newArrayDecl) val decl = newArrayDecl.declarations[0] as? VariableDeclaration - val arrayExpr = newArraySubscriptionExpression(instrStr) + val arrayExpr = newSubscriptExpression(instrStr) arrayExpr.arrayExpression = - newDeclaredReferenceExpression( + newReference( decl?.name?.toString() ?: Node.EMPTY_NAME, decl?.type ?: unknownType(), instrStr @@ -1273,7 +1269,7 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : * instruction which is modeled as access to an array at a given index. */ private fun handleExtractelement(instr: LLVMValueRef): Statement { - val arrayExpr = newArraySubscriptionExpression(frontend.codeOf(instr)) + val arrayExpr = newSubscriptExpression(frontend.codeOf(instr)) arrayExpr.arrayExpression = frontend.getOperandValueAtIndex(instr, 0) arrayExpr.subscriptExpression = frontend.getOperandValueAtIndex(instr, 1) @@ -1330,7 +1326,7 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : } else if (array1 is Literal<*> && array1.value == null) { initializers += newLiteral(null, elementType, instrStr) } else { - val arrayExpr = newArraySubscriptionExpression(instrStr) + val arrayExpr = newSubscriptExpression(instrStr) arrayExpr.arrayExpression = frontend.getOperandValueAtIndex(instr, 0) arrayExpr.subscriptExpression = newLiteral(idxInt, primitiveType("i32"), instrStr) @@ -1342,7 +1338,7 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : } else if (array2 is Literal<*> && array2.value == null) { initializers += newLiteral(null, elementType, instrStr) } else { - val arrayExpr = newArraySubscriptionExpression(instrStr) + val arrayExpr = newSubscriptExpression(instrStr) arrayExpr.arrayExpression = frontend.getOperandValueAtIndex(instr, 1) arrayExpr.subscriptExpression = newLiteral(idxInt - array1Length, primitiveType("i32"), instrStr) @@ -1393,7 +1389,7 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : if (labelMap.keys.size == 1) { // We only have a single pair, so we insert a declaration in that one BB. val (key, value) = labelMap.entries.elementAt(0) - val basicBlock = key.subStatement as? CompoundStatement + val basicBlock = key.subStatement as? Block val decl = declarationOrNot(value, instr) flatAST.addAll(SubgraphWalker.flattenAST(decl)) val mutableStatements = basicBlock?.statements?.toMutableList() @@ -1418,7 +1414,7 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : throw TranslationException("Wrong number of functions for phi statement.") } // Create the dummy declaration at the beginning of the function body - val firstBB = (functions[0] as FunctionDeclaration).body as CompoundStatement + val firstBB = (functions[0] as FunctionDeclaration).body as Block val varName = instr.name val type = frontend.typeOf(instr) val code = frontend.codeOf(instr) @@ -1441,17 +1437,12 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : for ((l, r) in labelMap) { // Now, we iterate over all the basic blocks and add an assign statement. val assignment = - newAssignExpression( - "=", - listOf(newDeclaredReferenceExpression(varName, type, code)), - listOf(r), - code - ) - (assignment.lhs.first() as DeclaredReferenceExpression).type = type - (assignment.lhs.first() as DeclaredReferenceExpression).refersTo = declaration + newAssignExpression("=", listOf(newReference(varName, type, code)), listOf(r), code) + (assignment.lhs.first() as Reference).type = type + (assignment.lhs.first() as Reference).refersTo = declaration flatAST.add(assignment) - val basicBlock = l.subStatement as? CompoundStatement + val basicBlock = l.subStatement as? Block val mutableStatements = basicBlock?.statements?.toMutableList() mutableStatements?.add(basicBlock.statements.size - 1, assignment) if (mutableStatements != null) { @@ -1498,11 +1489,11 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : } /** - * Handles a basic block and returns a [CompoundStatement] comprised of the statements of this + * Handles a basic block and returns a [BlockStatement] comprised of the statements of this * block or a [LabelStatement] if the basic block has a label. */ private fun handleBasicBlock(bb: LLVMBasicBlockRef): Statement { - val compound = newCompoundStatement("") + val compound = newBlock("") var instr = LLVMGetFirstInstruction(bb) while (instr != null) { @@ -1682,10 +1673,10 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : } /** - * This functions creates a new [DeclaredReferenceExpression] to an internal LLVM function. This - * would allow us to handle them all in the same way. + * This functions creates a new [Reference] to an internal LLVM function. This would allow us to + * handle them all in the same way. */ - private fun llvmInternalRef(name: String): DeclaredReferenceExpression { - return newDeclaredReferenceExpression(name) + private fun llvmInternalRef(name: String): Reference { + return newReference(name) } } diff --git a/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CompressLLVMPass.kt b/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CompressLLVMPass.kt index e15b3f5bac..b74660fbf5 100644 --- a/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CompressLLVMPass.kt +++ b/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CompressLLVMPass.kt @@ -29,6 +29,7 @@ import de.fraunhofer.aisec.cpg.TranslationContext import de.fraunhofer.aisec.cpg.frontends.llvm.LLVMIRLanguageFrontend import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.statements.* +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Block import de.fraunhofer.aisec.cpg.graph.statements.expressions.ProblemExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.UnaryOperator import de.fraunhofer.aisec.cpg.graph.types.UnknownType @@ -57,7 +58,7 @@ class CompressLLVMPass(ctx: TranslationContext) : ComponentPass(ctx) { // prevents to treat the final goto in the case or default statement as a normal // compound // statement which would lead to inlining the instructions BB but we want to keep the BB - // inside a CompoundStatement. + // inside a Block. for (node in flatAST.sortedBy { n -> when (n) { @@ -96,7 +97,7 @@ class CompressLLVMPass(ctx: TranslationContext) : ComponentPass(ctx) { } else if (node is SwitchStatement) { // Iterate over all statements in a body of the switch/case and replace a goto // statement if it is the only one jumping to the target - val caseBodyStatements = node.statement as CompoundStatement + val caseBodyStatements = node.statement as Block val newStatements = caseBodyStatements.statements.toMutableList() for (i in 0 until newStatements.size) { val subStatement = @@ -108,7 +109,7 @@ class CompressLLVMPass(ctx: TranslationContext) : ComponentPass(ctx) { subStatement?.let { newStatements[i] = it } } } - (node.statement as CompoundStatement).statements = newStatements + (node.statement as Block).statements = newStatements } else if ( node is TryStatement && node.catchClauses.size == 1 && @@ -137,22 +138,21 @@ class CompressLLVMPass(ctx: TranslationContext) : ComponentPass(ctx) { } else if ( node is TryStatement && node.catchClauses.size == 1 && - node.catchClauses[0].body?.statements?.get(0) is CompoundStatement + node.catchClauses[0].body?.statements?.get(0) is Block ) { // A compound statement which is wrapped in the catchClause. We can simply move // it // one layer up and make // the compound statement the body of the catch clause. - val innerCompound = - node.catchClauses[0].body?.statements?.get(0) as? CompoundStatement + val innerCompound = node.catchClauses[0].body?.statements?.get(0) as? Block innerCompound?.statements?.let { node.catchClauses[0].body?.statements = it } fixThrowStatementsForCatch(node.catchClauses[0]) } else if (node is TryStatement && node.catchClauses.isNotEmpty()) { for (catch in node.catchClauses) { fixThrowStatementsForCatch(catch) } - } else if (node is CompoundStatement) { - // Get the last statement in a CompoundStatement and replace a goto statement + } else if (node is Block) { + // Get the last statement in a Block and replace a goto statement // iff it is the only one jumping to the target val goto = node.statements.lastOrNull() if ( @@ -165,7 +165,7 @@ class CompressLLVMPass(ctx: TranslationContext) : ComponentPass(ctx) { ) { val subStatement = goto.targetLabel?.subStatement val newStatements = node.statements.dropLast(1).toMutableList() - newStatements.addAll((subStatement as CompoundStatement).statements) + newStatements.addAll((subStatement as Block).statements) node.statements = newStatements } } @@ -196,7 +196,7 @@ class CompressLLVMPass(ctx: TranslationContext) : ComponentPass(ctx) { catch.parameter = error } val exceptionReference = - catch.newDeclaredReferenceExpression( + catch.newReference( catch.parameter?.name, catch.parameter?.type ?: UnknownType.getUnknownType(catch.language), "" diff --git a/cpg-language-llvm/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/LLVMIRLanguageFrontendTest.kt b/cpg-language-llvm/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/LLVMIRLanguageFrontendTest.kt index 97710bb3a0..1c1f7ce344 100644 --- a/cpg-language-llvm/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/LLVMIRLanguageFrontendTest.kt +++ b/cpg-language-llvm/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/LLVMIRLanguageFrontendTest.kt @@ -73,11 +73,11 @@ class LLVMIRLanguageFrontendTest { assertLocalName("i32", main.type) val xVector = - (main.bodyOrNull(0)?.statements?.get(0) as? DeclarationStatement) + (main.bodyOrNull(0)?.statements?.get(0) as? DeclarationStatement) ?.singleDeclaration as? VariableDeclaration val xInit = xVector?.initializer as? InitializerListExpression assertNotNull(xInit) - assertLocalName("poison", xInit.initializers[0] as? DeclaredReferenceExpression) + assertLocalName("poison", xInit.initializers[0] as? Reference) assertEquals(0L, (xInit.initializers[1] as? Literal<*>)?.value) assertEquals(0L, (xInit.initializers[2] as? Literal<*>)?.value) assertEquals(0L, (xInit.initializers[3] as? Literal<*>)?.value) @@ -184,7 +184,7 @@ class LLVMIRLanguageFrontendTest { assertNotNull(unary) assertEquals("&", unary.operatorCode) - var arrayExpr = unary.input as? ArraySubscriptionExpression + var arrayExpr = unary.input as? SubscriptExpression assertNotNull(arrayExpr) assertLocalName("13", arrayExpr) assertEquals( @@ -192,7 +192,7 @@ class LLVMIRLanguageFrontendTest { (arrayExpr.subscriptExpression as? Literal<*>)?.value ) // should this be integer instead of long? - arrayExpr = arrayExpr.arrayExpression as? ArraySubscriptionExpression + arrayExpr = arrayExpr.arrayExpression as? SubscriptExpression assertNotNull(arrayExpr) assertLocalName("5", arrayExpr) assertEquals( @@ -200,15 +200,15 @@ class LLVMIRLanguageFrontendTest { (arrayExpr.subscriptExpression as? Literal<*>)?.value ) // should this be integer instead of long? - var memberExpr = arrayExpr.arrayExpression as? MemberExpression - assertNotNull(memberExpr) - assertLocalName("field_1", memberExpr) + var memberExpression = arrayExpr.arrayExpression as? MemberExpression + assertNotNull(memberExpression) + assertLocalName("field_1", memberExpression) - memberExpr = memberExpr.base as? MemberExpression - assertNotNull(memberExpr) - assertLocalName("field_2", memberExpr) + memberExpression = memberExpression.base as? MemberExpression + assertNotNull(memberExpression) + assertLocalName("field_2", memberExpression) - arrayExpr = memberExpr.base as? ArraySubscriptionExpression + arrayExpr = memberExpression.base as? SubscriptExpression assertNotNull(arrayExpr) assertLocalName("1", arrayExpr) assertEquals( @@ -216,7 +216,7 @@ class LLVMIRLanguageFrontendTest { (arrayExpr.subscriptExpression as? Literal<*>)?.value ) // should this be integer instead of long? - val ref = arrayExpr.arrayExpression as? DeclaredReferenceExpression + val ref = arrayExpr.arrayExpression as? Reference assertNotNull(ref) assertLocalName("s", ref) assertSame(s, ref.refersTo) @@ -240,17 +240,17 @@ class LLVMIRLanguageFrontendTest { val onzeroLabel = main.bodyOrNull(0) assertNotNull(onzeroLabel) assertLocalName("onzero", onzeroLabel) - assertTrue(onzeroLabel.subStatement is CompoundStatement) + assertTrue(onzeroLabel.subStatement is Block) val ononeLabel = main.bodyOrNull(1) assertNotNull(ononeLabel) assertLocalName("onone", ononeLabel) - assertTrue(ononeLabel.subStatement is CompoundStatement) + assertTrue(ononeLabel.subStatement is Block) val defaultLabel = main.bodyOrNull(2) assertNotNull(defaultLabel) assertLocalName("otherwise", defaultLabel) - assertTrue(defaultLabel.subStatement is CompoundStatement) + assertTrue(defaultLabel.subStatement is Block) // Check that the type of %a is i32 val xorStatement = main.bodyOrNull(3) @@ -265,9 +265,9 @@ class LLVMIRLanguageFrontendTest { assertNotNull(switchStatement) // Check that we have switch(a) - assertSame(a, (switchStatement.selector as DeclaredReferenceExpression).refersTo) + assertSame(a, (switchStatement.selector as Reference).refersTo) - val cases = switchStatement.statement as CompoundStatement + val cases = switchStatement.statement as Block // Check that the first case is case 0 -> goto onzero and that the BB is inlined val case1 = cases.statements[0] as CaseStatement assertEquals(0L, (case1.caseExpression as Literal<*>).value as Long) @@ -307,10 +307,10 @@ class LLVMIRLanguageFrontendTest { val comparison = variableDecl.initializer as BinaryOperator assertEquals("==", comparison.operatorCode) val rhs = (comparison.rhs as Literal<*>) - val lhs = (comparison.lhs as DeclaredReferenceExpression).refersTo as VariableDeclaration + val lhs = (comparison.lhs as Reference).refersTo as VariableDeclaration assertEquals(10L, (rhs.value as Long)) assertEquals(tu.primitiveType("i32"), rhs.type) - assertLocalName("x", comparison.lhs as DeclaredReferenceExpression) + assertLocalName("x", comparison.lhs as Reference) assertLocalName("x", lhs) assertEquals(tu.primitiveType("i32"), lhs.type) @@ -318,15 +318,14 @@ class LLVMIRLanguageFrontendTest { val ifStatement = main.bodyOrNull(0) assertNotNull(ifStatement) assertEquals("IfUnequal", (ifStatement.elseStatement!! as GotoStatement).labelName) - val ifBranch = (ifStatement.thenStatement as CompoundStatement) + val ifBranch = (ifStatement.thenStatement as Block) // Check that the condition is set correctly val ifCondition = ifStatement.condition - assertSame(variableDecl, (ifCondition as DeclaredReferenceExpression).refersTo) + assertSame(variableDecl, (ifCondition as Reference).refersTo) val elseBranch = - (ifStatement.elseStatement!! as GotoStatement).targetLabel?.subStatement - as CompoundStatement + (ifStatement.elseStatement!! as GotoStatement).targetLabel?.subStatement as Block assertEquals(2, elseBranch.statements.size) assertEquals(" %y = mul i32 %x, 32768", elseBranch.statements[0].code) assertEquals(" ret i32 %y", elseBranch.statements[1].code) @@ -349,14 +348,14 @@ class LLVMIRLanguageFrontendTest { assertEquals(tu.objectType("ui32"), ifBranchCompLhs.castType) assertEquals(tu.objectType("ui32"), ifBranchCompLhs.type) - val declRefExpr = ifBranchCompLhs.expression as DeclaredReferenceExpression + val declRefExpr = ifBranchCompLhs.expression as Reference assertEquals(-3, ((ifBranchCompRhs.expression as Literal<*>).value as Long)) assertLocalName("x", declRefExpr) // TODO: declRefExpr.refersTo is null. Is that expected/intended? val ifBranchSecondStatement = ifBranch.statements[1] as? IfStatement assertNotNull(ifBranchSecondStatement) - val ifRet = ifBranchSecondStatement.thenStatement as? CompoundStatement + val ifRet = ifBranchSecondStatement.thenStatement as? Block assertNotNull(ifRet) assertEquals(1, ifRet.statements.size) assertEquals(" ret i32 1", ifRet.statements[0].code) @@ -377,7 +376,7 @@ class LLVMIRLanguageFrontendTest { val foo = tu.byNameOrNull("foo") assertNotNull(foo) - val atomicrmwStatement = foo.bodyOrNull() + val atomicrmwStatement = foo.bodyOrNull() assertNotNull(atomicrmwStatement) // Check that the value is assigned to @@ -417,7 +416,7 @@ class LLVMIRLanguageFrontendTest { val foo = tu.byNameOrNull("foo") assertNotNull(foo) - val cmpxchgStatement = foo.bodyOrNull(1) + val cmpxchgStatement = foo.bodyOrNull(1) assertNotNull(cmpxchgStatement) assertEquals(2, cmpxchgStatement.statements.size) @@ -453,8 +452,8 @@ class LLVMIRLanguageFrontendTest { assertEquals("=", thenExpr.operatorCode) assertEquals("*", (thenExpr.lhs.first() as UnaryOperator).operatorCode) assertLocalName("ptr", (thenExpr.lhs.first() as UnaryOperator).input) - assertLocalName("old", thenExpr.rhs.first() as DeclaredReferenceExpression) - assertLocalName("old", (thenExpr.rhs.first() as DeclaredReferenceExpression).refersTo) + assertLocalName("old", thenExpr.rhs.first() as Reference) + assertLocalName("old", (thenExpr.rhs.first() as Reference).refersTo) } @Test @@ -556,7 +555,7 @@ class LLVMIRLanguageFrontendTest { (loadXStatement.singleDeclaration as VariableDeclaration).initializer as UnaryOperator assertEquals("*", initXOp.operatorCode) - var ref = initXOp.input as? DeclaredReferenceExpression + var ref = initXOp.input as? Reference assertNotNull(ref) assertLocalName("x", ref) assertSame(globalX, ref.refersTo) @@ -568,7 +567,7 @@ class LLVMIRLanguageFrontendTest { (loadAStatement.singleDeclaration as VariableDeclaration).initializer as UnaryOperator assertEquals("*", initAOp.operatorCode) - ref = initAOp.input as? DeclaredReferenceExpression + ref = initAOp.input as? Reference assertNotNull(ref) assertLocalName("a", ref) assertSame(globalA, ref.refersTo) @@ -595,7 +594,7 @@ class LLVMIRLanguageFrontendTest { val ptr = main.bodyOrNull()?.singleDeclaration as? VariableDeclaration assertNotNull(ptr) - val alloca = ptr.initializer as? ArrayCreationExpression + val alloca = ptr.initializer as? NewArrayExpression assertNotNull(alloca) assertEquals("i32*", alloca.type.typeName) @@ -609,7 +608,7 @@ class LLVMIRLanguageFrontendTest { assertNotNull(dereferencePtr) assertEquals("*", dereferencePtr.operatorCode) assertEquals("i32", dereferencePtr.type.typeName) - assertSame(ptr, (dereferencePtr.input as? DeclaredReferenceExpression)?.refersTo) + assertSame(ptr, (dereferencePtr.input as? Reference)?.refersTo) assertEquals(1, store.rhs.size) val value = store.rhs.first() as? Literal<*> @@ -651,7 +650,7 @@ class LLVMIRLanguageFrontendTest { assertEquals(100L, (args[0] as Literal<*>).value as Long) assertNull((args[1] as Literal<*>).value) - val compoundStatement = foo.bodyOrNull() + val compoundStatement = foo.bodyOrNull() assertNotNull(compoundStatement) // First copy a to b val copyStatement = @@ -688,7 +687,7 @@ class LLVMIRLanguageFrontendTest { val main = tu.byNameOrNull("main") assertNotNull(main) - val mainBody = main.body as CompoundStatement + val mainBody = main.body as Block val tryStatement = mainBody.statements[0] as? TryStatement assertNotNull(tryStatement) @@ -719,10 +718,10 @@ class LLVMIRLanguageFrontendTest { assertLocalName("_ZTIi | ...", tryStatement.catchClauses[0]) val ifStatement = tryStatement.catchClauses[0].body?.statements?.get(4) as? IfStatement assertNotNull(ifStatement) - assertTrue(ifStatement.thenStatement is CompoundStatement) - assertEquals(4, (ifStatement.thenStatement as CompoundStatement).statements.size) - assertTrue(ifStatement.elseStatement is CompoundStatement) - assertEquals(1, (ifStatement.elseStatement as CompoundStatement).statements.size) + assertTrue(ifStatement.thenStatement is Block) + assertEquals(4, (ifStatement.thenStatement as Block).statements.size) + assertTrue(ifStatement.elseStatement is Block) + assertEquals(1, (ifStatement.elseStatement as Block).statements.size) } @Test @@ -754,7 +753,7 @@ class LLVMIRLanguageFrontendTest { val main = tu.byNameOrNull("main") assertNotNull(main) - val mainBody = main.body as CompoundStatement + val mainBody = main.body as Block val yDecl = (mainBody.statements[0] as DeclarationStatement).singleDeclaration as VariableDeclaration @@ -763,7 +762,7 @@ class LLVMIRLanguageFrontendTest { val ifStatement = mainBody.statements[3] as? IfStatement assertNotNull(ifStatement) - val thenStmt = ifStatement.thenStatement as? CompoundStatement + val thenStmt = ifStatement.thenStatement as? Block assertNotNull(thenStmt) assertEquals(3, thenStmt.statements.size) assertNotNull(thenStmt.statements[1] as? AssignExpression) @@ -773,10 +772,10 @@ class LLVMIRLanguageFrontendTest { val thenY = thenStmt.statements[1] as AssignExpression assertEquals(1, thenY.lhs.size) assertEquals(1, thenY.rhs.size) - assertSame(aDecl, (thenY.rhs.first() as DeclaredReferenceExpression).refersTo) - assertSame(yDecl, (thenY.lhs.first() as DeclaredReferenceExpression).refersTo) + assertSame(aDecl, (thenY.rhs.first() as Reference).refersTo) + assertSame(yDecl, (thenY.lhs.first() as Reference).refersTo) - val elseStmt = ifStatement.elseStatement as? CompoundStatement + val elseStmt = ifStatement.elseStatement as? Block assertNotNull(elseStmt) assertEquals(3, elseStmt.statements.size) val bDecl = @@ -786,18 +785,15 @@ class LLVMIRLanguageFrontendTest { val elseY = elseStmt.statements[1] as AssignExpression assertEquals(1, elseY.lhs.size) assertEquals(1, elseY.lhs.size) - assertSame(bDecl, (elseY.rhs.first() as DeclaredReferenceExpression).refersTo) - assertSame(yDecl, (elseY.lhs.first() as DeclaredReferenceExpression).refersTo) + assertSame(bDecl, (elseY.rhs.first() as Reference).refersTo) + assertSame(yDecl, (elseY.lhs.first() as Reference).refersTo) val continueBlock = - (thenStmt.statements[2] as? GotoStatement)?.targetLabel?.subStatement - as? CompoundStatement + (thenStmt.statements[2] as? GotoStatement)?.targetLabel?.subStatement as? Block assertNotNull(continueBlock) assertEquals( yDecl, - ((continueBlock.statements[1] as ReturnStatement).returnValue - as DeclaredReferenceExpression) - .refersTo + ((continueBlock.statements[1] as ReturnStatement).returnValue as Reference).refersTo ) } @@ -816,7 +812,7 @@ class LLVMIRLanguageFrontendTest { assertNotNull(main) // Test that x is initialized correctly - val mainBody = main.body as CompoundStatement + val mainBody = main.body as Block val origX = ((mainBody.statements[0] as? DeclarationStatement)?.singleDeclaration as? VariableDeclaration) @@ -842,35 +838,31 @@ class LLVMIRLanguageFrontendTest { val zInit = ((mainBody.statements[2] as? DeclarationStatement)?.singleDeclaration as? VariableDeclaration) - ?.initializer as? ArraySubscriptionExpression + ?.initializer as? SubscriptExpression assertNotNull(zInit) assertEquals(0L, (zInit.subscriptExpression as? Literal<*>)?.value) - assertEquals("x", (zInit.arrayExpression as? DeclaredReferenceExpression)?.name?.localName) - assertSame(origX, (zInit.arrayExpression as? DeclaredReferenceExpression)?.refersTo) + assertEquals("x", (zInit.arrayExpression as? Reference)?.name?.localName) + assertSame(origX, (zInit.arrayExpression as? Reference)?.refersTo) // Test the assignment of y to yMod val yModInit = - ((mainBody.statements[3] as CompoundStatement).statements[0] as? DeclarationStatement) + ((mainBody.statements[3] as Block).statements[0] as? DeclarationStatement) ?.singleDeclaration as? VariableDeclaration assertNotNull(yModInit) - assertEquals("y", (yModInit.initializer as? DeclaredReferenceExpression)?.name?.localName) - assertSame(origY, (yModInit.initializer as? DeclaredReferenceExpression)?.refersTo) + assertEquals("y", (yModInit.initializer as? Reference)?.name?.localName) + assertSame(origY, (yModInit.initializer as? Reference)?.refersTo) // Now, test the modification of yMod[3] = 8 - val yMod = - ((mainBody.statements[3] as CompoundStatement).statements[1] as? AssignExpression) + val yMod = ((mainBody.statements[3] as Block).statements[1] as? AssignExpression) assertNotNull(yMod) assertEquals(1, yMod.lhs.size) assertEquals(1, yMod.rhs.size) assertEquals( 3L, - ((yMod.lhs.first() as? ArraySubscriptionExpression)?.subscriptExpression as? Literal<*>) - ?.value + ((yMod.lhs.first() as? SubscriptExpression)?.subscriptExpression as? Literal<*>)?.value ) assertSame( yModInit, - ((yMod.lhs.first() as? ArraySubscriptionExpression)?.arrayExpression - as? DeclaredReferenceExpression) - ?.refersTo + ((yMod.lhs.first() as? SubscriptExpression)?.arrayExpression as? Reference)?.refersTo ) assertEquals(8L, (yMod.rhs.first() as? Literal<*>)?.value) @@ -882,37 +874,34 @@ class LLVMIRLanguageFrontendTest { assertNotNull(shuffledInit) assertSame( origX, - ((shuffledInit.initializers[0] as? ArraySubscriptionExpression)?.arrayExpression - as? DeclaredReferenceExpression) + ((shuffledInit.initializers[0] as? SubscriptExpression)?.arrayExpression as? Reference) ?.refersTo ) assertSame( yModInit, - ((shuffledInit.initializers[1] as? ArraySubscriptionExpression)?.arrayExpression - as? DeclaredReferenceExpression) + ((shuffledInit.initializers[1] as? SubscriptExpression)?.arrayExpression as? Reference) ?.refersTo ) assertSame( yModInit, - ((shuffledInit.initializers[2] as? ArraySubscriptionExpression)?.arrayExpression - as? DeclaredReferenceExpression) + ((shuffledInit.initializers[2] as? SubscriptExpression)?.arrayExpression as? Reference) ?.refersTo ) assertSame( 1, - ((shuffledInit.initializers[0] as? ArraySubscriptionExpression)?.subscriptExpression + ((shuffledInit.initializers[0] as? SubscriptExpression)?.subscriptExpression as? Literal<*>) ?.value ) assertSame( 2, - ((shuffledInit.initializers[1] as? ArraySubscriptionExpression)?.subscriptExpression + ((shuffledInit.initializers[1] as? SubscriptExpression)?.subscriptExpression as? Literal<*>) ?.value ) assertSame( 3, - ((shuffledInit.initializers[2] as? ArraySubscriptionExpression)?.subscriptExpression + ((shuffledInit.initializers[2] as? SubscriptExpression)?.subscriptExpression as? Literal<*>) ?.value ) @@ -933,7 +922,7 @@ class LLVMIRLanguageFrontendTest { assertNotNull(main) // Test that x is initialized correctly - val mainBody = main.body as CompoundStatement + val mainBody = main.body as Block val fenceCall = mainBody.statements[0] as? CallExpression assertNotNull(fenceCall) @@ -964,7 +953,7 @@ class LLVMIRLanguageFrontendTest { assertNotNull(funcF) val tryStatement = - (funcF.bodyOrNull(0)?.subStatement as? CompoundStatement) + (funcF.bodyOrNull(0)?.subStatement as? Block) ?.statements ?.firstOrNull { s -> s is TryStatement } as? TryStatement assertNotNull(tryStatement) @@ -997,13 +986,13 @@ class LLVMIRLanguageFrontendTest { assertFullName("llvm.matchesCatchpad", matchesExceptionCall) assertEquals( catchSwitchExpr.singleDeclaration, - (matchesExceptionCall.arguments[0] as DeclaredReferenceExpression).refersTo + (matchesExceptionCall.arguments[0] as Reference).refersTo ) assertEquals(null, (matchesExceptionCall.arguments[1] as Literal<*>).value) assertEquals(64L, (matchesExceptionCall.arguments[2] as Literal<*>).value as Long) assertEquals(null, (matchesExceptionCall.arguments[3] as Literal<*>).value) - val catchBlock = ifExceptionMatches.thenStatement as? CompoundStatement + val catchBlock = ifExceptionMatches.thenStatement as? Block assertNotNull(catchBlock) assertFullName( "llvm.catchpad", @@ -1025,7 +1014,7 @@ class LLVMIRLanguageFrontendTest { val innerCatchClause = (innerTry.catchClauses[0].body?.statements?.get(1) as? IfStatement)?.thenStatement - as? CompoundStatement + as? Block assertNotNull(innerCatchClause) assertFullName( "llvm.catchpad", @@ -1042,7 +1031,7 @@ class LLVMIRLanguageFrontendTest { assertNotNull(innerCatchThrows.input) assertSame( innerTry.catchClauses[0].parameter, - (innerCatchThrows.input as? DeclaredReferenceExpression)?.refersTo + (innerCatchThrows.input as? Reference)?.refersTo ) } diff --git a/cpg-language-python/src/main/python/CPGPython/__init__.py b/cpg-language-python/src/main/python/CPGPython/__init__.py index 49d84d2bf8..6d40445fc3 100644 --- a/cpg-language-python/src/main/python/CPGPython/__init__.py +++ b/cpg-language-python/src/main/python/CPGPython/__init__.py @@ -65,7 +65,7 @@ def __init__(self, fname, frontend, code): from ._statements import handle_function_or_method from ._statements import handle_statement from ._statements import handle_statement_impl - from ._statements import make_compound_statement + from ._statements import make_block_statement def execute(self): if isinstance(self.rootNode, ast.Module): diff --git a/cpg-language-python/src/main/python/CPGPython/_expressions.py b/cpg-language-python/src/main/python/CPGPython/_expressions.py index b7ee1506a8..f4a31e8ab4 100644 --- a/cpg-language-python/src/main/python/CPGPython/_expressions.py +++ b/cpg-language-python/src/main/python/CPGPython/_expressions.py @@ -315,9 +315,9 @@ def handle_expression_impl(self, expr): if self.is_declaration(value): self.log_with_loc( ("Found a new declaration. " - "Wrapping it in a DeclaredReferenceExpression."), + "Wrapping it in a Reference."), loglevel="DEBUG") - value = ExpressionBuilderKt.newDeclaredReferenceExpression( + value = ExpressionBuilderKt.newReference( self.frontend, value.getName(), value.getType(), value.getCode()) mem = ExpressionBuilderKt.newMemberExpression( @@ -329,7 +329,7 @@ def handle_expression_impl(self, expr): elif isinstance(expr, ast.Subscript): value = self.handle_expression(expr.value) slc = self.handle_expression(expr.slice) - exp = ExpressionBuilderKt.newArraySubscriptionExpression( + exp = ExpressionBuilderKt.newSubscriptExpression( self.frontend, self.get_src_code(expr)) exp.setArrayExpression(value) exp.setSubscriptExpression(slc) @@ -339,7 +339,7 @@ def handle_expression_impl(self, expr): r = ExpressionBuilderKt.newExpression(self.frontend, "") return r elif isinstance(expr, ast.Name): - r = ExpressionBuilderKt.newDeclaredReferenceExpression( + r = ExpressionBuilderKt.newReference( self.frontend, expr.id, TypeBuilderKt.unknownType(self.frontend), self.get_src_code(expr)) diff --git a/cpg-language-python/src/main/python/CPGPython/_misc.py b/cpg-language-python/src/main/python/CPGPython/_misc.py index 23d6407ea2..af9224ec5a 100644 --- a/cpg-language-python/src/main/python/CPGPython/_misc.py +++ b/cpg-language-python/src/main/python/CPGPython/_misc.py @@ -106,7 +106,7 @@ def is_variable_declaration(self, target): def is_declared_reference(self, target): - n = CPG_JAVA + ".graph.statements.expressions.DeclaredReferenceExpression" + n = CPG_JAVA + ".graph.statements.expressions.Reference" return target is not None and target.java_name == n diff --git a/cpg-language-python/src/main/python/CPGPython/_statements.py b/cpg-language-python/src/main/python/CPGPython/_statements.py index 40f7b98a48..dd13767b34 100644 --- a/cpg-language-python/src/main/python/CPGPython/_statements.py +++ b/cpg-language-python/src/main/python/CPGPython/_statements.py @@ -29,7 +29,7 @@ from de.fraunhofer.aisec.cpg.graph import TypeBuilderKt from de.fraunhofer.aisec.cpg.graph import StatementBuilderKt from de.fraunhofer.aisec.cpg.graph import ExpressionBuilderKt -from de.fraunhofer.aisec.cpg.graph.statements import CompoundStatement +from de.fraunhofer.aisec.cpg.graph.statements.expressions import Block from de.fraunhofer.aisec.cpg.graph.types import UnknownType from java.util import ArrayList import ast @@ -117,7 +117,7 @@ def handle_statement_impl(self, stmt): whl_stmt.setConditionDeclaration(expr) else: whl_stmt.setCondition(expr) - body = self.make_compound_statement(stmt.body) + body = self.make_block_statement(stmt.body) whl_stmt.setStatement(body) if stmt.orelse is not None and len(stmt.orelse) != 0: self.log_with_loc( @@ -131,11 +131,11 @@ def handle_statement_impl(self, stmt): # Condition if_stmt.setCondition(self.handle_expression(stmt.test)) # Then - body = self.make_compound_statement(stmt.body) + body = self.make_block_statement(stmt.body) if_stmt.setThenStatement(body) # Else if stmt.orelse is not None and len(stmt.orelse) != 0: - orelse = self.make_compound_statement(stmt.orelse) + orelse = self.make_block_statement(stmt.orelse) if_stmt.setElseStatement(orelse) return if_stmt @@ -233,8 +233,8 @@ def handle_statement_impl(self, stmt): elif isinstance(stmt, ast.Try): s = StatementBuilderKt.newTryStatement(self.frontend, self.get_src_code(stmt)) - try_block = self.make_compound_statement(stmt.body) - finally_block = self.make_compound_statement(stmt.finalbody) + try_block = self.make_block_statement(stmt.body) + finally_block = self.make_block_statement(stmt.finalbody) if stmt.orelse is not None and len(stmt.orelse) != 0: self.log_with_loc(NOT_IMPLEMENTED_MSG, loglevel="ERROR") if len(stmt.handlers) != 0: @@ -337,7 +337,7 @@ def handle_function_or_method(self, node, record=None): loglevel="ERROR") if len(node.body) > 0: - f.setBody(self.make_compound_statement(node.body)) + f.setBody(self.make_block_statement(node.body)) annotations = [] for decorator in node.decorator_list: @@ -414,7 +414,7 @@ def handle_argument(self, arg: ast.arg): else: tpe = TypeBuilderKt.unknownType(self.frontend) # TODO variadic - pvd = DeclarationBuilderKt.newParamVariableDeclaration( + pvd = DeclarationBuilderKt.newParameterDeclaration( self.frontend, arg.arg, tpe, False, self.get_src_code(arg)) self.add_loc_info(arg, pvd) self.scopemanager.addDeclaration(pvd) @@ -455,7 +455,7 @@ def handle_for(self, stmt): for_stmt.setVariable(target) - body = self.make_compound_statement(stmt.body) + body = self.make_block_statement(stmt.body) for_stmt.setStatement(body) if stmt.orelse is not None and len(stmt.orelse) != 0: @@ -464,12 +464,12 @@ def handle_for(self, stmt): return for_stmt -def make_compound_statement(self, stmts) -> CompoundStatement: +def make_block_statement(self, stmts) -> Block: if stmts is None or len(stmts) == 0: self.log_with_loc( "Expected at least one statement. Returning a dummy.", loglevel="WARN") - return StatementBuilderKt.newCompoundStatement(self.frontend, "") + return StatementBuilderKt.newBlock(self.frontend, "") if False and len(stmts) == 1: """ TODO decide how to handle this... """ @@ -478,17 +478,17 @@ def make_compound_statement(self, stmts) -> CompoundStatement: s = self.wrap_declaration_to_stmt(s) return s else: - compound_statement = StatementBuilderKt.newCompoundStatement( + block_statement = ExpressionBuilderKt.newBlock( self.frontend, "") for s in stmts: s = self.handle_statement(s) if self.is_declaration(s): s = self.wrap_declaration_to_stmt(s) - compound_statement.addStatement(s) + block_statement.addStatement(s) if len(stmts) > 0: - self.add_mul_loc_infos(stmts[0], stmts[-1], compound_statement) + self.add_mul_loc_infos(stmts[0], stmts[-1], block_statement) - return compound_statement + return block_statement def handle_assign(self, stmt): @@ -541,7 +541,7 @@ def handle_assign_impl(self, stmt): if not self.is_declared_reference( lhs) and not self.is_member_expression(lhs): self.log_with_loc( - "Expected a DeclaredReferenceExpression or MemberExpression " + "Expected a Reference or MemberExpression " "but got \"%s\". Skipping." % lhs.java_name, loglevel="ERROR") r = ExpressionBuilderKt.newArrayList(self.frontend, @@ -576,7 +576,7 @@ class Foo: else: name = "DUMMY" self.log_with_loc( - "Expected a DeclaredReferenceExpression but got a " + "Expected a Reference but got a " "MemberExpression. Using a dummy.", loglevel="ERROR") diff --git a/cpg-language-python/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonFrontendTest.kt b/cpg-language-python/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonFrontendTest.kt index ec275da66d..2ca7c4fb66 100644 --- a/cpg-language-python/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonFrontendTest.kt +++ b/cpg-language-python/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonFrontendTest.kt @@ -129,7 +129,7 @@ class PythonFrontendTest : BaseTest() { assertNotNull(bar) assertEquals(2, bar.parameters.size) - var callExpression = (foo.body as? CompoundStatement)?.statements?.get(0) as? CallExpression + var callExpression = (foo.body as? Block)?.statements?.get(0) as? CallExpression assertNotNull(callExpression) assertLocalName("bar", callExpression) @@ -146,7 +146,7 @@ class PythonFrontendTest : BaseTest() { assertLocalName("bar", bar) - val compStmt = bar.body as? CompoundStatement + val compStmt = bar.body as? Block assertNotNull(compStmt) assertNotNull(compStmt.statements) @@ -161,7 +161,7 @@ class PythonFrontendTest : BaseTest() { assertEquals("bar(s) here: ", literal.value) assertEquals(tu.primitiveType("str"), literal.type) - val ref = callExpression.arguments[1] as? DeclaredReferenceExpression + val ref = callExpression.arguments[1] as? Reference assertNotNull(ref) assertLocalName("s", ref) @@ -211,7 +211,7 @@ class PythonFrontendTest : BaseTest() { val main = p.functions["foo"] assertNotNull(main) - val body = main.body as? CompoundStatement + val body = main.body as? Block assertNotNull(body) val sel = @@ -261,7 +261,7 @@ class PythonFrontendTest : BaseTest() { assertLocalName("someFunc", clsfunc) assertLocalName("foo", foo) - val body = foo.body as? CompoundStatement + val body = foo.body as? Block assertNotNull(body) assertNotNull(body.statements) assertEquals(2, body.statements.size) @@ -278,7 +278,7 @@ class PythonFrontendTest : BaseTest() { assertEquals(ctor, cls.constructors.first()) assertFullName("simple_class.SomeClass", c1.type) - assertEquals(c1, (s2.base as? DeclaredReferenceExpression)?.refersTo) + assertEquals(c1, (s2.base as? Reference)?.refersTo) assertEquals(1, s2.invokes.size) assertEquals(clsfunc, s2.invokes.first()) @@ -302,7 +302,7 @@ class PythonFrontendTest : BaseTest() { val main = p.functions["foo"] assertNotNull(main) - val body = (main.body as? CompoundStatement)?.statements?.get(0) as? DeclarationStatement + val body = (main.body as? Block)?.statements?.get(0) as? DeclarationStatement assertNotNull(body) val foo = body.singleDeclaration as? VariableDeclaration @@ -316,9 +316,9 @@ class PythonFrontendTest : BaseTest() { val ifCond = initializer.condition as? Literal<*> assertNotNull(ifCond) - val thenExpr = initializer.thenExpr as? Literal<*> + val thenExpr = initializer.thenExpression as? Literal<*> assertNotNull(thenExpr) - val elseExpr = initializer.elseExpr as? Literal<*> + val elseExpr = initializer.elseExpression as? Literal<*> assertNotNull(elseExpr) assertEquals(tu.primitiveType("bool"), ifCond.type) @@ -377,12 +377,11 @@ class PythonFrontendTest : BaseTest() { assertNotNull(methBar) assertLocalName("bar", methBar) - val barZ = (methBar.body as? CompoundStatement)?.statements?.get(0) as? MemberExpression + val barZ = (methBar.body as? Block)?.statements?.get(0) as? MemberExpression assertNotNull(barZ) assertEquals(fieldZ, barZ.refersTo) - val barBaz = - (methBar.body as? CompoundStatement)?.statements?.get(1) as? DeclarationStatement + val barBaz = (methBar.body as? Block)?.statements?.get(1) as? DeclarationStatement assertNotNull(barBaz) val barBazInner = barBaz.declarations[0] as? FieldDeclaration assertNotNull(barBazInner) @@ -437,15 +436,14 @@ class PythonFrontendTest : BaseTest() { // self.somevar = i val someVarDeclaration = - ((bar.body as? CompoundStatement)?.statements?.get(0) as? DeclarationStatement) + ((bar.body as? Block)?.statements?.get(0) as? DeclarationStatement) ?.declarations ?.first() as? FieldDeclaration assertNotNull(someVarDeclaration) assertLocalName("somevar", someVarDeclaration) - assertEquals(i, (someVarDeclaration.initializer as? DeclaredReferenceExpression)?.refersTo) + assertEquals(i, (someVarDeclaration.initializer as? Reference)?.refersTo) - val fooMemCall = - (foo.body as? CompoundStatement)?.statements?.get(0) as? MemberCallExpression + val fooMemCall = (foo.body as? Block)?.statements?.get(0) as? MemberCallExpression assertNotNull(fooMemCall) val mem = fooMemCall.callee as? MemberExpression @@ -491,10 +489,10 @@ class PythonFrontendTest : BaseTest() { assertNotNull(bar) assertLocalName("bar", bar) - assertEquals(2, (bar.body as? CompoundStatement)?.statements?.size) - val line1 = (bar.body as? CompoundStatement)?.statements?.get(0) as? DeclarationStatement + assertEquals(2, (bar.body as? Block)?.statements?.size) + val line1 = (bar.body as? Block)?.statements?.get(0) as? DeclarationStatement assertNotNull(line1) - val line2 = (bar.body as? CompoundStatement)?.statements?.get(1) as? MemberCallExpression + val line2 = (bar.body as? Block)?.statements?.get(1) as? MemberCallExpression assertNotNull(line2) assertEquals(1, line1.declarations.size) @@ -505,7 +503,7 @@ class PythonFrontendTest : BaseTest() { val initializer = fooDecl.initializer as? ConstructExpression assertEquals(fooCtor, initializer?.constructor) - assertEquals(fooDecl, (line2.base as? DeclaredReferenceExpression)?.refersTo) + assertEquals(fooDecl, (line2.base as? Reference)?.refersTo) assertEquals(foobar, line2.invokes[0]) } @@ -547,7 +545,7 @@ class PythonFrontendTest : BaseTest() { assertNotNull(countParam) assertLocalName("c", countParam) - val countStmt = (methCount.body as? CompoundStatement)?.statements?.get(0) as? IfStatement + val countStmt = (methCount.body as? Block)?.statements?.get(0) as? IfStatement assertNotNull(countStmt) val ifCond = countStmt.condition as? BinaryOperator @@ -555,18 +553,14 @@ class PythonFrontendTest : BaseTest() { val lhs = ifCond.lhs as? MemberCallExpression assertNotNull(lhs) - assertEquals(countParam, (lhs.base as? DeclaredReferenceExpression)?.refersTo) + assertEquals(countParam, (lhs.base as? Reference)?.refersTo) assertLocalName("inc", lhs) assertEquals(0, lhs.arguments.size) - val ifThen = - (countStmt.thenStatement as? CompoundStatement)?.statements?.get(0) as? CallExpression + val ifThen = (countStmt.thenStatement as? Block)?.statements?.get(0) as? CallExpression assertNotNull(ifThen) assertEquals(methCount, ifThen.invokes.first()) - assertEquals( - countParam, - (ifThen.arguments.first() as? DeclaredReferenceExpression)?.refersTo - ) + assertEquals(countParam, (ifThen.arguments.first() as? Reference)?.refersTo) assertNull(countStmt.elseStatement) // class c1(counter) @@ -590,7 +584,7 @@ class PythonFrontendTest : BaseTest() { assertLocalName("self", selfReceiver) assertEquals(0, meth.parameters.size) // self is receiver and not a parameter - val methBody = meth.body as? CompoundStatement + val methBody = meth.body as? Block assertNotNull(methBody) val assign = methBody.statements[0] as? AssignExpression @@ -601,20 +595,20 @@ class PythonFrontendTest : BaseTest() { assertEquals("=", assign.operatorCode) assertNotNull(assignLhs) assertNotNull(assignRhs) - assertEquals(selfReceiver, (assignLhs.base as? DeclaredReferenceExpression)?.refersTo) + assertEquals(selfReceiver, (assignLhs.base as? Reference)?.refersTo) assertEquals("+", assignRhs.operatorCode) val assignRhsLhs = assignRhs.lhs as? MemberExpression // the second "self.total" in "self.total = self.total + 1" assertNotNull(assignRhsLhs) - assertEquals(selfReceiver, (assignRhsLhs.base as? DeclaredReferenceExpression)?.refersTo) + assertEquals(selfReceiver, (assignRhsLhs.base as? Reference)?.refersTo) val r = methBody.statements[1] as? ReturnStatement assertNotNull(r) assertEquals( selfReceiver, - ((r.returnValue as? MemberExpression)?.base as? DeclaredReferenceExpression)?.refersTo + ((r.returnValue as? MemberExpression)?.base as? Reference)?.refersTo ) // TODO last line "count(c1())" @@ -661,15 +655,12 @@ class PythonFrontendTest : BaseTest() { assertNotNull(assignClsFieldOutsideFunc) assertEquals( classFieldNoInitializer, - (assignClsFieldOutsideFunc.lhs())?.refersTo - ) - assertEquals( - classFieldWithInit, - (assignClsFieldOutsideFunc.rhs())?.refersTo + (assignClsFieldOutsideFunc.lhs())?.refersTo ) + assertEquals(classFieldWithInit, (assignClsFieldOutsideFunc.rhs())?.refersTo) assertEquals("=", assignClsFieldOutsideFunc.operatorCode) - val barBody = methBar.body as? CompoundStatement + val barBody = methBar.body as? Block assertNotNull(barBody) // self.classFieldDeclaredInFunction = 456 @@ -693,27 +684,21 @@ class PythonFrontendTest : BaseTest() { val barStmt3 = barBody.statements[3] as? AssignExpression assertNotNull(barStmt3) assertEquals("=", barStmt3.operatorCode) - assertEquals( - classFieldNoInitializer, - (barStmt3.lhs())?.refersTo - ) + assertEquals(classFieldNoInitializer, (barStmt3.lhs())?.refersTo) assertEquals("shadowed", (barStmt3.rhs>())?.value) // classFieldWithInit = "shadowed" val barStmt4 = barBody.statements[4] as? AssignExpression assertNotNull(barStmt4) assertEquals("=", barStmt4.operatorCode) - assertEquals(classFieldWithInit, (barStmt4.lhs())?.refersTo) + assertEquals(classFieldWithInit, (barStmt4.lhs())?.refersTo) assertEquals("shadowed", (barStmt4.rhs>())?.value) // classFieldDeclaredInFunction = "shadowed" val barStmt5 = barBody.statements[5] as? AssignExpression assertNotNull(barStmt5) assertEquals("=", barStmt5.operatorCode) - assertEquals( - classFieldDeclaredInFunction, - (barStmt5.lhs())?.refersTo - ) + assertEquals(classFieldDeclaredInFunction, (barStmt5.lhs())?.refersTo) assertEquals("shadowed", (barStmt5.rhs>())?.value) /* TODO: @@ -775,13 +760,13 @@ class PythonFrontendTest : BaseTest() { val base = initializer.base as? MemberExpression assertNotNull(base) assertLocalName("baz", base) - val baseBase = base.base as? DeclaredReferenceExpression + val baseBase = base.base as? Reference assertNotNull(baseBase) assertLocalName("bar", baseBase) - val member = initializer.callee as? MemberExpression - assertNotNull(member) - assertLocalName("zzz", member) + val memberExpression = initializer.callee as? MemberExpression + assertNotNull(memberExpression) + assertLocalName("zzz", memberExpression) } @Test @@ -803,22 +788,22 @@ class PythonFrontendTest : BaseTest() { val main = p.functions["main"] assertNotNull(main) - val mainBody = (main as? FunctionDeclaration)?.body as? CompoundStatement + val mainBody = (main as? FunctionDeclaration)?.body as? Block assertNotNull(mainBody) val whlStmt = mainBody.statements[3] as? WhileStatement assertNotNull(whlStmt) - val whlBody = whlStmt.statement as? CompoundStatement + val whlBody = whlStmt.statement as? Block assertNotNull(whlBody) val xDeclaration = whlBody.statements[0] as? DeclarationStatement assertNotNull(xDeclaration) - val ifStmt = whlBody.statements[1] as? IfStatement - assertNotNull(ifStmt) + val ifStatement = whlBody.statements[1] as? IfStatement + assertNotNull(ifStatement) - val brk = ifStmt.elseStatement as? CompoundStatement + val brk = ifStatement.elseStatement as? Block assertNotNull(brk) brk.statements[0] as? BreakStatement } @@ -863,19 +848,19 @@ class PythonFrontendTest : BaseTest() { val forVariable = forStmt.variable as? InitializerListExpression assertNotNull(forVariable) assertEquals(3, forVariable.initializers.size) - val t1Decl = forVariable.initializers[0] as? DeclaredReferenceExpression - val t2Decl = forVariable.initializers[1] as? DeclaredReferenceExpression - val t3Decl = forVariable.initializers[2] as? DeclaredReferenceExpression + val t1Decl = forVariable.initializers[0] as? Reference + val t2Decl = forVariable.initializers[1] as? Reference + val t3Decl = forVariable.initializers[2] as? Reference assertNotNull(t1Decl) assertNotNull(t2Decl) assertNotNull(t3Decl) // TODO no refersTo - val iter = forStmt.iterable as? DeclaredReferenceExpression + val iter = forStmt.iterable as? Reference assertNotNull(iter) assertEquals(testDeclaration, iter.refersTo) - val forBody = forStmt.statement as? CompoundStatement + val forBody = forStmt.statement as? Block assertNotNull(forBody) assertEquals(1, forBody.statements.size) @@ -886,11 +871,11 @@ class PythonFrontendTest : BaseTest() { val printArg = forBodyStmt.arguments[0] as? MemberCallExpression assertNotNull(printArg) - val formatArgT1 = printArg.arguments[0] as? DeclaredReferenceExpression + val formatArgT1 = printArg.arguments[0] as? Reference assertNotNull(formatArgT1) - val formatArgT2 = printArg.arguments[1] as? DeclaredReferenceExpression + val formatArgT2 = printArg.arguments[1] as? Reference assertNotNull(formatArgT2) - val formatArgT3 = printArg.arguments[2] as? DeclaredReferenceExpression + val formatArgT3 = printArg.arguments[2] as? Reference assertNotNull(formatArgT3) // TODO check refersTo } @@ -911,18 +896,18 @@ class PythonFrontendTest : BaseTest() { val p = tu.namespaces["issue473"] assertNotNull(p) - val ifStmt = p.statements[0] as? IfStatement - assertNotNull(ifStmt) - val ifCond = ifStmt.condition as? BinaryOperator + val ifStatement = p.statements[0] as? IfStatement + assertNotNull(ifStatement) + val ifCond = ifStatement.condition as? BinaryOperator assertNotNull(ifCond) - val ifThen = ifStmt.thenStatement as? CompoundStatement + val ifThen = ifStatement.thenStatement as? Block assertNotNull(ifThen) - val ifElse = ifStmt.elseStatement as? CompoundStatement + val ifElse = ifStatement.elseStatement as? Block assertNotNull(ifElse) // sys.version_info.minor > 9 assertEquals(">", ifCond.operatorCode) - assertLocalName("minor", ifCond.lhs as? DeclaredReferenceExpression) + assertLocalName("minor", ifCond.lhs as? Reference) // phr = {"user_id": user_id} | content val phrDeclaration = @@ -978,7 +963,7 @@ class PythonFrontendTest : BaseTest() { assertEquals(1, literals.size) assertEquals("# comment start", literals.first().comment) - val params = commentedNodes.filterIsInstance() + val params = commentedNodes.filterIsInstance() assertEquals(2, params.size) assertEquals("# a parameter", params.first { it.name.localName == "i" }.comment) assertEquals("# another parameter", params.first { it.name.localName == "j" }.comment) @@ -987,7 +972,7 @@ class PythonFrontendTest : BaseTest() { assertEquals(1, variable.size) assertEquals("# A comment", variable.first().comment) - val block = commentedNodes.filterIsInstance() + val block = commentedNodes.filterIsInstance() assertEquals(1, block.size) assertEquals("# foo", block.first().comment) @@ -1036,7 +1021,7 @@ class PythonFrontendTest : BaseTest() { } assertNotNull(tu) - val namespace = tu.functions["forloop"]?.body as? CompoundStatement + val namespace = tu.functions["forloop"]?.body as? Block assertNotNull(namespace) val varDefinedBeforeLoop = namespace.variables["varDefinedBeforeLoop"] @@ -1072,7 +1057,7 @@ class PythonFrontendTest : BaseTest() { ) // dataflow from first loop to foo call - val loopVar = firstLoop.variable as? DeclaredReferenceExpression + val loopVar = firstLoop.variable as? Reference assertNotNull(loopVar) assert(fooCall.arguments.first().prevDFG.contains(loopVar)) diff --git a/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/DeclarationHandler.kt b/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/DeclarationHandler.kt index f928df4c2d..776b5bd213 100644 --- a/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/DeclarationHandler.kt +++ b/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/DeclarationHandler.kt @@ -111,7 +111,7 @@ class DeclarationHandler(lang: TypeScriptLanguageFrontend) : val name = this.frontend.getIdentifierName(node) val type = node.typeChildNode?.let { this.frontend.typeOf(it) } ?: unknownType() - return newParamVariableDeclaration(name, type, false, this.frontend.codeOf(node)) + return newParameterDeclaration(name, type, false, this.frontend.codeOf(node)) } fun handleSourceFile(node: TypeScriptNode): TranslationUnitDeclaration { diff --git a/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/ExpressionHandler.kt b/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/ExpressionHandler.kt index 9b50a431f7..30b5d64666 100644 --- a/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/ExpressionHandler.kt +++ b/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/ExpressionHandler.kt @@ -169,7 +169,7 @@ class ExpressionHandler(lang: TypeScriptLanguageFrontend) : private fun handleIdentifier(node: TypeScriptNode): Expression { val name = this.frontend.codeOf(node)?.trim() ?: "" - return newDeclaredReferenceExpression(name, unknownType(), this.frontend.codeOf(node)) + return newReference(name, unknownType(), this.frontend.codeOf(node)) } private fun handlePropertyAccessExpression(node: TypeScriptNode): Expression { @@ -190,17 +190,20 @@ class ExpressionHandler(lang: TypeScriptLanguageFrontend) : call = if (propertyAccess != null) { - val memberExpression = + val memberExpressionExpression = this.handle(propertyAccess) as? MemberExpression ?: return ProblemExpression("node is not a member expression") - newMemberCallExpression(memberExpression, code = this.frontend.codeOf(node)) + newMemberCallExpression( + memberExpressionExpression, + code = this.frontend.codeOf(node) + ) } else { // TODO: fqn - how? val fqn = this.frontend.getIdentifierName(node) // regular function call - val ref = newDeclaredReferenceExpression(fqn) + val ref = newReference(fqn) newCallExpression(ref, fqn, this.frontend.codeOf(node), false) } diff --git a/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/StatementHandler.kt b/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/StatementHandler.kt index 77ae321271..51c1edb9f6 100644 --- a/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/StatementHandler.kt +++ b/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/StatementHandler.kt @@ -26,13 +26,13 @@ package de.fraunhofer.aisec.cpg.frontends.typescript import de.fraunhofer.aisec.cpg.frontends.Handler -import de.fraunhofer.aisec.cpg.graph.newCompoundStatement +import de.fraunhofer.aisec.cpg.graph.newBlock import de.fraunhofer.aisec.cpg.graph.newDeclarationStatement import de.fraunhofer.aisec.cpg.graph.newReturnStatement -import de.fraunhofer.aisec.cpg.graph.statements.CompoundStatement import de.fraunhofer.aisec.cpg.graph.statements.DeclarationStatement import de.fraunhofer.aisec.cpg.graph.statements.ReturnStatement import de.fraunhofer.aisec.cpg.graph.statements.Statement +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Block import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression import de.fraunhofer.aisec.cpg.graph.statements.expressions.ProblemExpression @@ -81,8 +81,8 @@ class StatementHandler(lang: TypeScriptLanguageFrontend) : return returnStmt } - private fun handleBlock(node: TypeScriptNode): CompoundStatement { - val block = newCompoundStatement(this.frontend.codeOf(node)) + private fun handleBlock(node: TypeScriptNode): Block { + val block = newBlock(this.frontend.codeOf(node)) node.children?.forEach { this.handle(it)?.let { it1 -> block.addStatement(it1) } } diff --git a/cpg-language-typescript/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/TypescriptLanguageFrontendTest.kt b/cpg-language-typescript/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/TypescriptLanguageFrontendTest.kt index 5e465efe3f..e06ebbdb3b 100644 --- a/cpg-language-typescript/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/TypescriptLanguageFrontendTest.kt +++ b/cpg-language-typescript/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/TypescriptLanguageFrontendTest.kt @@ -133,11 +133,11 @@ class TypeScriptLanguageFrontendTest { tu.getDeclarationsByName("doJsx", FunctionDeclaration::class.java).iterator().next() assertNotNull(doJsx) - val returnStmt = doJsx.getBodyStatementAs(0, ReturnStatement::class.java) - assertNotNull(returnStmt) + val returnStatement = doJsx.getBodyStatementAs(0, ReturnStatement::class.java) + assertNotNull(returnStatement) // check the return statement for the TSX statements - val jsx = returnStmt.returnValue as? ExpressionList + val jsx = returnStatement.returnValue as? ExpressionList assertNotNull(jsx) val tag = jsx.expressions.firstOrNull() @@ -204,7 +204,7 @@ class TypeScriptLanguageFrontendTest { val fetch = chainedCall.base as? CallExpression assertNotNull(fetch) - val refArg = fetch.arguments.first() as? DeclaredReferenceExpression + val refArg = fetch.arguments.first() as? Reference assertNotNull(refArg) assertLocalName("apiUrl", refArg) @@ -218,7 +218,7 @@ class TypeScriptLanguageFrontendTest { var keyValue = objectArg.initializers.first() as? KeyValueExpression assertNotNull(keyValue) - assertLocalName("method", keyValue.key as? DeclaredReferenceExpression) + assertLocalName("method", keyValue.key as? Reference) assertEquals("POST", (keyValue.value as? Literal<*>)?.value) keyValue = objectArg.initializers.last() as? KeyValueExpression @@ -301,11 +301,11 @@ class TypeScriptLanguageFrontendTest { val render = usersComponent.methods["render"] assertNotNull(render) - val returnStmt = render.getBodyStatementAs(1, ReturnStatement::class.java) - assertNotNull(returnStmt) + val returnStatement = render.getBodyStatementAs(1, ReturnStatement::class.java) + assertNotNull(returnStatement) // check the return statement for the TSX statements - val jsx = returnStmt.returnValue as? ExpressionList + val jsx = returnStatement.returnValue as? ExpressionList assertNotNull(jsx) val tag = jsx.expressions.firstOrNull() diff --git a/docs/docs/CPG/specs/dfg.md b/docs/docs/CPG/specs/dfg.md index ea827917ce..e1bc885f9c 100755 --- a/docs/docs/CPG/specs/dfg.md +++ b/docs/docs/CPG/specs/dfg.md @@ -65,7 +65,7 @@ Interesting fields: ### Case 1: Normal assignment (`operatorCode: =`) The `rhs` flows to `lhs`. In some languages, it is possible to have an assignment in a subexpression (e.g. `a + (b=1)`). -For this reason, if the assignment's ast parent is not a `CompoundStatement` (i.e., a block of statements), we also add a DFG edge to the whole operator. +For this reason, if the assignment's ast parent is not a `Block` (i.e., a block of statements), we also add a DFG edge to the whole operator. If the `lhs` consists of multiple variables (or a tuple), we try to split up the `rhs` by the index. If we can't do this, the whole `rhs` flows to all variables in `lhs`. Scheme: @@ -87,7 +87,7 @@ flowchart LR ```mermaid flowchart LR A[assignment.rhs] -- DFG --> assignment.lhs; - subgraph S[If the ast parent is not a CompoundStatement] + subgraph S[If the ast parent is not a Block direction LR assignment.rhs -- DFG --> assignment; end @@ -140,7 +140,7 @@ Scheme: lhs -- DFG --> node; ``` -## ArrayCreationExpression +## NewArrayExpression Interesting fields: @@ -151,7 +151,7 @@ The `initializer` flows to the array creation expression. Scheme: ```mermaid flowchart LR - node([ArrayCreationExpression]) -.- initializer(initializer) + node([NewArrayExpression]) -.- initializer(initializer) initializer -- DFG --> node ``` @@ -169,7 +169,7 @@ Scheme: initializer -- DFG --> node ``` -## ArraySubscriptionExpression +## SubscriptExpression Interesting fields: @@ -181,7 +181,7 @@ The `arrayExpression` flows to the subscription expression. This means, we do no Scheme: ```mermaid flowchart LR - arrayExpression -- DFG --> node([ArraySubscriptionExpression]); + arrayExpression -- DFG --> node([SubscriptExpression]); arrayExpression -.- node; ``` @@ -191,21 +191,21 @@ Scheme: Interesting fields: * `condition: Expression`: The condition which is evaluated -* `thenExpr: Expression`: The expression which is executed if the condition holds -* `elseExpr: Expression`: The expression which is executed if the condition does not hold +* `thenExpression: Expression`: The expression which is executed if the condition holds +* `elseExpression: Expression`: The expression which is executed if the condition does not hold The `thenExpr` and the `elseExpr` flow to the `ConditionalExpression`. This means that implicit data flows are not considered. Scheme: ```mermaid flowchart LR - thenExpr -- DFG --> node([ConditionalExpression]); - thenExpr -.- node; - elseExpr -.- node; - elseExpr -- DFG --> node; + thenExpression -- DFG --> node([ConditionalExpression]); + thenExpression -.- node; + elseExpression -.- node; + elseExpression -- DFG --> node; ``` -## DeclaredReferenceExpression +## Reference Interesting fields: @@ -219,19 +219,19 @@ The `DFGPass` generates very simple edges based on the access to the variable as * The value flows from the declaration to the expression for read access. Scheme: ```mermaid flowchart LR - refersTo -- DFG --> node([DeclaredReferenceExpression]); + refersTo -- DFG --> node([Reference]); refersTo -.- node; ``` * For write access, data flow from the expression to the declaration. Scheme: ```mermaid flowchart LR - node([DeclaredReferenceExpression]) -- DFG --> refersTo; + node([Reference]) -- DFG --> refersTo; node -.- refersTo; ``` * For readwrite access, both flows are present. Scheme: ```mermaid flowchart LR - refersTo -- DFG 1 --> node([DeclaredReferenceExpression]); + refersTo -- DFG 1 --> node([Reference]); refersTo -.- node; node -- DFG 2 --> refersTo; ``` @@ -240,20 +240,20 @@ This mostly serves one purpose: The current function pointer resolution requires The `ControlFlowSensitiveDFGPass` completely changes this behavior and accounts for the data flows which differ depending on the program's control flow (e.g., different assignments to a variable in an if and else branch, ...). The pass performs the following actions: -* First, it clears all the edges between a `VariableDeclaration` and its `DeclaredReferenceExpression`. Actually, it clears all incoming and outgoing DFG edges of all VariableDeclarations in a function. This includes the initializer but this edge is restored right away. Scheme: +* First, it clears all the edges between a `VariableDeclaration` and its `Reference`. Actually, it clears all incoming and outgoing DFG edges of all VariableDeclarations in a function. This includes the initializer but this edge is restored right away. Scheme: ```mermaid flowchart LR node([VariableDeclaration]) -.- initializer; initializer -- DFG --> node; ``` -* For each read access to a DeclaredReferenceExpression, it collects all potential previous assignments to the variable and adds these to the incoming DFG edges. You can imagine that this is done by traversing the EOG backwards until finding the first assignment to the variable for each possible path. Scheme: +* For each read access to a Reference, it collects all potential previous assignments to the variable and adds these to the incoming DFG edges. You can imagine that this is done by traversing the EOG backwards until finding the first assignment to the variable for each possible path. Scheme: ```mermaid flowchart LR - node([DeclaredReferenceExpression]) -.- refersTo; + node([Reference]) -.- refersTo; A == last write to ==> refersTo; A[/Node/] -- DFG --> node; ``` -* If we increment or decrement a variable with "++" or "--", the data of this statement flows from the previous writes of the variable to the input of the statement (= the DeclaredReferenceExpression). We write back to this reference and consider the lhs as a "write" to the variable! *Attention: This potentially adds loops and can look like a branch. Needs to be handled with care in subsequent passes/analyses!* Scheme: +* If we increment or decrement a variable with "++" or "--", the data of this statement flows from the previous writes of the variable to the input of the statement (= the Reference). We write back to this reference and consider the lhs as a "write" to the variable! *Attention: This potentially adds loops and can look like a branch. Needs to be handled with care in subsequent passes/analyses!* Scheme: ```mermaid flowchart LR node([UnaryOperator]) -.- input; @@ -298,9 +298,9 @@ Interesting fields: * `base: Expression`: The base object whose field is accessed * `refersTo: Declaration?`: The field it refers to. If the class is not implemented in the code under analysis, it is `null`. -The MemberExpression represents an access to an object's field and extends a DeclaredReferenceExpression with a `base`. +The MemberExpression represents an access to an object's field and extends a Reference with a `base`. -If an implementation of the respective class is available, we handle it like a normal DeclaredReferenceExpression. +If an implementation of the respective class is available, we handle it like a normal Reference. If the `refersTo` field is `null` (i.e., the implementation is not available), base flows to the expression. ## ExpressionList @@ -554,7 +554,7 @@ Interesting fields: The value of the initializer flows to the whole field. -In addition, all writes to a reference to the field (via a `DeclaredReferenceExpression`) flow to the field, for all reads, data flow to the reference. +In addition, all writes to a reference to the field (via a `Reference`) flow to the field, for all reads, data flow to the reference. Scheme: ```mermaid @@ -571,7 +571,7 @@ Interesting fields: * `initializer: Expression?`: The value which is used to initialize a variable (if applicable). -The value of the initializer flows to the variable declaration. The value of the variable declarations flows to all `DeclaredReferenceExpressions` which read the value before the value of the variable is written to through another reference to the variable. +The value of the initializer flows to the variable declaration. The value of the variable declarations flows to all `References` which read the value before the value of the variable is written to through another reference to the variable. Scheme: ```mermaid @@ -589,7 +589,7 @@ Interesting fields: * `initializer: Expression?`: The value which is used to initialize a variable (if applicable). * `element: List`: The value which is used to initialize a variable (if applicable). -The value of the initializer flows to the elements of the tuple declaration. The value of the variable declarations flows to all `DeclaredReferenceExpressions` which read the value before the value of the variable is written to through another reference to the variable. +The value of the initializer flows to the elements of the tuple declaration. The value of the variable declarations flows to all `References` which read the value before the value of the variable is written to through another reference to the variable. Scheme: ```mermaid diff --git a/docs/docs/CPG/specs/eog.md b/docs/docs/CPG/specs/eog.md index ca8c1bd700..8ef5512d20 100644 --- a/docs/docs/CPG/specs/eog.md +++ b/docs/docs/CPG/specs/eog.md @@ -15,7 +15,7 @@ The EOG is similar to a CFG which connects basic blocks of statements, but there * For methods without explicit return statement, the EOG will have an edge to a virtual return node with line number -1 which does not exist in the original code. A CFG will always end with the last reachable statement(s) and not insert any virtual return statements. -* The EOG considers an opening blocking (`CompoundStatement`, indicated by a `{`) as a separate node. +* The EOG considers an opening blocking (`Block`, indicated by a `{`) as a separate node. A CFG will rather use the first actual executable statement within the block. * For IF statements, the EOG treats the `if` keyword and the condition as separate nodes. A CFG treats this as one `if` statement. @@ -52,7 +52,7 @@ A function declaration is the start of an intraprocedural EOG and contains its e Interesting fields: -* `parameters: List`: The parameters of the function. +* `parameters: List`: The parameters of the function. * `defaultValue: Expression`: Optional default values of the parameters that have to be evaluated before executing the function's body. * `body: Statement`: One or multiple statements executed when this function is called. @@ -69,11 +69,11 @@ flowchart LR ``` ## StatementHolder -StatementHolder is an interface for any node that is not a function and contains code that should be connected with an EOG. The following classes implement this interface: `NamespaceDeclaration`, `TranslationUnitDeclaration`, `RecordDeclaration` and `CompoundStatement`. The Node implementing the interface is the start of one or multiple EOGs. Note that code inside such a holder can be static or non static (bound to an instance of a record). Therefore, two separate EOGs may be built. +StatementHolder is an interface for any node that is not a function and contains code that should be connected with an EOG. The following classes implement this interface: `NamespaceDeclaration`, `TranslationUnitDeclaration`, `RecordDeclaration` and `Block`. The Node implementing the interface is the start of one or multiple EOGs. Note that code inside such a holder can be static or non static (bound to an instance of a record). Therefore, two separate EOGs may be built. Interesting fields: -* `statements: List`: The code inside a holder. The individual elements are distinguished by a property marking them as `staticBlock` if they are a `CompoundStatement`. +* `statements: List`: The code inside a holder. The individual elements are distinguished by a property marking them as `staticBlock` if they are a `Block`. Scheme: ```mermaid @@ -168,7 +168,7 @@ flowchart LR child --EOG--> parent ``` -## ArraySubscriptionExpression +## SubscriptExpression Array access in the form of `arrayExpression[subscriptExpression]`. Interesting fields: @@ -182,13 +182,13 @@ flowchart LR classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; prev:::outer --EOG--> child child --EOG--> child2["subscriptExpression"] - parent(["ArraySubscriptionExpression"]) --EOG--> next:::outer + parent(["SubscriptExpression"]) --EOG--> next:::outer parent -.-> child["arrayExpression"] parent -.-> child2 child2 --EOG--> parent ``` -## ArrayCreationExpression +## NewArrayExpression Interesting fields: * `dimensions: List`: Multiple expressions that define the array's dimensions. @@ -201,7 +201,7 @@ flowchart LR prev:::outer --EOG--> child1["dimension(i-1)"] child1 --EOG--> child2["dimension(i)"] child2 --EOG--> initializer - parent(["ArrayCreationExpression"]) --EOG--> next:::outer + parent(["NewArrayExpression"]) --EOG--> next:::outer parent -.-> child1 parent -.-> child2 parent -.-> initializer @@ -320,7 +320,7 @@ flowchart LR ``` -## CompoundStatement +## Block Represents an explicit block of statements. @@ -334,7 +334,7 @@ flowchart LR classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; prev:::outer --EOG--> child1["statement(i-1)"] child1 --EOG-->child2["statement(i)"] - parent(["CompoundStatement"]) --EOG--> next:::outer + parent(["Block"]) --EOG--> next:::outer parent -."statements(n)".-> child1 parent -."statements(n)".-> child2 child2 --EOG--> parent @@ -407,9 +407,9 @@ After the execution of the statement the control flow only proceeds with the nex Interesting fields: * `resources:List`: Initialization of values needed in the block or special objects needing cleanup. -* `tryBlock:CompoundStatement`: The code that should be tried, exceptions inside lead to an eog edge to the catch clauses. -* `finallyBlock:CompoundStatement`: All EOG paths inside the `tryBlock` or the `catch` blocks will finally reach this block and evaluate it. -* `catchBlocks:List`: Children of `CatchClause` (omitted here), evaluated when the exception matches the clauses condition. +* `tryBlock:Block`: The code that should be tried, exceptions inside lead to an eog edge to the catch clauses. +* `finallyBlock:Block`: All EOG paths inside the `tryBlock` or the `catch` blocks will finally reach this block and evaluate it. +* `catchBlocks:List`: Children of `CatchClause` (omitted here), evaluated when the exception matches the clauses condition. Scheme: ```mermaid @@ -584,7 +584,7 @@ The placement of the root node between expression and executed block is such tha Interesting fields: * `expression: Expression`: Its evaluation returns an object that acts as a lock for synchronization. -* `blockStatement: CompoundStatement`: Code executed while the object evaluated from `expression` is locked. +* `block: Block`: Code executed while the object evaluated from `expression` is locked. Scheme: ```mermaid @@ -592,7 +592,7 @@ flowchart LR classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; prev:::outer --EOG--> child1["expression"] child1 --EOG--> parent - parent --EOG--> child2["blockStatement"] + parent --EOG--> child2["block"] child2 --EOG--> next:::outer parent -.-> child1 parent -.-> child2 @@ -604,8 +604,8 @@ A conditional evaluation of two expression, realizing the branching pattern of a Interesting fields: * `condition:Expression`: Executed first to decide the branch of evaluation. -* `thenExpr:Expression`: Evaluated if `condition` evaluates to `true.` -* `elseExpr:Expression`: Evaluated if `condition` evaluates to `false.` +* `thenExpression:Expression`: Evaluated if `condition` evaluates to `true.` +* `elseExpression:Expression`: Evaluated if `condition` evaluates to `false.` Scheme: ```mermaid @@ -613,8 +613,8 @@ flowchart LR classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; prev:::outer --EOG--> child1["condition"] child1 --EOG--> parent(["ConditionalExpression"]) - parent --EOG:true--> child2["thenExpr"] - parent --EOG:false--> child3["elseExpr"] + parent --EOG:true--> child2["thenExpression"] + parent --EOG:false--> child3["elseExpression"] child2 --EOG--> next:::outer child3 --EOG--> next:::outer parent -.-> child1 diff --git a/docs/docs/CPG/specs/graph.md b/docs/docs/CPG/specs/graph.md index d4dcc81ecf..7e594aad52 100644 --- a/docs/docs/CPG/specs/graph.md +++ b/docs/docs/CPG/specs/graph.md @@ -77,7 +77,7 @@ Node--"TYPEDEFS*"-->NodeTYPEDEFS[TypedefDeclarati [SwitchStatement](#eswitchstatement) [GotoStatement](#egotostatement) [WhileStatement](#ewhilestatement) -[CompoundStatement](#ecompoundstatement) +[BlockStatement](#ecompoundstatement) [ContinueStatement](#econtinuestatement) [DefaultStatement](#edefaultstatement) [SynchronizedStatement](#esynchronizedstatement) @@ -251,15 +251,15 @@ ReturnStatement--"RETURN_VALUES*"-->ReturnStatementRETURN_VALUES[[CallExpression](#ecallexpression) ### Children -[ExplicitConstructorInvocation](#eexplicitconstructorinvocation) +[ConstructorCallExpression](#eexplicitconstructorinvocation) [ConstructExpression](#econstructexpression) [MemberCallExpression](#emembercallexpression) @@ -530,12 +530,12 @@ flowchart LR classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; CallExpression--"TEMPLATE_PARAMETERS*"-->CallExpressionTEMPLATE_PARAMETERS[Node]:::outer ``` -## ExplicitConstructorInvocation +## ConstructorCallExpression **Labels**:[Node](#enode) [Statement](#estatement) [Expression](#eexpression) [CallExpression](#ecallexpression) -[ExplicitConstructorInvocation](#eexplicitconstructorinvocation) +[ConstructorCallExpression](#eexplicitconstructorinvocation) ### Relationships [CALLEE](#CallExpressionCALLEE) @@ -834,16 +834,16 @@ flowchart LR classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; CastExpression--"EXPRESSION¹"-->CastExpressionEXPRESSION[Expression]:::outer ``` -## ArrayCreationExpression +## NewArrayExpression **Labels**:[Node](#enode) [Statement](#estatement) [Expression](#eexpression) -[ArrayCreationExpression](#earraycreationexpression) +[NewArrayExpression](#earraycreationexpression) ### Relationships -[INITIALIZER](#ArrayCreationExpressionINITIALIZER) +[INITIALIZER](#NewArrayExpressionINITIALIZER) -[DIMENSIONS](#ArrayCreationExpressionDIMENSIONS) +[DIMENSIONS](#NewArrayExpressionDIMENSIONS) [POSSIBLE_SUB_TYPES](#ExpressionPOSSIBLE_SUB_TYPES) @@ -863,28 +863,28 @@ CastExpression--"EXPRESSION¹"-->CastExpressionEXPRESSION[[TYPEDEFS](#NodeTYPEDEFS) -#### INITIALIZER +#### INITIALIZER ```mermaid flowchart LR classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; -ArrayCreationExpression--"INITIALIZER¹"-->ArrayCreationExpressionINITIALIZER[Expression]:::outer +NewArrayExpression--"INITIALIZER¹"-->NewArrayExpressionINITIALIZER[Expression]:::outer ``` -#### DIMENSIONS +#### DIMENSIONS ```mermaid flowchart LR classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; -ArrayCreationExpression--"DIMENSIONS*"-->ArrayCreationExpressionDIMENSIONS[Expression]:::outer +NewArrayExpression--"DIMENSIONS*"-->NewArrayExpressionDIMENSIONS[Expression]:::outer ``` -## ArraySubscriptionExpression +## SubscriptionExpression **Labels**:[Node](#enode) [Statement](#estatement) [Expression](#eexpression) -[ArraySubscriptionExpression](#earraysubscriptionexpression) +[SubscriptionExpression](#earraysubscriptionexpression) ### Relationships -[ARRAY_EXPRESSION](#ArraySubscriptionExpressionARRAY_EXPRESSION) +[ARRAY_EXPRESSION](#SubscriptionExpressionARRAY_EXPRESSION) -[SUBSCRIPT_EXPRESSION](#ArraySubscriptionExpressionSUBSCRIPT_EXPRESSION) +[SUBSCRIPT_EXPRESSION](#SubscriptionExpressionSUBSCRIPT_EXPRESSION) [POSSIBLE_SUB_TYPES](#ExpressionPOSSIBLE_SUB_TYPES) @@ -904,17 +904,17 @@ ArrayCreationExpression--"DIMENSIONS*"-->ArrayCreationExpressionDIMENSIONS[[TYPEDEFS](#NodeTYPEDEFS) -#### ARRAY_EXPRESSION +#### ARRAY_EXPRESSION ```mermaid flowchart LR classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; -ArraySubscriptionExpression--"ARRAY_EXPRESSION¹"-->ArraySubscriptionExpressionARRAY_EXPRESSION[Expression]:::outer +SubscriptionExpression--"ARRAY_EXPRESSION¹"-->SubscriptionExpressionARRAY_EXPRESSION[Expression]:::outer ``` -#### SUBSCRIPT_EXPRESSION +#### SUBSCRIPT_EXPRESSION ```mermaid flowchart LR classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; -ArraySubscriptionExpression--"SUBSCRIPT_EXPRESSION¹"-->ArraySubscriptionExpressionSUBSCRIPT_EXPRESSION[Expression]:::outer +SubscriptionExpression--"SUBSCRIPT_EXPRESSION¹"-->SubscriptionExpressionSUBSCRIPT_EXPRESSION[Expression]:::outer ``` ## TypeExpression **Labels**:[Node](#enode) @@ -1031,17 +1031,17 @@ flowchart LR classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; ConditionalExpression--"CONDITION¹"-->ConditionalExpressionCONDITION[Expression]:::outer ``` -## DeclaredReferenceExpression +## Reference **Labels**:[Node](#enode) [Statement](#estatement) [Expression](#eexpression) -[DeclaredReferenceExpression](#edeclaredreferenceexpression) +[Reference](#edeclaredreferenceexpression) ### Children [MemberExpression](#ememberexpression) ### Relationships -[REFERS_TO](#DeclaredReferenceExpressionREFERS_TO) +[REFERS_TO](#ReferenceREFERS_TO) [POSSIBLE_SUB_TYPES](#ExpressionPOSSIBLE_SUB_TYPES) @@ -1061,23 +1061,23 @@ ConditionalExpression--"CONDITION¹"-->ConditionalExpressionCONDITION[Declaration]:::outer +Reference--"REFERS_TO¹"-->ReferenceREFERS_TO[Declaration]:::outer ``` ## MemberExpression **Labels**:[Node](#enode) [Statement](#estatement) [Expression](#eexpression) -[DeclaredReferenceExpression](#edeclaredreferenceexpression) +[Reference](#edeclaredreferenceexpression) [MemberExpression](#ememberexpression) ### Relationships [BASE](#MemberExpressionBASE) -[REFERS_TO](#DeclaredReferenceExpressionREFERS_TO) +[REFERS_TO](#ReferenceREFERS_TO) [POSSIBLE_SUB_TYPES](#ExpressionPOSSIBLE_SUB_TYPES) @@ -1169,14 +1169,14 @@ flowchart LR classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; DeleteExpression--"OPERAND¹"-->DeleteExpressionOPERAND[Expression]:::outer ``` -## CompoundStatementExpression +## BlockStatementExpression **Labels**:[Node](#enode) [Statement](#estatement) [Expression](#eexpression) -[CompoundStatementExpression](#ecompoundstatementexpression) +[BlockStatementExpression](#ecompoundstatementexpression) ### Relationships -[STATEMENT](#CompoundStatementExpressionSTATEMENT) +[STATEMENT](#BlockStatementExpressionSTATEMENT) [POSSIBLE_SUB_TYPES](#ExpressionPOSSIBLE_SUB_TYPES) @@ -1196,11 +1196,11 @@ DeleteExpression--"OPERAND¹"-->DeleteExpressionOPERAND[E [TYPEDEFS](#NodeTYPEDEFS) -#### STATEMENT +#### STATEMENT ```mermaid flowchart LR classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; -CompoundStatementExpression--"STATEMENT¹"-->CompoundStatementExpressionSTATEMENT[Statement]:::outer +BlockStatementExpression--"STATEMENT¹"-->BlockStatementExpressionSTATEMENT[Statement]:::outer ``` ## ProblemExpression **Labels**:[Node](#enode) @@ -1526,7 +1526,7 @@ CatchClause--"PARAMETER¹"-->CatchClausePARAMETER[CompoundStatement]:::outer +CatchClause--"BODY¹"-->CatchClauseBODY[BlockStatement]:::outer ``` ## SwitchStatement **Labels**:[Node](#enode) @@ -1652,13 +1652,13 @@ flowchart LR classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; WhileStatement--"STATEMENT¹"-->WhileStatementSTATEMENT[Statement]:::outer ``` -## CompoundStatement +## BlockStatement **Labels**:[Node](#enode) [Statement](#estatement) -[CompoundStatement](#ecompoundstatement) +[BlockStatement](#ecompoundstatement) ### Relationships -[STATEMENTS](#CompoundStatementSTATEMENTS) +[STATEMENTS](#BlockStatementSTATEMENTS) [LOCALS](#StatementLOCALS) @@ -1674,11 +1674,11 @@ WhileStatement--"STATEMENT¹"-->WhileStatementSTATEMENT[St [TYPEDEFS](#NodeTYPEDEFS) -#### STATEMENTS +#### STATEMENTS ```mermaid flowchart LR classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; -CompoundStatement--"STATEMENTS*"-->CompoundStatementSTATEMENTS[Statement]:::outer +BlockStatement--"STATEMENTS*"-->BlockStatementSTATEMENTS[Statement]:::outer ``` ## ContinueStatement **Labels**:[Node](#enode) @@ -1748,7 +1748,7 @@ CompoundStatement--"STATEMENTS*"-->CompoundStatementSTATEMENTS[CompoundStatement]:::outer +SynchronizedStatement--"BLOCK_STATEMENT¹"-->SynchronizedStatementBLOCK_STATEMENT[BlockStatement]:::outer ``` #### EXPRESSION ```mermaid @@ -1794,13 +1794,13 @@ TryStatement--"RESOURCES*"-->TryStatementRESOURCES[Stateme ```mermaid flowchart LR classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; -TryStatement--"FINALLY_BLOCK¹"-->TryStatementFINALLY_BLOCK[CompoundStatement]:::outer +TryStatement--"FINALLY_BLOCK¹"-->TryStatementFINALLY_BLOCK[BlockStatement]:::outer ``` #### TRY_BLOCK ```mermaid flowchart LR classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; -TryStatement--"TRY_BLOCK¹"-->TryStatementTRY_BLOCK[CompoundStatement]:::outer +TryStatement--"TRY_BLOCK¹"-->TryStatementTRY_BLOCK[BlockStatement]:::outer ``` #### CATCH_CLAUSES ```mermaid @@ -1960,8 +1960,8 @@ LabelStatement--"SUB_STATEMENT¹"-->LabelStatementSUB_STATEMENT[Type]:::o ```mermaid flowchart LR classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; -ValueDeclaration--"USAGE*"-->ValueDeclarationUSAGE[DeclaredReferenceExpression]:::outer +ValueDeclaration--"USAGE*"-->ValueDeclarationUSAGE[Reference]:::outer ``` ## FieldDeclaration **Labels**:[Node](#enode) @@ -2216,7 +2216,7 @@ FunctionDeclaration--"RETURN_TYPES*"-->FunctionDeclarationRETURN_TYPES[ParamVariableDeclaration]:::outer +FunctionDeclaration--"PARAMETERS*"-->FunctionDeclarationPARAMETERS[ParameterDeclaration]:::outer ``` #### DEFINES ```mermaid @@ -2328,14 +2328,14 @@ MethodDeclaration--"RECORD_DECLARATION¹"-->MethodDeclarationRECORD_DECLARATION[ [TYPEDEFS](#NodeTYPEDEFS) -## ParamVariableDeclaration +## ParameterDeclaration **Labels**:[Node](#enode) [Declaration](#edeclaration) [ValueDeclaration](#evaluedeclaration) -[ParamVariableDeclaration](#eparamvariabledeclaration) +[ParameterDeclaration](#eparamvariabledeclaration) ### Relationships -[DEFAULT](#ParamVariableDeclarationDEFAULT) +[DEFAULT](#ParameterDeclarationDEFAULT) [POSSIBLE_SUB_TYPES](#ValueDeclarationPOSSIBLE_SUB_TYPES) @@ -2355,20 +2355,20 @@ MethodDeclaration--"RECORD_DECLARATION¹"-->MethodDeclarationRECORD_DECLARATION[ [TYPEDEFS](#NodeTYPEDEFS) -#### DEFAULT +#### DEFAULT ```mermaid flowchart LR classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; -ParamVariableDeclaration--"DEFAULT¹"-->ParamVariableDeclarationDEFAULT[Expression]:::outer +ParameterDeclaration--"DEFAULT¹"-->ParameterDeclarationDEFAULT[Expression]:::outer ``` -## TypeParamDeclaration +## TypeParameterDeclaration **Labels**:[Node](#enode) [Declaration](#edeclaration) [ValueDeclaration](#evaluedeclaration) -[TypeParamDeclaration](#etypeparamdeclaration) +[TypeParameterDeclaration](#etypeparamdeclaration) ### Relationships -[DEFAULT](#TypeParamDeclarationDEFAULT) +[DEFAULT](#TypeParameterDeclarationDEFAULT) [POSSIBLE_SUB_TYPES](#ValueDeclarationPOSSIBLE_SUB_TYPES) @@ -2388,11 +2388,11 @@ ParamVariableDeclaration--"DEFAULT¹"-->ParamVariableDeclarationDEFAULT[[TYPEDEFS](#NodeTYPEDEFS) -#### DEFAULT +#### DEFAULT ```mermaid flowchart LR classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; -TypeParamDeclaration--"DEFAULT¹"-->TypeParamDeclarationDEFAULT[Type]:::outer +TypeParameterDeclaration--"DEFAULT¹"-->TypeParameterDeclarationDEFAULT[Type]:::outer ``` ## TemplateDeclaration **Labels**:[Node](#enode) diff --git a/docs/docs/CPG/specs/schema.md b/docs/docs/CPG/specs/schema.md index dcb736047a..3b91b95eb8 100644 --- a/docs/docs/CPG/specs/schema.md +++ b/docs/docs/CPG/specs/schema.md @@ -238,7 +238,7 @@ Node--"TYPEDEFS*"-->NodeTYPEDEFS[TypedefDeclarati text-align: center; margin-bottom: 10px; padding-left: 10px; - padding-right: 10px;">[CompoundStatement](#ecompoundstatement) [BlockStatement](#ecompoundstatement) ReturnStatementRETURN_VALUES[[DeclaredReferenceExpression](#edeclaredreferenceexpression) [Reference](#edeclaredreferenceexpression) ReturnStatementRETURN_VALUES[[ExplicitConstructorInvocation](#eexplicitconstructorinvocation) [ConstructorCallExpression](#eexplicitconstructorinvocation) CallExpressionTEMPLATE_PARAMETERS[Node]:::outer ``` -# ExplicitConstructorInvocation +# ConstructorCallExpression **Labels**:CallExpressionTEMPLATE_PARAMETERS[[ExplicitConstructorInvocation](#eexplicitconstructorinvocation) + padding-right: 10px;">[ConstructorCallExpression](#eexplicitconstructorinvocation) ## Relationships CastExpressionEXPRESSION[Expression]:::outer ``` -# ArrayCreationExpression +# NewArrayExpression **Labels**:CastExpressionEXPRESSION[[ArrayCreationExpression](#earraycreationexpression) + padding-right: 10px;">[NewArrayExpression](#earraycreationexpression) ## Relationships CastExpressionEXPRESSION[[INITIALIZER](#ArrayCreationExpressionINITIALIZER) + padding-right: 10px;">[INITIALIZER](#NewArrayExpressionINITIALIZER) CastExpressionEXPRESSION[[DIMENSIONS](#ArrayCreationExpressionDIMENSIONS) + padding-right: 10px;">[DIMENSIONS](#NewArrayExpressionDIMENSIONS) CastExpressionEXPRESSION[[TYPEDEFS](#NodeTYPEDEFS) -### INITIALIZER +### INITIALIZER ```mermaid flowchart LR classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; -ArrayCreationExpression--"INITIALIZER¹"-->ArrayCreationExpressionINITIALIZER[Expression]:::outer +NewArrayExpression--"INITIALIZER¹"-->NewArrayExpressionINITIALIZER[Expression]:::outer ``` -### DIMENSIONS +### DIMENSIONS ```mermaid flowchart LR classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; -ArrayCreationExpression--"DIMENSIONS*"-->ArrayCreationExpressionDIMENSIONS[Expression]:::outer +NewArrayExpression--"DIMENSIONS*"-->NewArrayExpressionDIMENSIONS[Expression]:::outer ``` -# ArraySubscriptionExpression +# SubscriptionExpression **Labels**:ArrayCreationExpressionDIMENSIONS[[ArraySubscriptionExpression](#earraysubscriptionexpression) + padding-right: 10px;">[SubscriptionExpression](#earraysubscriptionexpression) ## Relationships ArrayCreationExpressionDIMENSIONS[[ARRAY_EXPRESSION](#ArraySubscriptionExpressionARRAY_EXPRESSION) + padding-right: 10px;">[ARRAY_EXPRESSION](#SubscriptionExpressionARRAY_EXPRESSION) ArrayCreationExpressionDIMENSIONS[[SUBSCRIPT_EXPRESSION](#ArraySubscriptionExpressionSUBSCRIPT_EXPRESSION) + padding-right: 10px;">[SUBSCRIPT_EXPRESSION](#SubscriptionExpressionSUBSCRIPT_EXPRESSION) ArrayCreationExpressionDIMENSIONS[[TYPEDEFS](#NodeTYPEDEFS) -### ARRAY_EXPRESSION +### ARRAY_EXPRESSION ```mermaid flowchart LR classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; -ArraySubscriptionExpression--"ARRAY_EXPRESSION¹"-->ArraySubscriptionExpressionARRAY_EXPRESSION[Expression]:::outer +SubscriptionExpression--"ARRAY_EXPRESSION¹"-->SubscriptionExpressionARRAY_EXPRESSION[Expression]:::outer ``` -### SUBSCRIPT_EXPRESSION +### SUBSCRIPT_EXPRESSION ```mermaid flowchart LR classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; -ArraySubscriptionExpression--"SUBSCRIPT_EXPRESSION¹"-->ArraySubscriptionExpressionSUBSCRIPT_EXPRESSION[Expression]:::outer +SubscriptionExpression--"SUBSCRIPT_EXPRESSION¹"-->SubscriptionExpressionSUBSCRIPT_EXPRESSION[Expression]:::outer ``` # TypeExpression **Labels**:ConditionalExpressionCONDITION[Expression]:::outer ``` -# DeclaredReferenceExpression +# Reference **Labels**:ConditionalExpressionCONDITION[[REFERS_TO](#DeclaredReferenceExpressionREFERS_TO) + padding-right: 10px;">[REFERS_TO](#ReferenceREFERS_TO) ConditionalExpressionCONDITION[Declaration]:::outer +Reference--"REFERS_TO¹"-->ReferenceREFERS_TO[Declaration]:::outer ``` # MemberExpression **Labels**:DeclaredReferenceExpressionREFERS_T text-align: center; margin-bottom: 10px; padding-left: 10px; - padding-right: 10px;">[DeclaredReferenceExpression](#edeclaredreferenceexpression) [Reference](#edeclaredreferenceexpression) DeclaredReferenceExpressionREFERS_T text-align: center; margin-bottom: 10px; padding-left: 10px; - padding-right: 10px;">[REFERS_TO](#DeclaredReferenceExpressionREFERS_TO) + padding-right: 10px;">[REFERS_TO](#ReferenceREFERS_TO) DeleteExpressionOPERAND[Expression]:::outer ``` -# CompoundStatementExpression +# BlockStatementExpression **Labels**:DeleteExpressionOPERAND[E text-align: center; margin-bottom: 10px; padding-left: 10px; - padding-right: 10px;">[CompoundStatementExpression](#ecompoundstatementexpression) + padding-right: 10px;">[BlockStatementExpression](#ecompoundstatementexpression) ## Relationships DeleteExpressionOPERAND[E text-align: center; margin-bottom: 10px; padding-left: 10px; - padding-right: 10px;">[STATEMENT](#CompoundStatementExpressionSTATEMENT) + padding-right: 10px;">[STATEMENT](#BlockStatementExpressionSTATEMENT) DeleteExpressionOPERAND[E margin-bottom: 10px; padding-left: 10px; padding-right: 10px;">[TYPEDEFS](#NodeTYPEDEFS) -### STATEMENT +### STATEMENT ```mermaid flowchart LR classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; -CompoundStatementExpression--"STATEMENT¹"-->CompoundStatementExpressionSTATEMENT[Statement]:::outer +BlockStatementExpression--"STATEMENT¹"-->BlockStatementExpressionSTATEMENT[Statement]:::outer ``` # ProblemExpression **Labels**:CatchClausePARAMETER[CompoundStatement]:::outer +CatchClause--"BODY¹"-->CatchClauseBODY[BlockStatement]:::outer ``` # SwitchStatement **Labels**:WhileStatementSTATEMENT[Statement]:::outer ``` -# CompoundStatement +# BlockStatement **Labels**:WhileStatementSTATEMENT[St text-align: center; margin-bottom: 10px; padding-left: 10px; - padding-right: 10px;">[CompoundStatement](#ecompoundstatement) + padding-right: 10px;">[BlockStatement](#ecompoundstatement) ## Relationships WhileStatementSTATEMENT[St text-align: center; margin-bottom: 10px; padding-left: 10px; - padding-right: 10px;">[STATEMENTS](#CompoundStatementSTATEMENTS) + padding-right: 10px;">[STATEMENTS](#BlockStatementSTATEMENTS) WhileStatementSTATEMENT[St margin-bottom: 10px; padding-left: 10px; padding-right: 10px;">[TYPEDEFS](#NodeTYPEDEFS) -### STATEMENTS +### STATEMENTS ```mermaid flowchart LR classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; -CompoundStatement--"STATEMENTS*"-->CompoundStatementSTATEMENTS[Statement]:::outer +BlockStatement--"STATEMENTS*"-->BlockStatementSTATEMENTS[Statement]:::outer ``` # ContinueStatement **Labels**:CompoundStatementSTATEMENTS[CompoundStatement]:::outer +SynchronizedStatement--"BLOCK_STATEMENT¹"-->SynchronizedStatementBLOCK_STATEMENT[BlockStatement]:::outer ``` ### EXPRESSION ```mermaid @@ -5908,13 +5908,13 @@ TryStatement--"RESOURCES*"-->TryStatementRESOURCES[Stateme ```mermaid flowchart LR classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; -TryStatement--"FINALLY_BLOCK¹"-->TryStatementFINALLY_BLOCK[CompoundStatement]:::outer +TryStatement--"FINALLY_BLOCK¹"-->TryStatementFINALLY_BLOCK[BlockStatement]:::outer ``` ### TRY_BLOCK ```mermaid flowchart LR classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; -TryStatement--"TRY_BLOCK¹"-->TryStatementTRY_BLOCK[CompoundStatement]:::outer +TryStatement--"TRY_BLOCK¹"-->TryStatementTRY_BLOCK[BlockStatement]:::outer ``` ### CATCH_CLAUSES ```mermaid @@ -6501,14 +6501,14 @@ LabelStatement--"SUB_STATEMENT¹"-->LabelStatementSUB_STATEMENT[Type]:::o ```mermaid flowchart LR classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; -ValueDeclaration--"USAGE*"-->ValueDeclarationUSAGE[DeclaredReferenceExpression]:::outer +ValueDeclaration--"USAGE*"-->ValueDeclarationUSAGE[Reference]:::outer ``` # FieldDeclaration **Labels**:FunctionDeclarationRETURN_TYPES[ParamVariableDeclaration]:::outer +FunctionDeclaration--"PARAMETERS*"-->FunctionDeclarationPARAMETERS[ParameterDeclaration]:::outer ``` ### DEFINES ```mermaid @@ -7684,7 +7684,7 @@ MethodDeclaration--"RECORD_DECLARATION¹"-->MethodDeclarationRECORD_DECLARATION[ margin-bottom: 10px; padding-left: 10px; padding-right: 10px;">[TYPEDEFS](#NodeTYPEDEFS) -# ParamVariableDeclaration +# ParameterDeclaration **Labels**:MethodDeclarationRECORD_DECLARATION[ text-align: center; margin-bottom: 10px; padding-left: 10px; - padding-right: 10px;">[ParamVariableDeclaration](#eparamvariabledeclaration) + padding-right: 10px;">[ParameterDeclaration](#eparamvariabledeclaration) ## Relationships MethodDeclarationRECORD_DECLARATION[ text-align: center; margin-bottom: 10px; padding-left: 10px; - padding-right: 10px;">[DEFAULT](#ParamVariableDeclarationDEFAULT) + padding-right: 10px;">[DEFAULT](#ParameterDeclarationDEFAULT) MethodDeclarationRECORD_DECLARATION[ margin-bottom: 10px; padding-left: 10px; padding-right: 10px;">[TYPEDEFS](#NodeTYPEDEFS) -### DEFAULT +### DEFAULT ```mermaid flowchart LR classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; -ParamVariableDeclaration--"DEFAULT¹"-->ParamVariableDeclarationDEFAULT[Expression]:::outer +ParameterDeclaration--"DEFAULT¹"-->ParameterDeclarationDEFAULT[Expression]:::outer ``` -# TypeParamDeclaration +# TypeParameterDeclaration **Labels**:ParamVariableDeclarationDEFAULT[[TypeParamDeclaration](#etypeparamdeclaration) + padding-right: 10px;">[TypeParameterDeclaration](#etypeparamdeclaration) ## Relationships ParamVariableDeclarationDEFAULT[[DEFAULT](#TypeParamDeclarationDEFAULT) + padding-right: 10px;">[DEFAULT](#TypeParameterDeclarationDEFAULT) ParamVariableDeclarationDEFAULT[[TYPEDEFS](#NodeTYPEDEFS) -### DEFAULT +### DEFAULT ```mermaid flowchart LR classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5; -TypeParamDeclaration--"DEFAULT¹"-->TypeParamDeclarationDEFAULT[Type]:::outer +TypeParameterDeclaration--"DEFAULT¹"-->TypeParameterDeclarationDEFAULT[Type]:::outer ``` # TemplateDeclaration **Labels**:>() +var parameterEdges = mutableListOf>() /** Virtual property for accessing [parameterEdges] without property edges. */ var parameters by PropertyEdgeDelegate(FunctionDeclaration::parameterEdges) diff --git a/docs/docs/GettingStarted/query.md b/docs/docs/GettingStarted/query.md index 6fb8a49444..361e78b760 100755 --- a/docs/docs/GettingStarted/query.md +++ b/docs/docs/GettingStarted/query.md @@ -49,7 +49,7 @@ all (==> false) -------- Starting at CallExpression[name=memcpy,location=vulnerable.cpp(3:5-3:38),type=UNKNOWN,base=]: 5 > 11 (==> false) ------------------------ - sizeof(DeclaredReferenceExpression[DeclaredReferenceExpression[name=array,location=vulnerable.cpp(3:12-3:17),type=PointerType[name=char[]]],refersTo=VariableDeclaration[name=array,location=vulnerable.cpp(2:10-2:28),initializer=Literal[location=vulnerable.cpp(2:21-2:28),type=PointerType[name=char[]],value=hello]]]) (==> 5) + sizeof(Reference[Reference[name=array,location=vulnerable.cpp(3:12-3:17),type=PointerType[name=char[]]],refersTo=VariableDeclaration[name=array,location=vulnerable.cpp(2:10-2:28),initializer=Literal[location=vulnerable.cpp(2:21-2:28),type=PointerType[name=char[]],value=hello]]]) (==> 5) ---------------------------------------- ------------------------ sizeof(Literal[location=vulnerable.cpp(3:19-3:32),type=PointerType[name=char[]],value=Hello world]) (==> 11) diff --git a/docs/docs/GettingStarted/shortcuts.md b/docs/docs/GettingStarted/shortcuts.md index f97492c443..3bfc3448d3 100644 --- a/docs/docs/GettingStarted/shortcuts.md +++ b/docs/docs/GettingStarted/shortcuts.md @@ -86,7 +86,7 @@ which search for other patterns in the graph. Note that these are often less stable than the information from above! * The size of an array is evaluated using - `ArraySubscriptionExpression.arraySize`. Unfortunately, this only works if the + `SubscriptExpression.arraySize`. Unfortunately, this only works if the size is given in the initialization. Updates are not considered. * Control dependencies are currently available via the extensions `Node.controlledBy()` and `IfStatement.controls()`. diff --git a/tutorial.md b/tutorial.md index 008b636ce3..c12f5686f0 100644 --- a/tutorial.md +++ b/tutorial.md @@ -89,22 +89,22 @@ In the following, we will use the aforementioned objects to query the source cod res3: List = ... ``` -The output here can be quite verbose, so additional filtering is needed. The `all` function takes an additional type parameter, which can be used to further filter nodes of a particular type. In this case, we are interested in all `ArraySubscriptionExpression` nodes, i.e. those that represent access to an element of an array. These operations are often prone to out of bounds errors and we want to explore, whether our code is also affected by that. +The output here can be quite verbose, so additional filtering is needed. The `all` function takes an additional type parameter, which can be used to further filter nodes of a particular type. In this case, we are interested in all `SubscriptExpression` nodes, i.e. those that represent access to an element of an array. These operations are often prone to out of bounds errors and we want to explore, whether our code is also affected by that. ```kotlin -[4] result.all() -res4: List = [ - {"@type":"ArraySubscriptionExpression","location":"array.go(6:2-6:7)","type":{"@type":"ObjectType","name":"int"}}, - {"@type":"ArraySubscriptionExpression","location":"array.cpp(6:12-6:18)","type":{"@type":"ObjectType","name":"char"}}, - {"@type":"ArraySubscriptionExpression","location":"Array.java(8:18-8:22)","type":{"@type":"ObjectType","name":"char"}}, - {"@type":"ArraySubscriptionExpression","location":"array.cpp(12:12-12:16)","type":{"@type":"ObjectType","name":"char"}} +[4] result.all() +res4: List = [ + {"@type":"SubscriptExpression","location":"array.go(6:2-6:7)","type":{"@type":"ObjectType","name":"int"}}, + {"@type":"SubscriptExpression","location":"array.cpp(6:12-6:18)","type":{"@type":"ObjectType","name":"char"}}, + {"@type":"SubscriptExpression","location":"Array.java(8:18-8:22)","type":{"@type":"ObjectType","name":"char"}}, + {"@type":"SubscriptExpression","location":"array.cpp(12:12-12:16)","type":{"@type":"ObjectType","name":"char"}} ] ``` Much better. We have found four nodes that represent an array access. To see the corresponding source code of our result, we can prefix our previous command with `:code` or `:c`. This shows the raw source code as well as the location of the file where the code is located. ```kotlin -[5] :code result.all() +[5] :code result.all() --- src/test/resources/array.go:6:2 --- 6: a[11] ------------------------------------------------ @@ -129,7 +129,7 @@ This also demonstrates quite nicely, that queries on the CPG work independently In a next step, we want to identify, which of those expression are accessing an array index that is greater than its capacity, thus leading to an error. From the code output we have seen before we can already identify two array indicies: `0` and `11`. But the other two are using a variable `b` as the index. Using the `evaluate` function, we can try to evaluate the variable `b`, to check if it has a constant value. ```kotlin -[6] result.all().map { it.subscriptExpression.evaluate() } +[6] result.all().map { it.subscriptExpression.evaluate() } res6: List = [11, 5, 5, 0] ``` @@ -138,17 +138,17 @@ In this case we are in luck and we see that, next to the `0` and `11` we already In a next step, we want to check to capacity of the array the access is referring to. We can make use of two helper functions `dfgFrom` and `capacity` to quickly check this, using the built-in data flow analysis. ```kotlin -[7] var expr = result.all().map { Triple( +[7] var expr = result.all().map { Triple( it.subscriptExpression.evaluate() as Int, - it.arrayExpression.dfgFrom().first().capacity, + it.arrayExpression.dfgFrom().first().capacity, it ) } [8]: expr -res8: List> = [ - (11, 10, {"@type":"ArraySubscriptionExpression","location":"array.go(6:2-6:7)","type":{"@type":"ObjectType","name":"int"}}), - (5, 4, {"@type":"ArraySubscriptionExpression","location":"array.cpp(6:12-6:18)","type":{"@type":"ObjectType","name":"char"}}), - (5, 4, {"@type":"ArraySubscriptionExpression","location":"Array.java(8:18-8:22)","type":{"@type":"ObjectType","name":"char"}}), - (0, 100, {"@type":"ArraySubscriptionExpression","location":"array.cpp(12:12-12:16)","type":{"@type":"ObjectType","name":"char"}}) +res8: List> = [ + (11, 10, {"@type":"SubscriptExpression","location":"array.go(6:2-6:7)","type":{"@type":"ObjectType","name":"int"}}), + (5, 4, {"@type":"SubscriptExpression","location":"array.cpp(6:12-6:18)","type":{"@type":"ObjectType","name":"char"}}), + (5, 4, {"@type":"SubscriptExpression","location":"Array.java(8:18-8:22)","type":{"@type":"ObjectType","name":"char"}}), + (0, 100, {"@type":"SubscriptExpression","location":"array.cpp(12:12-12:16)","type":{"@type":"ObjectType","name":"char"}}) ] ``` @@ -158,10 +158,10 @@ Lastly, we can make use of the `filter` function to return only those nodes wher ```kotlin [9] expr.filter { it.first >= it.second } -res8: List> = [ - (11, 10, {"@type":"ArraySubscriptionExpression","location":"array.go(6:2-6:7)","type":{"@type":"ObjectType","name":"int"}}), - (5, 4, {"@type":"ArraySubscriptionExpression","location":"array.cpp(6:12-6:18)","type":{"@type":"ObjectType","name":"char"}}), - (5, 4, {"@type":"ArraySubscriptionExpression","location":"Array.java(8:18-8:22)","type":{"@type":"ObjectType","name":"char"}}) +res8: List> = [ + (11, 10, {"@type":"SubscriptExpression","location":"array.go(6:2-6:7)","type":{"@type":"ObjectType","name":"int"}}), + (5, 4, {"@type":"SubscriptExpression","location":"array.cpp(6:12-6:18)","type":{"@type":"ObjectType","name":"char"}}), + (5, 4, {"@type":"SubscriptExpression","location":"Array.java(8:18-8:22)","type":{"@type":"ObjectType","name":"char"}}) ] ``` @@ -189,13 +189,13 @@ Because the manual analyis we have shown can be quite tedious, we already includ ```kotlin [11] :run ---- FINDING: Out of bounds access in ArrayCreationExpression when accessing index 11 of a, an array of length 10 --- +--- FINDING: Out of bounds access in NewArrayExpression when accessing index 11 of a, an array of length 10 --- src/test/resources/array.go:6:2: a[11] The following path was discovered that leads to 11 being 11: src/test/resources/array.go:6:4: 11 ---- FINDING: Out of bounds access in ArrayCreationExpression when accessing index 5 of c, an array of length 4 --- +--- FINDING: Out of bounds access in NewArrayExpression when accessing index 5 of c, an array of length 4 --- src/test/resources/array.cpp:6:12: = c[b] The following path was discovered that leads to b being 5:

w%k_tC9o+J?jlEWgdzIe0b`MG#H=N&ZEd~ha9 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 -

+ +
+

Supported Languages

+ + --- + + The library supports the following programming languages out of the box: + + * Java + * C/C++ + * Go + * Python + * TypeScript + * LLVM-IR + + Nothing suitable found? [Write your own language frontend](./CPG/impl/language.md) + for the respective language. +
+ +
+

Built-in Analyses

+ + --- + + The library currently provides different analyses: + + * Dataflow Analysis + * Reachability Analysis + * Constant Propagation + * Intraprocedural Order Evaluation of Statements +
+ +
+

Accessing the Graph

+ + --- + + The library can be used by analysts or tools in different ways: + + * The graph can be exported to the graph database [neo4j](https://neo4j.com) + * The CPG can be included into every project as a library + * We offer an interactive CLI to explore the graph + * We provide an API for querying the graph for interesting properties +
+ +
+

Highly Extensible

+ + --- + + The library is easily extensible. You can add new... + + * language frontends [Tell me more about it!](./CPG/impl/language.md), + * passes [Tell me more about it!](./CPG/impl/passes.md) or + * analyses. +
+ +
+

Handling Incomplete Code

+ + --- + + The code you have to analyze is missing dependencies, is under active development and might + miss some code fragments? +
+ No problem! Our tooling provides a certain resilience against such problems. + +
+ +
+ +
+ +

+ +

+
+ +
+
+

About Us + +

+ +

We're a team of researchers at Fraunhofer AISEC. +We're interested in different topics in the area of static program analysis. If +you're interested in our work, feel free to reach out to us - we're happy to +collaborate and push the boundaries of static code analysis. +

+
+ +

+ + +## Publications +### 2023 + +

+ +
+

AbsIntIO: Towards Showing the Absence of Integer Overflows in Binaries using Abstract Interpretation

+
+

Alexander Küchler, Leon Wenning, Florian Wendland

+

In: ACM ASIA Conference on Computer and Communications Security (Asia CCS). Melbourne, VIC, Australia.

+
bibtex +
@inproceedings{kuechler2023absintio,
+  author={Alexander K\"uchler and Leon Wenning, and Florian Wendland},
+  title={AbsIntIO: Towards Showing the Absence of Integer Overflows in Binaries using Abstract Interpretation},
+  year={2023},
+  booktitle={ACM ASIA Conference on Computer and Communications Security},
+  series={Asia CCS '23},
+  doi={10.1145/3579856.3582814},
+  location={Melbourne, VIC, Australia},
+  publisher={ACM}
+}
+
+
+
+ paper +
+
+ +
+ +### 2022 + +
+ +
+

Representing LLVM-IR in a Code Property Graph

+ +
+

Alexander Küchler, Christian Banse

+

In: 25th Information Security Conference (ISC). Bali, Indonesia.

+
bibtex +
@inproceedings{kuechler2022representing,
+  author={Alexander K\"uchler and Christian Banse},
+  title={Representing LLVM-IR in a Code Property Graph},
+  year={2022},
+  booktitle={25th Information Security Conference},
+  series={ISC},
+  doi={10.1007/978-3-031-22390-7\_21},
+  location={Bali, Indonesia},
+  publisher={Springer}
+}
+
+
+
+ preprint
+ paper +
+
+ +
+

A Language-Independent Analysis Platform for Source Code

+ +
+

Konrad Weiss, Christian Banse

+
bibtex +
@misc{weiss2022a,
+  doi = {10.48550/ARXIV.2203.08424},
+  url = {https://arxiv.org/abs/2203.08424},
+  author = {Weiss, Konrad and Banse, Christian},
+  title = {A Language-Independent Analysis Platform for Source Code},
+  publisher = {arXiv},
+  year = {2022},
+}
+
+
+
+ paper +
+ +
+ +
+ +### 2021 + +
+ +
+

Cloud Property Graph: Connecting Cloud Security Assessments with Static Code Analysis

+ +
+

Christian Banse, Immanuel Kunz, Angelika Schneider, Konrad Weiss

+

In: 2021 IEEE 14th International Conference on Cloud Computing (CLOUD). Los Alamitos, CA, USA

+
bibtex +
@inproceedings{banse2021cloudpg,
+  author = {Christian Banse and Immanuel Kunz and Angelika Schneider and Konrad Weiss},
+  booktitle = {2021 IEEE 14th International Conference on Cloud Computing (CLOUD)},
+  title = {Cloud Property Graph: Connecting Cloud Security Assessments with Static Code Analysis},
+  year = {2021},
+  pages = {13-19},
+  doi = {10.1109/CLOUD53861.2021.00014},
+  url = {https://doi.ieeecomputersociety.org/10.1109/CLOUD53861.2021.00014},
+  publisher = {IEEE Computer Society},
+  address = {Los Alamitos, CA, USA},
+  month = {sep}
+}
+
+
+
+ preprint
+ paper +
+
+
+ diff --git a/docs/docs/stylesheets/extra.css b/docs/docs/stylesheets/extra.css new file mode 100755 index 0000000000..5c78c78f26 --- /dev/null +++ b/docs/docs/stylesheets/extra.css @@ -0,0 +1,448 @@ +[data-md-color-scheme="light"] { + --md-primary-fg-color: var(--fraunhofer-green); + --md-secondary-fg-color: var(--fraunhofer-blue); + --md-accent-fg-color: #728bab; + --md-typeset-color: #555555; +} + +[data-md-color-scheme="slate"] { + --md-primary-fg-color: var(--fraunhofer-green); + --md-footer-bg-color--dark: var(--fraunhofer-blue); + /* need to set the typeset color because it uses the primary color otherwise*/ + --md-typeset-a-color: #5981b4; + --md-hue: 220; + --md-accent-fg-color: #a4bede; +} + +:root { + --md-text-font-family: FrutigerLTW02,"Helvetica Neue",Helvetica,Arial,sans-serif; + --md-footer-bg-color--dark: var(--fraunhofer-blue); + --md-footer-bg-color--light: var(--fraunhofer-blue); + --md-default-fg-color--light: #1f82c0; + --fraunhofer-green: rgb(23, 156, 125); + --fraunhofer-green-light: rgba(23, 156, 125, 0.2); + --fraunhofer-blue: rgb(0, 91, 127); + --fraunhofer-blue-light: rgba(0, 91, 127, 0.4); + --fraunhofer-blue-medium: rgba(0, 91, 127, 0.6); + --border-gray: rgb(199, 202, 204); + --md-admonition-icon--paper: url('data:image/svg+xml;charset=utf8,'); +} + +body { + --md-text-font-family: FrutigerLTW02,"Helvetica Neue",Helvetica,Arial,sans-serif; +} + +.md-typeset .admonition.paper, +.md-typeset details.paper { + border-color: var(--fraunhofer-green); + font-size: 0.8rem; +} + +.md-typeset hr { + border: 1px solid #c7cacc; + border-top: none; + display: flow-root; + margin: 1.5em 0; + margin-top: 0; + margin-right: 0; + margin-bottom: 1.5em; + margin-left: 0; +} + +.md-typeset { + line-height: 1.1rem; +} + +.md-typeset h1 { + color: var(--md-default-fg-color--light); + font-size: 2em; + line-height: 1.3; + border: 1px solid #c7cacc; + border-bottom: none; + padding-top: 1.2rem; + padding-right: 0.8rem; + padding-bottom: 1.2rem; + padding-left: 0.8rem; + font-weight: 300; + letter-spacing: -.01em; + margin: 0; +} + +.md-typeset p { + border: 1px solid #c7cacc; + border-bottom: none; + border-top: none; + padding-right: 0.8rem; + padding-left: 0.8rem; + padding-bottom: 0.8rem; + margin: 0; +} + +.md-typeset div.papers { + border: 1px solid #c7cacc; + border-bottom: none; + border-top: none; + padding-top: 1.5625em; + padding-bottom: 1.5625em; + padding-right: 0.8rem; + padding-left: 0.8rem; + margin: 0; +} + +.md-typeset h2 { + color: var(--md-default-fg-color--light); + font-size: 1.5625em; + line-height: 1.4; + font-weight:bold; + border: 1px solid #c7cacc; + border-bottom: none; + padding-top: 1.2rem; + padding-right: 0.8rem; + padding-bottom: 1.2rem; + padding-left: 0.8rem; + margin: 0; +} + +.md-typeset h3 { + color: var(--md-default-fg-color--light); + font-size: 1.2em; + line-height: 1.2; + font-weight:bold; + padding-top: 1.2rem; + padding-right: 0.8rem; + padding-bottom: 0.8rem; + padding-left: 0.8rem; + margin: 0; + border: 1px solid #c7cacc; + border-bottom: none; +} + +.md-typeset h4 { + color: var(--md-default-fg-color--light); + padding-right: 0.8rem; + padding-left: 0.8rem; + margin: 0; + border: 1px solid #c7cacc; + border-top: none; + border-bottom: none; +} + +.md-typeset h5 { + color: var(--md-default-fg-color--light); + padding-right: 0.8rem; + padding-left: 0.8rem; + margin: 0; + border: 1px solid #c7cacc; + border-top: none; + border-bottom: none; +} + +.md-typeset .highlight { + padding-right: 0.8rem; + padding-left: 0.8rem; + margin: 0; + border: 1px solid #c7cacc; + border-top: none; + border-bottom: none; +} + +.md-typeset .highlight > pre{ + margin:0; + padding-top:1em; + padding-bottom:1em; +} + +[dir="ltr"] .md-typeset ol, [dir="ltr"] .md-typeset ul { + padding-right: 0.8rem; + padding-left: 1.2rem; + padding-top:0em; + padding-bottom:1em; + margin: 0; + border: 1px solid #c7cacc; + border-top: none; + border-bottom: none; +} + +.md-typeset .box h3 + hr { + padding-top: 1.2rem; + border-left: none; + border-right: none; +} + +.md-typeset .mermaid { + padding-right: 0.8rem; + padding-left: 1rem; + margin: 0; + border: 1px solid #c7cacc; + border-top: none; + border-bottom: none; +} + +.md-typeset .child { + background:#dddddd; + border-radius:5%; + line-height: 26px; + display: inline-block; + text-align: center; + margin-bottom: 10px; + padding-left: 10px; + padding-right: 10px; +} + +.md-typeset .classLabel { + background:var(--fraunhofer-blue-light); + color: #ffffff; +} + +.md-typeset .relationship { + background:var(--fraunhofer-blue); + color: #ffffff; +} +.md-typeset .relationship > a:link, +.md-typeset .relationship > a:visited, +.md-typeset .relationship > a:hover, +.md-typeset .relationship > a:active { + color:#FFFFFF; +} + +.md-typeset h2 + h3 { + margin: 0; +} + +.box h3 { + color: var(--md-default-fg-color--light); + font-size: 1.2em; + line-height: 1.2; + font-weight:bold; + padding-top: 1.2rem; + padding-right: 0.8rem; + padding-bottom: 0rem; + padding-left: 0.8rem; + margin: 0; + border: none; +} + +[dir="ltr"] .md-typeset .box ol, [dir="ltr"] .md-typeset .box ul { + border: none; +} + +.md-typeset .empty { + height: 5em; +} + +.md-typeset .empty-small { + height: 2em; +} + +.md-typeset .paper > .admonition-title, +.md-typeset .paper > summary { + background-color: var(--fraunhofer-green); + border-color: var(--fraunhofer-green); + font-size: 0.9rem; + padding-left:3rem; + margin-left: 0px; + margin-top: .625em; + min-height: 2rem; + vertical-align: middle; +} + +.md-typeset .paper > .admonition-title::before, +.md-typeset .paper > summary::before { + background-color: #FFFFFF; + -webkit-mask-image: var(--md-admonition-icon--paper); + mask-image: var(--md-admonition-icon--paper); + height: 2rem; + width: 2rem; + top: 0px; +} + +.md-typeset .paper p { + border: none; + padding:0px; +} + +.md-typeset .paper { + border-radius:0px; + background-color: var(--fraunhofer-green); + color:#FFFFFF; + margin-top:1px; + margin-bottom:1px; +} + +.md-typeset details > .admonition-title::before, +.md-typeset details > summary::before { + background-color: #FFFFFF; +} + +.md-typeset details { + background-color: var(--fraunhofer-blue); + color: #FFFFFF; +} + +.md-typeset details > summary { + text-transform: uppercase; +} + +.authors { + font-weight: 300; + margin:0; +} + +.conference { + font-size: .7rem; + margin:0; +} + +div.left { + float:left; + width:80%; + padding-left:3rem; +} + +div.right-picture { + display: flex; + flex-direction: column; + align-items: flex-start; + padding: 0px; + gap: 10px; + width: 20%; + flex: none; + order: 0; + align-self: stretch; + flex-grow: 0; +} + +div.right-picture img { + background: url(.jpg); + flex: none; + order: 0; + align-self: stretch; + flex-grow: 0; +} + +div.right-picture-text { + display: flex; + flex-direction: column; + align-items: flex-start; + width: 80%; + flex: none; + order: 1; + flex-grow: 0; +} + + +div.right-picture-text p { + border: none; +} + +div.right-picture-text h2 { + border: none; +} + +div.left-picture { + display: flex; + flex-direction: column; + align-items: flex-start; + padding: 0px; + gap: 10px; + width: 20%; + flex: none; + order: 0; + align-self: stretch; + flex-grow: 0; + align-items: center; +} + +div.left-picture img { + align-self: stretch; + flex-grow: 0; + object-fit: cover; + height:100%; +} + +div.left-picture-text { + display: flex; + flex-direction: column; + align-items: flex-start; + width: 80%; + flex: none; + order: 1; + flex-grow: 0; +} + + +div.left-picture-text p { + border: none; +} + +div.left-picture-text h2 { + border: none; +} + +div.float-container { + box-sizing: border-box; + display: flex; + flex-direction: row; + align-items: center; + padding: 0px; + border: 1px solid #c7cacc; + border-bottom: none; + flex: none; + order: 0; + align-self: stretch; + flex-grow: 0; +} + +div.right { + float:right; + width:18%; + padding-left:2%; + vertical-align: middle; +} + +.md-typeset details > summary { + background-color: var(--fraunhofer-green-light); + border-color: var(--fraunhofer-green); +} + +.md-typeset details { + margin-top:0; + border-color: var(--fraunhofer-green); +} + + +.md-typeset .3-cards-grid { + display: grid; + grid-template-columns: 100px 100px 100px; +} + +.box { + border: 1px solid #c7cacc; +} + +.box p { + border: none; +} + +a.green-button { + -webkit-appearance: button; + -moz-appearance: button; + appearance: button; + color:#FFFFFF; + border:1px solid #FFFFFF; + padding-top:0.2rem; + padding-bottom:0.2rem; + color: initial; + width:100%; + display: inline-block; + text-align:center; + margin-top: 0.3em; + text-transform: uppercase; +} + +a.green-button:link, +a.green-button:visited, +a.green-button:hover, +a.green-button:active { + color:#FFFFFF; +} diff --git a/docs/mkdocs-material-plugins.txt b/docs/mkdocs-material-plugins.txt new file mode 100755 index 0000000000..5bc1347690 --- /dev/null +++ b/docs/mkdocs-material-plugins.txt @@ -0,0 +1,3 @@ +mkdocs-git-revision-date-localized-plugin +mkdocs-glightbox +mkdocs-minify-plugin diff --git a/docs/mkdocs.yaml b/docs/mkdocs.yaml new file mode 100755 index 0000000000..d88d99f2dd --- /dev/null +++ b/docs/mkdocs.yaml @@ -0,0 +1,171 @@ + +# yaml-language-server: $schema=https://squidfunk.github.io/mkdocs-material/schema.json + +# Project information +site_name: Code Property Graph +site_url: https://fraunhofer-aisec.github.io/cpg/ +site_author: Fraunhofer AISEC +site_description: Language-agnostic code analysis + +# Repository +repo_name: Fraunhofer-AISEC/cpg +repo_url: https://github.com/Fraunhofer-AISEC/cpg +edit_uri: edit/main/docs/ + +# Copyright +copyright: > +

+ Privacy Policy, Change cookie settings +

+ Copyright © 2023 Fraunhofer AISEC +# Configuration +theme: + name: material + + icon: + repo: fontawesome/brands/github + + language: en + features: + - content.code.annotate + # - content.tabs.link + - content.tooltips + # - header.autohide + # - navigation.expand + # - navigation.indexes + # - navigation.instant + # - navigation.prune + # - navigation.sections + - navigation.tabs + # - navigation.tabs.sticky + - navigation.indexes + - navigation.top + - navigation.tracking + - search.highlight + - search.share + - search.suggest + - toc.follow + # - toc.integrate + + palette: + # Palette toggle for light mode + - media: "(prefers-color-scheme: light)" + scheme: light + toggle: + icon: material/brightness-7 + name: Switch to dark mode + + # Palette toggle for dark mode + - media: "(prefers-color-scheme: dark)" + scheme: slate + toggle: + icon: material/brightness-4 + name: Switch to light mode + + font: false + +extra_css: + - stylesheets/extra.css + +# Plugins +plugins: + - glightbox: + zoomable: true + - search + - minify: + minify_html: true + - git-revision-date-localized: + enable_creation_date: true + type: timeago + fallback_to_build_date: true + +# Customization +extra: + social: + - icon: fontawesome/brands/github + link: https://github.com/Fraunhofer-AISEC + name: Visit us on GitHub to get involved. + - icon: fontawesome/solid/envelope + link: mailto:cpg@aisec.fraunhofer.de + name: Email us about the CPG project. + - icon: fontawesome/brands/twitter + link: https://twitter.com/FraunhoferAISEC + name: Follow Fraunhofer AISEC on Twitter. + + consent: + title: Consent + description: >- + We use external services to enrich information presented on our website. This information is not essential + for the operation of this website. You can opt-in, if you want to see additional information. Your choice + will be saved in a cookie. + actions: + - reject + - accept + - manage + cookies: + github: + name: GitHub + checked: false + +# Extensions +markdown_extensions: + - abbr + - admonition + - attr_list + - def_list + - footnotes + - meta + - md_in_html + - toc: + permalink: true + - pymdownx.arithmatex: + generic: true + - pymdownx.betterem: + smart_enable: all + - pymdownx.caret + - pymdownx.details + - pymdownx.emoji: + emoji_generator: !!python/name:materialx.emoji.to_svg + emoji_index: !!python/name:materialx.emoji.twemoji + - pymdownx.highlight: + anchor_linenums: true + - pymdownx.inlinehilite + - pymdownx.keys + - pymdownx.magiclink: + repo_url_shorthand: true + user: squidfunk + repo: mkdocs-material + - pymdownx.mark + - pymdownx.smartsymbols + - pymdownx.superfences: + custom_fences: + - name: mermaid + class: mermaid + format: !!python/name:pymdownx.superfences.fence_code_format + - pymdownx.tabbed: + alternate_style: true + - pymdownx.tasklist: + custom_checkbox: true + - pymdownx.tilde + +nav: + - 'Home': index.md + - 'Getting Started': + - GettingStarted/index.md + - 'Installing the CPG library': GettingStarted/installation.md + - 'Usage as library': GettingStarted/library.md + - 'Using the Interactive CLI': GettingStarted/cli.md + - 'Using the Query API': GettingStarted/query.md + - 'Specifications': + - CPG/specs/index.md + - 'Graph Schema': CPG/specs/graph.md + - 'Dataflow Graph (DFG)': CPG/specs/dfg.md + - 'Evaluation Order Graph (EOG)': CPG/specs/eog.md + - 'Implementation': + - CPG/impl/index.md + - 'Language Frontends': CPG/impl/language.md + - 'Scopes': CPG/impl/scopes.md + - 'Passes': CPG/impl/passes.md + - 'Contributing': + - 'Contributing to the CPG library': Contributing/index.md + - 'Documentation': dokka/dokkaCustomMultiModuleOutput From dc2f14fb6661c2f4886e54d7148bcf09a39c5a49 Mon Sep 17 00:00:00 2001 From: KuechA <31155350+KuechA@users.noreply.github.com> Date: Wed, 14 Jun 2023 15:22:11 +0200 Subject: [PATCH 067/143] Fix documentation page (#1207) fix docs with alex Co-authored-by: Maximilian Kaul --- docs/docs/index.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/docs/index.md b/docs/docs/index.md index d52cbba4f9..18877c454c 100755 --- a/docs/docs/index.md +++ b/docs/docs/index.md @@ -73,7 +73,7 @@ programming language. * We provide an API for querying the graph for interesting properties

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