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 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 03f9d6e7b2..f462b3b482 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,24 +7,22 @@ on: - main tags: - v*.** + paths-ignore: + - "docs/**" pull_request: types: [opened, synchronize, reopened] + paths-ignore: + - "docs/**" jobs: build-cpgo-osx: 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@v3 - with: - go-version: 1.18 - - uses: actions/setup-java@v3 + uses: actions/setup-go@v4 with: - distribution: "zulu" - java-version: "17" + go-version: "1.20" - name: Build run: | cd cpg-language-go/src/main/golang @@ -43,7 +41,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 @@ -55,18 +53,17 @@ 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@v3 + uses: actions/setup-go@v4 with: go-version: 1.18 - name: Cache SonarCloud packages @@ -92,7 +89,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 @@ -111,41 +110,80 @@ 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 -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 -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: Archive test reports + - name: Prepare test and coverage reports + if: ${{ always() }} + run: | + zip reports.zip **/build/reports/**/** || true + - name: Archive test and coverage reports if: ${{ always() }} uses: actions/upload-artifact@v3 with: - name: test - path: "**/build/reports/tests" - - name: Publish + name: reports + path: reports.zip + - 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 -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 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: Publish JavaDoc + - 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: | + ./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 + 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 with: - folder: build/dokkaCustomMultiModuleOutput - - name: "Create Release" + 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') id: create_release uses: softprops/action-gh-release@v1 diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000000..c532c7681e --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,47 @@ +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@v4 + 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 + clean-exclude: dokka/** diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3653cf96d5..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) @@ -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 5653e237a1..4e1eea4d7e 100644 --- a/README.md +++ b/README.md @@ -10,14 +10,15 @@ 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: -* [Dataflow Graph](./cpg-core/specifications/dfg.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 @@ -113,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/build.gradle.kts b/build.gradle.kts index 46e4f14eca..83cc1752e5 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. * @@ -30,6 +34,7 @@ plugins { id("org.jetbrains.dokka") id("org.sonarqube") + id("io.github.gradle-nexus.publish-plugin") } // this is needed for the plugins block @@ -37,60 +42,109 @@ repositories { mavenCentral() } +allprojects { + plugins.apply("org.jetbrains.dokka") + + group = "de.fraunhofer.aisec" + + val dokkaPlugin by configurations + dependencies { + dokkaPlugin("org.jetbrains.dokka:versioning-plugin:1.9.0") + } +} + // 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 configuredVersion = project.version.toString() + if(configuredVersion.isNotEmpty() && configuredVersion != "unspecified") { + 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 // -// 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") } } +/** + * Publishing to maven central + */ +nexusPublishing { + repositories { + sonatype() { + val mavenCentralUsername: String? by project + val mavenCentralPassword: String? by project + + username.set(mavenCentralUsername) + password.set(mavenCentralPassword) + } + } +} + + // // 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 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() } 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/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..dec3f4cc59 100644 --- a/buildSrc/src/main/kotlin/cpg.common-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/cpg.common-conventions.gradle.kts @@ -6,14 +6,12 @@ plugins { `java-library` jacoco - kotlin("jvm") - id("org.jetbrains.dokka") signing `maven-publish` + kotlin("jvm") + id("org.jetbrains.dokka") } -group = "de.fraunhofer.aisec" - java { withSourcesJar() } @@ -79,20 +77,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 { @@ -120,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 9f8c7d874e..ab526156a5 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. @@ -65,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. # @@ -92,28 +117,28 @@ 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(" - ") } python { + targetExclude( + fileTree(project.projectDir) { + include("**/node_modules") + } + ) 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(" - ") } -} \ No newline at end of file +} 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-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) +} 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-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..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 @@ -28,9 +28,11 @@ 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.* +import de.fraunhofer.aisec.cpg.passes.EdgeCachePass import de.fraunhofer.aisec.cpg.passes.astParent import org.slf4j.Logger import org.slf4j.LoggerFactory @@ -71,16 +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) @@ -91,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. @@ -125,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 @@ -136,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) } } "--" -> { @@ -146,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) } } } @@ -157,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) } } @@ -175,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. @@ -240,29 +279,26 @@ 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 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() 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) @@ -278,67 +314,60 @@ class MultiValueEvaluator : ValueEvaluator() { val loopOp = loop.iterationStatement loopVar = when (loopOp) { - is BinaryOperator -> { + is AssignExpression -> { if ( loopOp.operatorCode == "=" && - (loopOp.lhs as? DeclaredReferenceExpression)?.refersTo == + (loopOp.lhs.singleOrNull() as? Reference)?.refersTo == expr.refersTo && - loopOp.rhs is BinaryOperator + 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 - as? DeclaredReferenceExpression) - ?.refersTo == expr.refersTo - ) { - loopVar - } else { - (loopOp.rhs as BinaryOperator).lhs - } - val opRhs = - if ( - ((loopOp.rhs as BinaryOperator).rhs - as? DeclaredReferenceExpression) - ?.refersTo == expr.refersTo - ) { - loopVar - } else { - evaluateInternal((loopOp.rhs as BinaryOperator).rhs, depth + 1) - } - computeBinaryOpEffect(opLhs, opRhs, (loopOp.rhs as BinaryOperator)) - as? Number - } else { - // No idea what this is but it's a binary op... - val opLhs = - if ( - (loopOp.lhs as? DeclaredReferenceExpression)?.refersTo == + ((loopOp.rhs())?.lhs as? Reference)?.refersTo == expr.refersTo ) { loopVar } else { - loopOp.lhs + (loopOp.rhs())?.lhs } val opRhs = if ( - (loopOp.rhs as? DeclaredReferenceExpression)?.refersTo == + ((loopOp.rhs())?.rhs as? Reference)?.refersTo == expr.refersTo ) { loopVar } else { - loopOp.rhs + evaluateInternal((loopOp.rhs())?.rhs, depth + 1) } - computeBinaryOpEffect(opLhs, opRhs, loopOp) as? Number + computeBinaryOpEffect(opLhs, opRhs, (loopOp.rhs())) + as? Number + } else { + cannotEvaluate(loopOp, this) } } + is BinaryOperator -> { + + // No idea what this is but it's a binary op... + val opLhs = + if ((loopOp.lhs as? Reference)?.refersTo == expr.refersTo) { + loopVar + } else { + loopOp.lhs + } + val opRhs = + if ((loopOp.rhs as? Reference)?.refersTo == expr.refersTo) { + loopVar + } else { + loopOp.rhs + } + computeBinaryOpEffect(opLhs, opRhs, loopOp) as? Number + } is UnaryOperator -> { computeUnaryOpEffect( - if ( - (loopOp.input as? DeclaredReferenceExpression)?.refersTo == - expr.refersTo - ) { - loopVar!! + if ((loopOp.input as? Reference)?.refersTo == expr.refersTo) { + loopVar } else { loopOp.input }, @@ -353,12 +382,11 @@ class MultiValueEvaluator : ValueEvaluator() { if (loopVar == null) { return result } - // result.add(loopVar) - 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) @@ -366,26 +394,26 @@ 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) { - 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 b9cd312214..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,11 +25,18 @@ */ 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 + abstract fun max(): Long + abstract fun addValue(value: Long) + abstract fun maybe(value: Long): Boolean + abstract fun clear() } @@ -45,15 +52,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,16 +75,37 @@ 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() } + + 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/SizeEvaluator.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/SizeEvaluator.kt index 0d86d3c7c3..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 @@ -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? { @@ -52,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 394b79a1e0..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 @@ -26,11 +26,11 @@ 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.* import de.fraunhofer.aisec.cpg.graph.statements.expressions.AssignExpression -import kotlin.UnsupportedOperationException import org.slf4j.Logger import org.slf4j.LoggerFactory @@ -77,26 +77,30 @@ 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 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) - 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 @@ -105,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 } @@ -127,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) "-", @@ -146,93 +168,41 @@ 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) -> - 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: 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() - 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 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) -> - 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: 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() - 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 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 { @@ -240,7 +210,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 { @@ -248,7 +218,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 { @@ -256,7 +226,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 { @@ -264,7 +234,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 { @@ -280,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) } } @@ -307,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 { @@ -331,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) } @@ -345,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) } } @@ -358,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. @@ -388,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 @@ -411,14 +371,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)) } } @@ -427,40 +387,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/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..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 } @@ -82,6 +83,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 +91,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..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,11 +353,12 @@ 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 } - return Pair(prefixedBase, nodeToRelevantMethod[node]!!) + val relevantMethod = nodeToRelevantMethod[node] + if (relevantMethod != null) return Pair(prefixedBase, relevantMethod) } if (base == null) { @@ -392,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 } @@ -406,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 @@ -513,7 +512,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..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 @@ -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 @@ -45,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) @@ -144,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 }) ) } } @@ -193,4 +197,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..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 @@ -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-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/passes/UnreachableEOGPass.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGPass.kt index a48e403760..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 @@ -25,75 +25,201 @@ */ package de.fraunhofer.aisec.cpg.passes -import de.fraunhofer.aisec.cpg.TranslationResult +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 * 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(n: Node) { - when (n) { - is IfStatement -> handleIfStatement(n) - is WhileStatement -> handleWhileStatement(n) - } - - super.visit(n) - } +class UnreachableEOGPass(ctx: TranslationContext) : TranslationUnitPass(ctx) { + override fun cleanup() { + // Nothing to do + } + + override fun accept(tu: TranslationUnitDeclaration) { + 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/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt index f8090e7f60..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 @@ -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) } @@ -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/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/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 232e3c4a1c..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 @@ -26,14 +26,14 @@ 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 -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.Block import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression import de.fraunhofer.aisec.cpg.passes.EdgeCachePass import java.nio.file.Path @@ -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) @@ -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) } @@ -195,7 +199,7 @@ class MultiValueEvaluatorTest { topLevel, true ) { - it.registerPass(EdgeCachePass()) + it.registerPass() } assertNotNull(tu) @@ -207,11 +211,54 @@ class MultiValueEvaluatorTest { assertNotNull(forLoop) val evaluator = MultiValueEvaluator() - val iVar = ((forLoop.statement as CompoundStatement).statements[0] as BinaryOperator).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 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 = newReference("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/SizeEvaluatorTest.kt b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/SizeEvaluatorTest.kt index c7ee6bb57e..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 @@ -31,11 +31,13 @@ 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.statements.CompoundStatement +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.BinaryOperator +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 @@ -105,7 +107,9 @@ class SizeEvaluatorTest { assertNotNull(forLoop) val subscriptExpr = - ((forLoop.statement as CompoundStatement).statements[0] as BinaryOperator).lhs + ((forLoop.statement as Block).statements[0] as AssignExpression).lhs< + SubscriptExpression + >() value = evaluator.evaluate(subscriptExpr) as Int assertEquals(3, value) 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..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 @@ -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 @@ -37,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 { @@ -59,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) @@ -81,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) @@ -97,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) @@ -108,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) @@ -181,272 +209,478 @@ 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")) + // 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.4, parseType("double")) + 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, parseType("long")) - binOp.rhs = newLiteral(2, parseType("int")) + 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.4, parseType("double")) + 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(), parseType("short")) - binOp.rhs = newLiteral(2, parseType("int")) + 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.4, parseType("double")) + 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(), parseType("byte")) - binOp.rhs = newLiteral(2, parseType("int")) + 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.4, parseType("double")) + 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, parseType("double")) - binOp.rhs = newLiteral(2, parseType("int")) + assertThrows { + binOp.rhs = newLiteral(NotReallyANumber(), objectType("fake")) + ValueEvaluator().evaluate(binOp) + } - assertEquals(5.0, ValueEvaluator().evaluate(binOp)) + // Float.plus + binOp.lhs = newLiteral(3.0f, primitiveType("float")) + binOp.rhs = newLiteral(2, primitiveType("int")) + assertEquals(5.0f, 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.4, parseType("double")) + 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, parseType("float")) - binOp.rhs = newLiteral(2, parseType("int")) + 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.4, parseType("double")) + 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)) - binOp.lhs = newLiteral("Hello", parseType("String")) - binOp.rhs = newLiteral(" world", parseType("String")) + 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)) - binOp.rhs = newLiteral(2, parseType("int")) + binOp.rhs = newLiteral(2, primitiveType("int")) assertEquals("Hello2", ValueEvaluator().evaluate(binOp)) } } @Test fun testHandleMinus() { - with(TestHandler()) { + 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)) + assertEquals(1, 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)) + assertEquals(1, 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)) + assertEquals(1, 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)) + assertEquals(1.0f, 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)) } } @Test fun testHandleTimes() { - with(TestHandler()) { + with(TestHandler(TestLanguageFrontend())) { + // Int.times 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(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.4, parseType("double")) + 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, parseType("long")) - binOp.rhs = newLiteral(2, parseType("int")) + 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.4, parseType("double")) + 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(), parseType("short")) - binOp.rhs = newLiteral(2, parseType("int")) + 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.4, parseType("double")) + 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(), parseType("byte")) - binOp.rhs = newLiteral(2, parseType("int")) + 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(2.4, parseType("double")) - assertEquals(3 * 2.4, ValueEvaluator().evaluate(binOp)) + binOp.rhs = newLiteral(2L, primitiveType("byte")) + assertEquals(6L, ValueEvaluator().evaluate(binOp)) - binOp.lhs = newLiteral(3.0, parseType("double")) - binOp.rhs = newLiteral(2, parseType("int")) + 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(3L * 2.4, ValueEvaluator().evaluate(binOp)) + + 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)) + + 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.0f * 2.4, ValueEvaluator().evaluate(binOp)) + + 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(2.4, parseType("double")) - assertEquals(3 * 2.4, ValueEvaluator().evaluate(binOp)) + binOp.rhs = newLiteral(2L, primitiveType("byte")) + assertEquals(6.0, ValueEvaluator().evaluate(binOp)) - binOp.lhs = newLiteral(3.0f, parseType("float")) - binOp.rhs = newLiteral(2, parseType("int")) + 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.4, parseType("double")) - assertEquals(3 * 2.4, 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.0 * 2.4, ValueEvaluator().evaluate(binOp)) - binOp.lhs = newLiteral("Hello", parseType("String")) - binOp.rhs = newLiteral(" world", parseType("String")) + 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)) } } @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")) - 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)) + assertEquals(1, 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)) + assertEquals(1, 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)) + assertEquals(1, 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)) + assertEquals(1.5f, 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)) } } @Test fun testHandleUnary() { - with(TestHandler()) { + 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-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..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 @@ -86,8 +87,8 @@ class ComplexDFAOrderEvaluationTest { true ) { it.registerLanguage() - .registerPass(UnreachableEOGPass()) - .registerPass(EdgeCachePass()) + .registerPass() + .registerPass() } } @@ -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 8b319d8837..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 @@ -73,8 +73,8 @@ class SimpleDFAOrderEvaluationTest { true ) { it.registerLanguage() - .registerPass(UnreachableEOGPass()) - .registerPass(EdgeCachePass()) + .registerPass() + .registerPass() } } @@ -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 78f1371dfc..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 @@ -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 @@ -55,7 +52,7 @@ class UnreachableEOGPassTest { topLevel, true ) { - it.registerLanguage().registerPass(UnreachableEOGPass()) + it.registerLanguage().registerPass() } } @@ -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 block + 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 block + 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-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 7b96cd1855..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 } ) } @@ -466,15 +461,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 @@ -490,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 @@ -499,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) @@ -522,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 @@ -531,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) @@ -548,14 +547,14 @@ class QueryTest { .sourceLocations(File("src/test/resources/query/array2.cpp")) .defaultPasses() .defaultLanguages() - .registerPass(EdgeCachePass()) + .registerPass() .build() val analyzer = TranslationManager.builder().config(config).build() val result = analyzer.analyze().get() val queryTreeResult = - result.all( + result.all( mustSatisfy = { max(it.subscriptExpression) < min(it.arraySize) && min(it.subscriptExpression) >= 0 @@ -564,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) @@ -581,43 +580,35 @@ class QueryTest { .sourceLocations(File("src/test/resources/query/array3.cpp")) .defaultPasses() .defaultLanguages() - .registerPass(EdgeCachePass()) + .registerPass() .build() val analyzer = TranslationManager.builder().config(config).build() 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) } ) @@ -632,14 +623,14 @@ class QueryTest { .sourceLocations(File("src/test/resources/query/array_correct.cpp")) .defaultPasses() .defaultLanguages() - .registerPass(EdgeCachePass()) + .registerPass() .build() val analyzer = TranslationManager.builder().config(config).build() val result = analyzer.analyze().get() val queryTreeResult = - result.all( + result.all( mustSatisfy = { val max_sub = max(it.subscriptExpression) val min_dim = min(it.arraySize) @@ -650,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) @@ -813,7 +804,7 @@ class QueryTest { .defaultPasses() .defaultLanguages() .registerLanguage(JavaLanguage()) - .registerPass(EdgeCachePass()) + .registerPass() .build() val analyzer = TranslationManager.builder().config(config).build() @@ -867,7 +858,7 @@ class QueryTest { .defaultPasses() .defaultLanguages() .registerLanguage(JavaLanguage()) - .registerPass(EdgeCachePass()) + .registerPass() .build() val analyzer = TranslationManager.builder().config(config).build() @@ -921,7 +912,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-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..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 @@ -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) } @@ -61,7 +61,7 @@ class NullPointerCheck { handleHasBase(v) } - fun visit(v: ArraySubscriptionExpression) { + fun visit(v: SubscriptExpression) { handleHasBase(v) } } @@ -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/analysis/OutOfBoundsCheck.kt b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/OutOfBoundsCheck.kt index debfd6c994..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 @@ -52,8 +52,8 @@ class OutOfBoundsCheck { for (tu in result.translationUnits) { tu.accept( Strategy::AST_FORWARD, - object : IVisitor() { - fun visit(v: ArraySubscriptionExpression) { + object : IVisitor() { + 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/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-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..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 @@ -25,14 +25,13 @@ */ 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 -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 @@ -187,20 +186,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 -> { + is Reference -> { // 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)) @@ -214,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)) @@ -244,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/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/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 8b5e2fac85..0000000000 --- a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/TypeManager.java +++ /dev/null @@ -1,744 +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.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.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("\\(?\\*(?[^()]+)\\)?\\(.*\\)"); - @NotNull private static TypeManager instance = new TypeManager(); - 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<>()); - - 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 - * @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) { - return parameterizedType; - } else { - 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)); - } - - private TypeManager() {} - - public static @NotNull TypeManager getInstance() { - return instance; - } - - 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 - && typeObjectType.getGenerics() != null - && newObjectType.getGenerics() != null - && 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.setDepth(depth); - wrapState.setPointerOrigin(pointerOrigins); - wrapState.setReference(reference); - wrapState.setReferenceType(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 provider a {@link ScopeProvider}. - * @return the common type - */ - @NotNull - public Optional getCommonType(@NotNull Collection types, ScopeProvider provider) { - // 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.getDepth(), - wrapState.getPointerOrigins(), - wrapState.isReference(), - wrapState.getReferenceType()); - } - - 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(), a.getRecord().getLanguage())); - - Type finalType; - if (commonType.isPresent()) { - finalType = commonType.get(); - } else { - return commonType; - } - - return rewrapType( - finalType, - wrapState.getDepth(), - wrapState.getPointerOrigins(), - wrapState.isReference(), - wrapState.getReferenceType()); - } - - 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; - - if (superType.getReferenceDepth() != subType.getReferenceDepth()) { - return false; - } - - if (provider instanceof LanguageProvider languageProvider) { - language = languageProvider.getLanguage(); - } - - // arrays and pointers match in C/C++ - // TODO: Make this independent from the specific language - if (language instanceof CLanguage && 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)), scopeProvider); - 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; - } - } - } - - 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) { - if (alias.contains("(") && alias.contains("*")) { - // function pointer - return TypeParser.createFrom(currTarget.getName() + " " + alias, currTarget.getLanguage()); - } 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) { - if (alias.contains("(") && alias.contains("*")) { - // function pointer - Matcher matcher = funPointerPattern.matcher(alias); - if (matcher.find()) { - return TypeParser.createIgnoringAlias(matcher.group("alias"), language); - } else { - log.error("Could not find alias name in function pointer typedef: {}", alias); - return TypeParser.createIgnoringAlias(alias, language); - } - } else { - alias = alias.split("\\[")[0]; - alias = alias.replace("*", ""); - return TypeParser.createIgnoringAlias(alias, language); - } - } - - /** - * 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); - Type alias; - alias = getAlias(cleanedPart, frontend.getLanguage()); - - 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/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 deleted file mode 100644 index a4ee10e4d3..0000000000 --- a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/types/TypeParser.java +++ /dev/null @@ -1,718 +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.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) { - 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)); - } - } - - return parameters; - } - - private static List getGenerics( - String typeName, Language language) { - 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)); - } - } - 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 {@link #createIgnoringAlias(String, Language)} 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 - 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, - @Nullable ScopeManager scopeManager) { - // 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; - TypeManager typeManager = TypeManager.getInstance(); - - // 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); - List parameterList = getParameterList(funcptr.group("args"), language); - - return typeManager.registerType(new FunctionPointerType(parameterList, returnType, language)); - } 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); - 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 && scopeManager != null) { - 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 lang) { - Type templateType = searchForTemplateTypes(type, lang.getScopeManager()); - if (templateType != null) { - return templateType; - } - - Type createdType = createFrom(type, lang.getLanguage(), resolveAlias, lang.getScopeManager()); - - if (createdType instanceof SecondOrderType) { - templateType = - searchForTemplateTypes( - createdType.getRoot().getName().toString(), lang.getScopeManager()); - if (templateType != null) { - createdType.setRoot(templateType); - } - } - - return createdType; - } - - private static Type searchForTemplateTypes(@NotNull String type, ScopeManager scopeManager) { - return TypeManager.getInstance() - .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 scopeManager optional, but required if resolveAlias is true - * @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, - ScopeManager scopeManager) { - try { - return createFromUnsafe(type, resolveAlias, language, scopeManager); - } 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/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/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..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 */ @@ -52,10 +51,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/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/PopulatedByPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/PopulatedByPass.kt index d6121a0f51..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(val value: KClass) +annotation class PopulatedByPass(vararg val value: KClass>) 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..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,17 +30,14 @@ 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 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.Consumer import java.util.function.Predicate import org.slf4j.LoggerFactory @@ -76,7 +73,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 @@ -92,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 @@ -173,7 +170,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 @@ -198,7 +198,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) @@ -214,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. @@ -226,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) @@ -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 */ @@ -458,7 +460,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." ) @@ -466,9 +470,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) } } @@ -491,9 +495,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) } } @@ -512,7 +516,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) { @@ -592,18 +597,16 @@ 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) ) { // 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 @@ -635,13 +638,19 @@ class ScopeManager : ScopeProvider { call: CallExpression, scope: Scope? = currentScope ): List { + val s = extractScope(call, scope) + + return resolve(s) { it.name.lastPartsMatch(call.name) && it.hasSignature(call.signature) } + } + + fun extractScope(node: Node, 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 (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 = call.name.parent + val scopeName = node.name.parent // TODO: proper scope selection @@ -649,26 +658,51 @@ 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 current scope", - scopeName, - call.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" ) - currentScope + s } 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 + internal 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( call: CallExpression ): List { - return resolve(currentScope, true) { f: FunctionDeclaration -> - f.name.lastPartsMatch(call.name) - } + return resolve(currentScope, true) { f -> f.name.lastPartsMatch(call.name) } } /** @@ -739,9 +773,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) } } /** @@ -759,37 +791,4 @@ class ScopeManager : ScopeProvider { /** Returns the current scope for the [ScopeProvider] interface. */ override val scope: Scope? get() = currentScope - - fun activateTypes(node: Node) { - 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 = - TypeManager.getInstance() - .resolvePossibleTypedef(t, this@ScopeManager) - } - typeCache.remove(n 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: HasType, types: List) -> - types.forEach( - Consumer { t: Type? -> - n.type = TypeManager.getInstance().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 dc2d737edb..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 @@ -25,19 +25,17 @@ */ 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 -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 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,21 +91,27 @@ private constructor( * always take priority over those in the whitelist. */ val includeBlocklist: List, - passes: List, - languages: 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, disableCleanup: Boolean, useUnityBuild: Boolean, useParallelFrontends: Boolean, - typeSystemActiveInFrontend: Boolean, inferenceConfiguration: InferenceConfiguration, compilationDatabase: CompilationDatabase?, matchCommentsToNodes: Boolean, 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. @@ -136,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 @@ -162,12 +159,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 @@ -181,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 @@ -202,13 +194,19 @@ 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() - private val languages = mutableListOf>() + private val languages = mutableListOf>() private var topLevel: File? = null private var debugParser = false private var failOnError = false @@ -217,17 +215,20 @@ 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 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 private var addIncludesToGraph = true + private var useDefaultPasses = false + fun symbols(symbols: Map): Builder { this.symbols = symbols return this @@ -366,14 +367,35 @@ 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 } /** 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}" @@ -382,7 +404,7 @@ private constructor( } /** Registers an additional [Language]. */ - inline fun > registerLanguage(): Builder { + inline fun > registerLanguage(): Builder { T::class.primaryConstructor?.call()?.let { registerLanguage(it) } return this } @@ -411,8 +433,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 } @@ -425,7 +447,6 @@ private constructor( * - [VariableUsageResolver] * - [CallResolver] * - [DFGPass] - * - [FunctionPointerCallResolver] * - [EvaluationOrderGraphPass] * - [TypeResolver] * - [ControlFlowSensitiveDFGPass] @@ -434,49 +455,69 @@ 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(FunctionPointerCallResolver()) - registerPass(EvaluationOrderGraphPass()) // creates EOG - registerPass(TypeResolver()) - registerPass(ControlFlowSensitiveDFGPass()) - registerPass(FilenameMapper()) + registerPass() + registerPass() + registerPass() + registerPass() // creates CG + registerPass() + registerPass() // creates EOG + 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() { - for (frontend in languages.map(Language::frontend)) { - val extraPasses = frontend.findAnnotations() + // 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()) { 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 + ) } } } } /** 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 } @@ -528,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 */ @@ -539,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 @@ -558,14 +585,8 @@ 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( symbols, softwareComponents, @@ -577,13 +598,13 @@ private constructor( includeWhitelist, includeBlocklist, orderPasses(), + replacedPasses, languages, codeInNodes, processAnnotations, disableCleanup, useUnityBuild, useParallelFrontends, - typeSystemActiveInFrontend, inferenceConfiguration, compilationDatabase, matchCommentsToNodes, @@ -600,28 +621,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, + softDependencies[p] ?: mutableSetOf(), + hardDependencies[p] ?: mutableSetOf() + ) + ) } } return workingList @@ -645,16 +696,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 @@ -691,7 +742,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 } } @@ -702,6 +753,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/java/de/fraunhofer/aisec/cpg/graph/types/ParameterizedType.java b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationContext.kt similarity index 50% 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/TranslationContext.kt index b9e4363653..c309225e62 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/TranslationContext.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,39 +23,27 @@ * \______/ \__| \______/ * */ -package de.fraunhofer.aisec.cpg.graph.types; - -import de.fraunhofer.aisec.cpg.frontends.Language; -import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend; +package de.fraunhofer.aisec.cpg /** - * ParameterizedTypes describe types, that are passed as Paramters to Classes E.g. uninitialized - * generics in the graph are represented as ParameterizedTypes + * The translation context holds all necessary managers and configurations needed during the + * translation process. */ -public class ParameterizedType extends Type { - - public ParameterizedType(Type type) { - super(type); - this.setLanguage(type.getLanguage()); - } - - public ParameterizedType(String typeName, Language language) { - super(typeName); - this.setLanguage(language); - } - - @Override - public Type reference(PointerType.PointerOrigin pointer) { - return new PointerType(this, pointer); - } +class TranslationContext( + /** The configuration for this translation. */ + val config: TranslationConfiguration, - @Override - public Type dereference() { - return this; - } + /** + * 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, - @Override - public Type duplicate() { - return new ParameterizedType(this); - } -} + /** + * 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 bf36d78f08..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 @@ -29,25 +29,19 @@ 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 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 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 @@ -71,59 +65,51 @@ 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 { - val outerBench = - Benchmark( - TranslationManager::class.java, - "Translation into full graph", - false, - result - ) - val executedPasses = mutableSetOf() - var executedFrontends = setOf() + return CompletableFuture.supplyAsync { analyzeNonAsync() } + } - try { - // Parse Java/C/CPP files - var bench = Benchmark(this.javaClass, "Executing Language Frontend", false, result) - executedFrontends = runFrontends(result, config) - bench.addMeasurement() + private fun analyzeNonAsync(): TranslationResult { + var executedFrontends = setOf>() - // 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) + // Build a new global translation context + val ctx = TranslationContext(config, ScopeManager(), TypeManager()) - executedPasses.forEach { it.cleanup() } + // Build a new translation result + val result = TranslationResult(this, ctx) - log.debug("Cleaning up {} Frontends", executedFrontends.size) + val outerBench = + Benchmark(TranslationManager::class.java, "Translation into full graph", false, result) - executedFrontends.forEach { it.cleanup() } - TypeManager.getInstance().cleanup() + try { + // Parse Java/C/CPP files + var bench = Benchmark(this.javaClass, "Executing Language Frontend", false, result) + executedFrontends = runFrontends(ctx, result) + bench.addMeasurement() + + // Apply passes + for (pass in config.registeredPasses) { + bench = Benchmark(pass.java, "Executing Pass", false, result) + executePassSequential(pass, ctx, result, executedFrontends) + + 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() } + } } - } - val passes: List - get() = config.registeredPasses + return result + } fun isCancelled(): Boolean { return isCancelled.get() @@ -134,38 +120,36 @@ 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, - ): Set { - val usedFrontends = mutableSetOf() - for (sc in this.config.softwareComponents.keys) { + ctx: TranslationContext, + result: TranslationResult + ): Set> { + val usedFrontends = mutableSetOf>() + for (sc in ctx.config.softwareComponents.keys) { val component = Component() component.name = Name(sc) result.addComponent(component) - var sourceLocations: List = this.config.softwareComponents[sc]!! + var sourceLocations: List = ctx.config.softwareComponents[sc] ?: listOf() - var useParallelFrontends = config.useParallelFrontends + var useParallelFrontends = ctx.config.useParallelFrontends 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 = @@ -186,15 +170,16 @@ 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() + 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( """ #include "${topLevel.relativize(it.toPath())}" @@ -216,39 +201,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) - usedFrontends.addAll( if (useParallelFrontends) { - parseParallel(component, result, sourceLocations) + parseParallel(component, result, ctx, sourceLocations) } else { - parseSequentially(component, result, sourceLocations) + parseSequentially(component, result, ctx, sourceLocations) } ) - - if (!config.typeSystemActiveInFrontend) { - TypeManager.setTypeSystemActive(true) - - result.components.forEach { s -> - s.translationUnits.forEach { - val bench = - Benchmark(this.javaClass, "Activating types for ${it.name}", true) - result.scopeManager.activateTypes(it) - bench.stop() - } - } - } } return usedFrontends @@ -257,25 +227,29 @@ private constructor( private fun parseParallel( component: Component, result: TranslationResult, + globalCtx: TranslationContext, sourceLocations: Collection - ): Set { - val usedFrontends = mutableSetOf() + ): Set> { + val usedFrontends = mutableSetOf>() log.info("Parallel parsing started") - val futures = mutableListOf>>() - val parallelScopeManagers = mutableListOf() + val futures = mutableListOf?>>() + val parallelContexts = mutableListOf() - val futureToFile: MutableMap>, File> = + 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) } @@ -287,7 +261,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) { @@ -295,12 +270,17 @@ 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() } } // 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") @@ -311,14 +291,14 @@ private constructor( private fun parseSequentially( component: Component, 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, result.scopeManager, sourceLocation).ifPresent { f: LanguageFrontend -> + val f = parse(component, ctx, sourceLocation) + if (f != null) { handleCompletion(result, usedFrontends, sourceLocation, f) } } @@ -328,9 +308,9 @@ private constructor( private fun handleCompletion( result: TranslationResult, - usedFrontends: MutableSet, + usedFrontends: MutableSet>, sourceLocation: File?, - f: LanguageFrontend + f: LanguageFrontend<*, *> ) { usedFrontends.add(f) @@ -339,18 +319,20 @@ 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) private fun parse( component: Component, - scopeManager: ScopeManager, - sourceLocation: File - ): Optional { - var frontend: LanguageFrontend? = null + ctx: TranslationContext, + sourceLocation: File, + ): LanguageFrontend<*, *>? { + log.info("Parsing {}", sourceLocation.absolutePath) + + 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,7 +342,7 @@ private constructor( "Found no parser frontend for ${sourceLocation.name}" ) } - return Optional.empty() + return null } component.translationUnits.add(frontend.parse(sourceLocation)) } catch (ex: TranslationException) { @@ -369,22 +351,19 @@ private constructor( throw ex } } - return Optional.ofNullable(frontend) + return 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 c98b0a86e7..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,30 @@ */ 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 -) : Node(), StatisticsHolder { + var finalCtx: TranslationContext, +) : Node(), StatisticsHolder, PassTarget { /** * Entry points to the CPG: "SoftwareComponent" refer to programs, application, other "bundles" @@ -68,7 +69,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 @@ -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) { @@ -128,7 +130,7 @@ class TranslationResult( components.add(swc) } } - swc.translationUnits.add(tu!!) + tu?.let { swc.translationUnits.add(it) } } /** @@ -148,21 +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/TypeManager.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TypeManager.kt new file mode 100644 index 0000000000..72bc8aa510 --- /dev/null +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TypeManager.kt @@ -0,0 +1,422 @@ +/* + * 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.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 + * ParameterizedTypes are unique to the RecordDeclaration and are not merged. + */ + private val recordToTypeParameters = + Collections.synchronizedMap(mutableMapOf>()) + private val templateToTypeParameters = + Collections.synchronizedMap( + mutableMapOf>() + ) + + val firstOrderTypes: MutableSet = ConcurrentHashMap.newKeySet() + val secondOrderTypes: MutableSet = ConcurrentHashMap.newKeySet() + + /** + * @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 + */ + private 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 + } + + 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) { + // 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 + } + + fun typeExists(name: String): Boolean { + return firstOrderTypes.stream().anyMatch { type: Type -> type.root.name.toString() == name } + } + + /** + * 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 { + val typedef = frontend.newTypedefDeclaration(target, alias, 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 applicable ?: alias + } +} + +val Type.ancestors: Set + get() { + return this.getAncestors(0) + } + +internal fun Type.getAncestors(depth: Int): Set { + val types = mutableSetOf() + + // 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 + } + + 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 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]) + } + } + + if (wrapState.isReference) { + wrapState.referenceType?.elementType = type + return wrapState.referenceType!! + } + + return type +} 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= 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/Handler.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/Handler.kt index 2a6d582284..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 @@ -26,13 +26,10 @@ package de.fraunhofer.aisec.cpg.frontends 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 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,37 @@ 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 { - 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. + * 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 + * 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 +82,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 +91,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 +109,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 +131,7 @@ abstract class Handler( // In case the node is empty, we report a problem if (ret == null) { - errorWithFileLocation( + errorWithFileLocation( frontend, ctx, log, @@ -140,7 +139,12 @@ abstract class Handler( ) ret = configConstructor.get() } - frontend.process(ctx, ret) + + if (ret != null) { + frontend.process(ctx, ret) + lastNode = ret + } + return ret } @@ -167,19 +171,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 - 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 db8983b943..f522474f52 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,15 +30,16 @@ 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.Name import de.fraunhofer.aisec.cpg.graph.Node 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 de.fraunhofer.aisec.cpg.graph.unknownType 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 @@ -48,7 +49,7 @@ import kotlin.reflect.KClass * 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 @@ -75,11 +76,17 @@ abstract class Language : Node() { open val arithmeticOperations: Set get() = setOf("+", "-", "*", "/", "%", "<<", ">>") - /** Creates a new [LanguageFrontend] object to parse the language. */ - abstract fun newFrontend( - config: TranslationConfiguration, - scopeManager: ScopeManager = ScopeManager(), - ): T + /** 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. 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 @@ -108,30 +115,25 @@ 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 { - 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 { - UnknownType.getUnknownType(this) + 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 -> unknownType() } } @@ -144,44 +146,59 @@ 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) + ?: unknownType() } 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.getUnknownType(this) + unknownType() } - else -> UnknownType.getUnknownType(this) // We don't know what is this thing + 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/frontends/LanguageFrontend.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/LanguageFrontend.kt index cca4f62d10..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,12 +25,11 @@ */ package de.fraunhofer.aisec.cpg.frontends -import de.fraunhofer.aisec.cpg.ScopeManager -import de.fraunhofer.aisec.cpg.TranslationConfiguration -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 +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 @@ -46,24 +45,28 @@ 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( - override val language: Language, - val config: TranslationConfiguration, - scopeManager: ScopeManager, +abstract class LanguageFrontend( + /** The language this frontend works for. */ + override val language: Language>, + + /** + * 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, + 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 } @@ -72,8 +75,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)) } } @@ -86,10 +89,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. * @@ -97,7 +112,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 @@ -107,20 +122,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) } /** @@ -219,7 +233,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/frontends/LanguageTraits.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/LanguageTraits.kt index c1d962c284..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 @@ -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 } @@ -202,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/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/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/frontends/cpp/CLanguage.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CLanguage.kt deleted file mode 100644 index 5bf4022de7..0000000000 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/CLanguage.kt +++ /dev/null @@ -1,89 +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.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.frontends.* -import de.fraunhofer.aisec.cpg.graph.types.* -import kotlin.reflect.KClass -import org.neo4j.ogm.annotation.Transient - -/** The C language. */ -open class CLanguage : - Language(), - HasStructs, - HasFunctionPointers, - HasQualifier, - HasElaboratedTypeSpecifier, - HasShortCircuitOperators { - override val fileExtensions = listOf("c", "h") - override val namespaceDelimiter = "::" - @Transient override val frontend: KClass = CXXLanguageFrontend::class - override val qualifiers = listOf("const", "volatile", "restrict", "atomic") - override val elaboratedTypeSpecifier = listOf("struct", "union", "enum") - override val conjunctiveOperators = listOf("&&") - override val disjunctiveOperators = listOf("||") - - @Transient - @JsonIgnore - override val builtInTypes: Map = - mapOf( - "boolean" to IntegerType("boolean", 1, this, NumericType.Modifier.SIGNED), - "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), - "int" to IntegerType("int", 32, this, NumericType.Modifier.SIGNED), - "long" to IntegerType("long", 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 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), - "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 int" to - IntegerType("unsigned long long int", 64, this, NumericType.Modifier.UNSIGNED) - ) - - override fun newFrontend( - config: TranslationConfiguration, - scopeManager: ScopeManager, - ): CXXLanguageFrontend { - return CXXLanguageFrontend(this, config, scopeManager) - } -} 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/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/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/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/DeclarationBuilder.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/DeclarationBuilder.kt index 6fc62696c6..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,10 +30,9 @@ 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.graph.types.UnknownType import de.fraunhofer.aisec.cpg.sarif.PhysicalLocation /** @@ -66,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 @@ -129,14 +129,14 @@ fun MetadataProvider.newConstructorDeclaration( * argument. */ @JvmOverloads -fun MetadataProvider.newParamVariableDeclaration( +fun MetadataProvider.newParameterDeclaration( name: CharSequence?, - type: Type = UnknownType.getUnknownType(), + 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 @@ -155,7 +155,7 @@ fun MetadataProvider.newParamVariableDeclaration( @JvmOverloads fun MetadataProvider.newVariableDeclaration( name: CharSequence?, - type: Type = UnknownType.getUnknownType(), + type: Type = unknownType(), code: String? = null, implicitInitializerAllowed: Boolean = false, rawNode: Any? = null @@ -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 @@ -194,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) @@ -276,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) @@ -325,7 +353,7 @@ fun MetadataProvider.newEnumConstantDeclaration( @JvmOverloads fun MetadataProvider.newFieldDeclaration( name: CharSequence?, - type: Type = UnknownType.getUnknownType(), + type: Type = unknownType(), modifiers: List? = listOf(), code: String? = null, location: PhysicalLocation? = null, @@ -341,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 @@ -414,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/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/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..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 @@ -26,12 +26,14 @@ 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 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 -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 +44,7 @@ import de.fraunhofer.aisec.cpg.graph.types.UnknownType @JvmOverloads fun MetadataProvider.newLiteral( value: T, - type: Type = UnknownType.getUnknownType(), + type: Type = unknownType(), code: String? = null, rawNode: Any? = null, ): Literal { @@ -57,10 +59,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 +72,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 @@ -86,16 +100,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 +152,7 @@ fun MetadataProvider.newAssignExpression( @JvmOverloads fun MetadataProvider.newNewExpression( code: String? = null, - type: Type = UnknownType.getUnknownType(), + type: Type = unknownType(), rawNode: Any? = null ): NewExpression { val node = NewExpression() @@ -178,19 +192,19 @@ fun MetadataProvider.newConstructExpression( @JvmOverloads fun MetadataProvider.newConditionalExpression( condition: Expression, - thenExpr: Expression?, - elseExpr: Expression?, - type: Type = UnknownType.getUnknownType(), + thenExpression: Expression?, + elseExpression: Expression?, + type: Type = unknownType(), code: String? = null, rawNode: Any? = null ): ConditionalExpression { val node = ConditionalExpression() node.applyMetadata(this, EMPTY_NAME, rawNode, code, true) - node.condition = condition - node.thenExpr = thenExpr - node.elseExpr = elseExpr node.type = type + node.condition = condition + node.thenExpression = thenExpression + node.elseExpression = elseExpression log(node) return node @@ -238,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) @@ -286,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 @@ -338,7 +348,7 @@ fun MetadataProvider.newMemberCallExpression( fun MetadataProvider.newMemberExpression( name: CharSequence?, base: Expression, - memberType: Type = UnknownType.getUnknownType(), + memberType: Type = unknownType(), operatorCode: String? = ".", code: String? = null, rawNode: Any? = null @@ -378,8 +388,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 = unknownType(), + referencedType: Type = unknownType(), code: String? = null, rawNode: Any? = null ): TypeIdExpression { @@ -395,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) @@ -413,63 +423,63 @@ fun MetadataProvider.newArraySubscriptionExpression( } /** - * 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 - * requires an appropriate [MetadataProvider], such as a [LanguageFrontend] as an additional - * prepended argument. + * 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.newArrayCreationExpression( +fun MetadataProvider.newRangeExpression( + floor: Expression? = null, + ceiling: Expression? = null, code: String? = null, rawNode: Any? = null -): ArrayCreationExpression { - val node = ArrayCreationExpression() +): 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 [DeclaredReferenceExpression]. 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.newDeclaredReferenceExpression( - name: CharSequence?, - type: Type = UnknownType.getUnknownType(), +fun MetadataProvider.newNewArrayExpression( code: String? = null, rawNode: Any? = null -): DeclaredReferenceExpression { - val node = DeclaredReferenceExpression() - node.applyMetadata(this, name, rawNode, code, true) - - node.type = type +): NewArrayExpression { + val node = NewArrayExpression() + node.applyMetadata(this, EMPTY_NAME, rawNode, code, true) log(node) 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. + * 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.newArrayRangeExpression( - floor: Expression?, - ceil: Expression?, +fun MetadataProvider.newReference( + name: CharSequence?, + type: Type = unknownType(), code: String? = null, rawNode: Any? = null -): ArrayRangeExpression { - val node = ArrayRangeExpression() - node.applyMetadata(this, EMPTY_NAME, rawNode, code, true) +): Reference { + val node = Reference() + node.applyMetadata(this, name, rawNode, code, true) - node.floor = floor - node.ceiling = ceil + node.type = type log(node) return node @@ -517,12 +527,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 } @@ -554,7 +567,7 @@ fun MetadataProvider.newDesignatedInitializerExpression( @JvmOverloads fun MetadataProvider.newTypeExpression( name: CharSequence?, - type: Type = UnknownType.getUnknownType(), + type: Type = unknownType(), rawNode: Any? = null ): TypeExpression { val node = TypeExpression() @@ -586,15 +599,24 @@ 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 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 @@ -610,6 +632,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/Extensions.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt index 8262adf455..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 @@ -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 } @@ -514,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]. */ @@ -538,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]. */ @@ -558,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 @@ -571,6 +569,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 } @@ -578,7 +581,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 } @@ -598,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 @@ -623,9 +626,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) } @@ -637,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/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/HasType.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/HasType.kt deleted file mode 100644 index bb9aca0955..0000000000 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/HasType.kt +++ /dev/null @@ -1,94 +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 de.fraunhofer.aisec.cpg.graph.types.Type - -interface HasType { - 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 - - /** - * Side-effect free type modification WARNING: This should only be used by the TypeSystem Pass - * - * @param type new type - */ - fun updateType(type: Type) - fun updatePossibleSubtypes(types: List) - - /** - * Set the node's type. This may start a chain of type listener notifications - * - * @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. - */ - fun setType(type: Type, root: MutableList?) - var possibleSubTypes: List - - /** - * 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. - */ - fun setPossibleSubTypes(possibleSubTypes: List, root: MutableList) - fun registerTypeListener(listener: TypeListener) - fun unregisterTypeListener(listener: TypeListener) - val typeListeners: Set - 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 - */ - fun resetTypes(type: Type) - interface TypeListener { - fun typeChanged(src: HasType, root: MutableList, oldType: Type) - fun possibleSubTypesChanged(src: HasType, root: MutableList) - } - - /** - * 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 - */ - interface SecondaryTypeEdge { - fun updateType(typeState: Collection) - } -} 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 88% 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 index 47e55df0d0..ad44450b1c 100644 --- 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 @@ -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/Name.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Name.kt index cb8aa9717d..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 @@ -46,7 +46,7 @@ class Name( constructor( localName: String, parent: Name? = null, - language: Language? + language: Language<*>? ) : this(localName, parent, language?.namespaceDelimiter ?: ".") /** @@ -131,14 +131,16 @@ 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 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 4dbfb79181..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 @@ -26,36 +26,44 @@ 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 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.* 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.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.passes.* import de.fraunhofer.aisec.cpg.processing.IVisitable 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. @@ -74,7 +82,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 @@ -99,18 +107,42 @@ 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. */ @Relationship(value = "EOG", direction = Relationship.Direction.INCOMING) + @PopulatedByPass(EvaluationOrderGraphPass::class) var prevEOGEdges: MutableList> = ArrayList() protected set - /** outgoing control flow edges. */ + /** Outgoing control flow edges. */ @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. @@ -126,31 +158,50 @@ open class Node : IVisitable, Persistable, LanguageProvider, ScopeProvider get() = SubgraphWalker.getAstChildren(this) /** Virtual property for accessing [prevEOGEdges] without property edges. */ - 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 - } + @PopulatedByPass(EvaluationOrderGraphPass::class) + var prevEOG: List by PropertyEdgeDelegate(Node::prevEOGEdges, false) /** Virtual property for accessing [nextEOGEdges] without property edges. */ - var nextEOG: List - get() = unwrap(nextEOGEdges) - set(value) { - this.nextEOGEdges = PropertyEdge.transformIntoOutgoingPropertyEdgeList(value, this) - } + @PopulatedByPass(EvaluationOrderGraphPass::class) + var nextEOG: List by PropertyEdgeDelegate(Node::nextEOGEdges) + /** Incoming data flow edges */ @Relationship(value = "DFG", direction = Relationship.Direction.INCOMING) - var prevDFG: MutableSet = HashSet() + @PopulatedByPass(DFGPass::class, ControlFlowSensitiveDFGPass::class) + var prevDFGEdges: MutableList> = mutableListOf() + protected set + + /** Virtual property for accessing [prevDFGEdges] without property edges. */ + @PopulatedByPass(DFGPass::class, ControlFlowSensitiveDFGPass::class) + 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() + protected set - @Relationship(value = "DFG") var nextDFG: MutableSet = HashSet() + /** Virtual property for accessing [nextDFGEdges] without property edges. */ + @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() @@ -201,32 +252,72 @@ 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) + open 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) { + 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) } + fun addAllPrevDFG( + prev: Collection, + properties: MutableMap = EnumMap(Properties::class.java) + ) { + 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) { - 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) } } @@ -260,15 +351,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 = @@ -335,7 +430,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/NodeBuilder.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/NodeBuilder.kt index 6acf60581e..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 @@ -25,19 +25,20 @@ */ 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.TypeParser +import de.fraunhofer.aisec.cpg.graph.types.* import de.fraunhofer.aisec.cpg.passes.inference.IsInferredProvider 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) } } @@ -53,15 +54,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) } /** @@ -100,8 +101,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) { @@ -116,6 +117,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) { @@ -206,15 +217,11 @@ fun MetadataProvider.newAnnotationMember( return node } -/** - * 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) = - TypeParser.createFrom(name, resolveAlias, language) - /** 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/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/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/StatementHolder.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/StatementHolder.kt index d2a8a2b0ff..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) } /** @@ -66,6 +71,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/TypeBuilder.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/TypeBuilder.kt index e672552104..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,8 +25,137 @@ */ package de.fraunhofer.aisec.cpg.graph -import de.fraunhofer.aisec.cpg.graph.types.ObjectType +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 LanguageProvider.autoType(): Type { + return AutoType(this.language) +} + +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 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 { + // 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" + ) + + 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) + } +} + +/** + * 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 427491f402..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 @@ -25,29 +25,31 @@ */ 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.* 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( - config: TranslationConfiguration, +fun LanguageFrontend<*, *>.translationResult( init: TranslationResult.() -> Unit ): TranslationResult { - val node = TranslationResult(TranslationManager.builder().config(config).build(), scopeManager) + val node = + TranslationResult( + TranslationManager.builder().config(ctx.config).build(), + ctx, + ) val component = Component() node.addComponent(component) init(node) - config.registeredPasses.forEach { it.accept(node) } + ctx.config.registeredPasses.forEach { executePassSequential(it, ctx, node, listOf()) } return node } @@ -59,7 +61,7 @@ fun LanguageFrontend.translationResult( */ context(TranslationResult) -fun LanguageFrontend.translationUnit( +fun LanguageFrontend<*, *>.translationUnit( name: CharSequence = Node.EMPTY_NAME, init: TranslationUnitDeclaration.() -> Unit ): TranslationUnitDeclaration { @@ -79,7 +81,7 @@ fun LanguageFrontend.translationUnit( */ context(DeclarationHolder) -fun LanguageFrontend.namespace( +fun LanguageFrontend<*, *>.namespace( name: CharSequence, init: NamespaceDeclaration.() -> Unit ): NamespaceDeclaration { @@ -99,7 +101,7 @@ fun LanguageFrontend.namespace( */ context(DeclarationHolder) -fun LanguageFrontend.record( +fun LanguageFrontend<*, *>.record( name: CharSequence, kind: String = "class", init: RecordDeclaration.() -> Unit @@ -121,9 +123,9 @@ fun LanguageFrontend.record( */ context(DeclarationHolder) -fun LanguageFrontend.field( +fun LanguageFrontend<*, *>.field( name: CharSequence, - type: Type = UnknownType.getUnknownType(), + type: Type = unknownType(), init: FieldDeclaration.() -> Unit ): FieldDeclaration { val node = newFieldDeclaration(name) @@ -143,9 +145,9 @@ fun LanguageFrontend.field( */ context(DeclarationHolder) -fun LanguageFrontend.function( +fun LanguageFrontend<*, *>.function( name: CharSequence, - returnType: Type = UnknownType.getUnknownType(), + returnType: Type = unknownType(), returnTypes: List? = null, init: (FunctionDeclaration.() -> Unit)? = null ): FunctionDeclaration { @@ -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) @@ -173,16 +178,19 @@ fun LanguageFrontend.function( */ context(RecordDeclaration) -fun LanguageFrontend.method( +fun LanguageFrontend<*, *>.method( name: CharSequence, - returnType: Type = UnknownType.getUnknownType(), - init: MethodDeclaration.() -> Unit + returnType: Type = unknownType(), + 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) @@ -198,32 +206,32 @@ fun LanguageFrontend.method( */ context(RecordDeclaration) -fun LanguageFrontend.constructor(init: ConstructorDeclaration.() -> Unit): ConstructorDeclaration { - val recordDecl: RecordDeclaration = this@RecordDeclaration - val node = newConstructorDeclaration(recordDecl.name, recordDeclaration = recordDecl) +fun LanguageFrontend<*, *>.constructor( + init: ConstructorDeclaration.() -> Unit +): ConstructorDeclaration { + 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 @@ -232,19 +240,19 @@ 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. */ context(FunctionDeclaration) -fun LanguageFrontend.param( +fun LanguageFrontend<*, *>.param( name: CharSequence, - type: Type = UnknownType.getUnknownType(), - init: (ParamVariableDeclaration.() -> Unit)? = null -): ParamVariableDeclaration { + type: Type = unknownType(), + init: (ParameterDeclaration.() -> Unit)? = null +): ParameterDeclaration { val node = - (this@LanguageFrontend).newParamVariableDeclaration( + (this@LanguageFrontend).newParameterDeclaration( name, type, ) @@ -262,7 +270,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) @@ -271,6 +279,26 @@ fun LanguageFrontend.returnStmt(init: ReturnStatement.() -> Unit): ReturnStateme return node } +context(Holder) + +fun LanguageFrontend<*, *>.ase( + init: (SubscriptExpression.() -> Unit)? = null +): SubscriptExpression { + val node = newSubscriptExpression() + + 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 @@ -278,7 +306,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) @@ -294,9 +322,9 @@ fun LanguageFrontend.declare(init: DeclarationStatement.() -> Unit): Declaration */ context(DeclarationStatement) -fun LanguageFrontend.variable( +fun LanguageFrontend<*, *>.variable( name: String, - type: Type = UnknownType.getUnknownType(), + type: Type = unknownType(), init: (VariableDeclaration.() -> Unit)? = null ): VariableDeclaration { val node = newVariableDeclaration(name, type) @@ -317,19 +345,18 @@ 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) -fun LanguageFrontend.call( +fun LanguageFrontend<*, *>.call( name: CharSequence, isStatic: Boolean = false, 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( @@ -337,7 +364,7 @@ fun LanguageFrontend.call( isStatic ) } else { - newCallExpression(newDeclaredReferenceExpression(parsedName)) + newCallExpression(newReference(parsedName)) } if (init != null) { init(node) @@ -360,13 +387,12 @@ 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) -fun LanguageFrontend.memberCall( +fun LanguageFrontend<*, *>.memberCall( localName: CharSequence, member: Expression, isStatic: Boolean = false, @@ -397,7 +423,7 @@ fun LanguageFrontend.memberCall( */ context(Holder) -fun LanguageFrontend.construct( +fun LanguageFrontend<*, *>.construct( name: CharSequence, init: (ConstructExpression.() -> Unit)? = null ): ConstructExpression { @@ -420,7 +446,26 @@ fun LanguageFrontend.construct( context(Holder) -fun LanguageFrontend.new(init: (NewExpression.() -> Unit)? = null): NewExpression { +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) @@ -433,17 +478,16 @@ 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 = unknownType()): Expression { val node = if (name.parent != null) { newMemberExpression(name.localName, memberOrRef(name.parent)) } else { - newDeclaredReferenceExpression(name.localName, parseType(name.localName)) + newReference(name.localName) } - node.type = type + if (type !is UnknownType) { + node.type = type + } return node } @@ -455,7 +499,7 @@ fun LanguageFrontend.memberOrRef( */ context(StatementHolder) -fun LanguageFrontend.ifStmt(init: IfStatement.() -> Unit): IfStatement { +fun LanguageFrontend<*, *>.ifStmt(init: IfStatement.() -> Unit): IfStatement { val node = newIfStatement() init(node) @@ -464,6 +508,65 @@ 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 + * 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,22 +574,32 @@ fun LanguageFrontend.ifStmt(init: IfStatement.() -> Unit): IfStatement { */ context(IfStatement) -fun LanguageFrontend.condition(init: IfStatement.() -> BinaryOperator): BinaryOperator { +fun LanguageFrontend<*, *>.condition(init: IfStatement.() -> BinaryOperator): BinaryOperator { return init(this@IfStatement) } /** - * 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. + * 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 [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 @@ -501,7 +614,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) @@ -510,18 +623,61 @@ fun LanguageFrontend.elseIf(init: IfStatement.() -> Unit): IfStatement { return node } +// TODO: Merge the bodies together + +/** + * 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: Block.() -> Unit): Block { + val node = newBlock() + init(node) + statement = node + + return node +} +/** + * 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: 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 - * [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 [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: Block.() -> Unit): Block { + val node = newBlock() + init(node) + statement = node + + return node +} + +/** + * 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 @@ -529,13 +685,109 @@ 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(caseExpression: Expression? = null): CaseStatement { + val node = newCaseStatement() + node.caseExpression = caseExpression + + // 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]. */ context(Holder) -fun LanguageFrontend.literal(value: N, type: Type = UnknownType.getUnknownType()): 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 @@ -548,18 +800,43 @@ fun LanguageFrontend.literal(value: N, type: Type = UnknownType.getUnknownTy } /** - * Creates a new [DeclaredReferenceExpression] in the Fluent Node DSL and invokes + * 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.ref( +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 [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.getUnknownType(), - init: (DeclaredReferenceExpression.() -> Unit)? = null -): DeclaredReferenceExpression { - val node = newDeclaredReferenceExpression(name) + type: Type = unknownType(), + init: (Reference.() -> Unit)? = null +): Reference { + val node = newReference(name) node.type = type if (init != null) { @@ -582,22 +859,26 @@ 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) { - UnknownType.getUnknownType() + 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) } ?: UnknownType.getUnknownType() + val scopeType = scope?.name?.let { t(it) } ?: unknownType() scopeType } 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 @@ -608,11 +889,33 @@ 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]. */ -context(LanguageFrontend, ArgumentHolder) +context(LanguageFrontend<*, *>, ArgumentHolder) operator fun Expression.plus(rhs: Expression): BinaryOperator { val node = (this@LanguageFrontend).newBinaryOperator("+") @@ -630,11 +933,45 @@ 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) { + val node = (this@LanguageFrontend).newAssignExpression("+=", listOf(this), listOf(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]. */ -context(LanguageFrontend, ArgumentHolder) +context(LanguageFrontend<*, *>, ArgumentHolder) operator fun Expression.minus(rhs: Expression): BinaryOperator { val node = (this@LanguageFrontend).newBinaryOperator("-") @@ -646,11 +983,60 @@ 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]. */ -context(LanguageFrontend, ArgumentHolder) +context(LanguageFrontend<*, *>, ArgumentHolder) infix fun Expression.eq(rhs: Expression): BinaryOperator { val node = (this@LanguageFrontend).newBinaryOperator("==") @@ -666,7 +1052,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(">") @@ -679,50 +1065,117 @@ 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 [StatementHolder]. + * Creates a new [BinaryOperator] with a `<` [BinaryOperator.operatorCode] in the Fluent Node DSL + * and invokes [ArgumentHolder.addArgument] of the nearest enclosing [ArgumentHolder]. */ -context(LanguageFrontend, StatementHolder) +context(LanguageFrontend<*, *>, ArgumentHolder) -infix fun Expression.assign(init: BinaryOperator.() -> Expression): BinaryOperator { - val node = (this@LanguageFrontend).newBinaryOperator("=") +infix fun Expression.lt(rhs: Expression): BinaryOperator { + val node = (this@LanguageFrontend).newBinaryOperator("<") node.lhs = this - node.rhs = init(node) + node.rhs = rhs - (this@StatementHolder) += node + (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<*, *>, Holder) + +fun Expression.conditional( + condition: Expression, + thenExpression: Expression, + elseExpression: Expression +): ConditionalExpression { + val node = + (this@LanguageFrontend).newConditionalExpression(condition, thenExpression, elseExpression) + + if (this@Holder is StatementHolder) { + (this@Holder) += node + } else if (this@Holder is ArgumentHolder) { + 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 [StatementHolder]. + * and invokes [StatementHolder.addStatement] of the nearest enclosing [StatementHolder]. */ -context(LanguageFrontend, StatementHolder) +context(LanguageFrontend<*, *>, StatementHolder) -infix fun Expression.assign(rhs: Expression): BinaryOperator { - val node = (this@LanguageFrontend).newBinaryOperator("=") - node.lhs = this - node.rhs = rhs +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 return node } -/** Creates a new [Type] with the given [name] in the Fluent Node DSL. */ -fun LanguageFrontend.t(name: CharSequence): Type { - return parseType(name) +/** + * 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): AssignExpression { + val node = (this@LanguageFrontend).newAssignExpression("=", listOf(this), listOf(rhs)) + + if (this@Holder is StatementHolder) { + this@Holder += node + } + + 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, generics: List = listOf()) = + objectType(name, generics) /** * 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/declarations/ConstructorDeclaration.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/ConstructorDeclaration.kt index 138c3df43e..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.types.TypeParser - /** * 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(TypeParser.createFrom(recordDeclaration.name, language)) + returnTypes = listOf(recordDeclaration.toType()) } } } 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/graph/declarations/FieldDeclaration.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/FieldDeclaration.kt index f657ed9ec8..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,13 +25,7 @@ */ 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.statements.expressions.Expression -import de.fraunhofer.aisec.cpg.graph.statements.expressions.InitializerListExpression -import de.fraunhofer.aisec.cpg.graph.types.Type import java.util.* import org.apache.commons.lang3.builder.ToStringBuilder import org.neo4j.ogm.annotation.Relationship @@ -40,26 +34,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 @@ -74,53 +49,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 (!TypeManager.isTypeSystemActive()) { - return - } - if (!TypeManager.getInstance().isUnknown(type) && 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 (!TypeManager.getInstance().isUnknown(type)) { - 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 (!TypeManager.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()) @@ -136,9 +66,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 445bb91c78..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 @@ -25,18 +25,16 @@ */ 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.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.graph.types.UnknownType +import de.fraunhofer.aisec.cpg.isDerivedFrom import java.util.* import java.util.stream.Collectors import org.apache.commons.lang3.builder.ToStringBuilder @@ -44,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 /** @@ -56,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) @@ -113,11 +111,11 @@ open class FunctionDeclaration : ValueDeclaration(), DeclarationHolder { targetFunctionDeclaration.signatureTypes == signatureTypes } - fun hasSignature(targetSignature: List): Boolean { + fun hasSignature(targetSignature: List): Boolean { val signature = parameters .stream() - .sorted(Comparator.comparingInt(ParamVariableDeclaration::argumentIndex)) + .sorted(Comparator.comparingInt(ParameterDeclaration::argumentIndex)) .collect(Collectors.toList()) return if (targetSignature.size < signature.size) { false @@ -133,7 +131,7 @@ open class FunctionDeclaration : ValueDeclaration(), DeclarationHolder { return true } val provided = targetSignature[i] - if (!TypeManager.getInstance().isSupertypeOf(declared.type, provided, this)) { + if (!provided.isDerivedFrom(declared.type)) { return false } } @@ -174,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 @@ -198,7 +196,7 @@ open class FunctionDeclaration : ValueDeclaration(), DeclarationHolder { if (paramVariableDeclaration.default != null) { signature.add(paramVariableDeclaration.type) } else { - signature.add(UnknownType.getUnknownType(language)) + signature.add(unknownType()) } } return signature @@ -207,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 { @@ -235,15 +233,13 @@ 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) { + 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/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/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/RecordDeclaration.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/RecordDeclaration.kt index 1755a0e079..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 @@ -25,18 +25,13 @@ */ 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.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 import org.neo4j.ogm.annotation.Relationship import org.neo4j.ogm.annotation.Transient @@ -90,7 +85,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() @@ -118,6 +113,7 @@ class RecordDeclaration : Declaration(), DeclarationHolder, StatementHolder { fun removeMethod(methodDeclaration: MethodDeclaration?) { methodEdges.removeIf { it.end == methodDeclaration } } + fun addConstructor(constructorDeclaration: ConstructorDeclaration) { addIfNotContains(constructorEdges, constructorDeclaration) } @@ -129,6 +125,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 +140,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 @@ -150,10 +148,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,22 +172,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? @@ -213,13 +208,14 @@ class RecordDeclaration : Declaration(), DeclarationHolder, StatementHolder { * @return the type */ fun toType(): Type { - val type = TypeParser.createFrom(name, language) + 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 // 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/ClassTemplateDeclaration.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/RecordTemplateDeclaration.kt similarity index 85% 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 437ac531de..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,18 +59,18 @@ 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) } } - 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 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/TranslationUnitDeclaration.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/TranslationUnitDeclaration.kt index 42e7f79852..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,18 +27,19 @@ 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 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 @@ -62,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/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/TypeParamDeclaration.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/TypeParameterDeclaration.kt similarity index 68% 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 a184b6d1a6..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 @@ -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.HasType.SecondaryTypeEdge 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 { - /** - * 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 [HasType.SecondaryTypeEdge] to be aware of the edge to - * be able to merge the type nodes. - */ +class TypeParameterDeclaration : ValueDeclaration(), HasDefault { + /** TemplateParameters can define a default for the type parameter. */ @Relationship(value = "DEFAULT", direction = Relationship.Direction.OUTGOING) @AST override var default: Type? = null @@ -47,20 +41,9 @@ class TypeParamDeclaration : ValueDeclaration(), SecondaryTypeEdge, HasDefault) { - 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/declarations/TypedefDeclaration.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/TypedefDeclaration.kt index 1d0452ba2e..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 @@ -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 @@ -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/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 0f9199cf96..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 @@ -25,84 +25,70 @@ */ package de.fraunhofer.aisec.cpg.graph.declarations -import de.fraunhofer.aisec.cpg.graph.HasType -import de.fraunhofer.aisec.cpg.graph.TypeManager +import de.fraunhofer.aisec.cpg.PopulatedByPass +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.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.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 import java.util.stream.Collectors import org.apache.commons.lang3.builder.ToStringBuilder import org.neo4j.ogm.annotation.Relationship -import org.neo4j.ogm.annotation.Transient /** A declaration who has a type. */ 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() + override val typeObservers: MutableSet = identitySetOf() - /** - * 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 (TypeManager.isTypeSystemActive()) { - _type - } else { - TypeManager.getInstance() - .typeCache - .computeIfAbsent(this) { mutableListOf() } - .stream() - .findAny() - .orElse(UnknownType.getUnknownType()) - } - return result - } + /** The type of this declaration. */ + override var type: Type = unknownType() set(value) { - // Trigger the type listener foo - setType(value, null) - } - - @Relationship("POSSIBLE_SUB_TYPES") protected var _possibleSubTypes = mutableListOf() + 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) + } - override var possibleSubTypes: List - get() { - return if (!TypeManager.isTypeSystemActive()) { - TypeManager.getInstance().typeCache.getOrDefault(this, emptyList()) - } 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 - * 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. */ - /** Set all usages of the variable/field and assembles the access properties. */ - var usages: List + @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 .stream() - .map { ref: DeclaredReferenceExpression -> + .map { ref: Reference -> val edge = PropertyEdge(this, ref) edge.addProperty(Properties.ACCESS, ref.access) edge @@ -111,154 +97,12 @@ 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) } - /** - * 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.getUnknownType() - } else type - } - - override fun setType(type: Type, root: MutableList?) { - var type: Type? = type - var root: MutableList? = root - if (!TypeManager.isTypeSystemActive()) { - TypeManager.getInstance().cacheType(this, type) - return - } - if (root == null) { - root = ArrayList() - } - if ( - type == null || - root.contains(this) || - TypeManager.getInstance().isUnknown(type) || - this._type is FunctionPointerType && type !is FunctionPointerType - ) { - return - } - val oldType = this.type - type = type.duplicate() - val subTypes: MutableSet = HashSet() - for (t in possibleSubTypes) { - if (!t.isSimilar(type)) { - subTypes.add(t) - } - } - subTypes.add(type) - this._type = - TypeManager.getInstance() - .registerType(TypeManager.getInstance().getCommonType(subTypes, this).orElse(type)) - val newSubtypes: MutableList = ArrayList() - for (s in subTypes) { - if (TypeManager.getInstance().isSupertypeOf(this.type, s, this)) { - newSubtypes.add(TypeManager.getInstance().registerType(s)) - } - } - possibleSubTypes = newSubtypes - if (oldType == type) { - // 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. - // Notify all listeners about the changed type - for (l in typeListeners) { - if (l != this) { - l.typeChanged(this, root, oldType) - } - } - } - - override fun setPossibleSubTypes(possibleSubTypes: List, root: MutableList) { - var possibleSubTypes = possibleSubTypes - possibleSubTypes = - possibleSubTypes - .filterNot { type -> TypeManager.getInstance().isUnknown(type) } - .distinct() - .toMutableList() - if (!TypeManager.isTypeSystemActive()) { - possibleSubTypes.forEach { t -> TypeManager.getInstance().cacheType(this, t) } - - return - } - if (root.contains(this)) { - return - } - val oldSubTypes = this.possibleSubTypes - this._possibleSubTypes = possibleSubTypes - - if (HashSet(oldSubTypes).containsAll(possibleSubTypes)) { - // 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 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) { - 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() } @@ -270,9 +114,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 fe207e9956..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 @@ -26,18 +26,22 @@ 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.Expression -import de.fraunhofer.aisec.cpg.graph.statements.expressions.InitializerListExpression +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 import de.fraunhofer.aisec.cpg.graph.types.Type 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. @@ -61,71 +65,63 @@ 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 Reference) { + 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 (!TypeManager.isTypeSystemActive()) { - return - } - if (!TypeManager.getInstance().isUnknown(type) && src.propagationType == oldType) { + override fun toString(): String { + return ToStringBuilder(this, TO_STRING_STYLE) + .append("name", name) + .append("location", location) + .append("initializer", initializer) + .toString() + } + + override fun typeChanged(newType: Type, src: HasType) { + // Only accept type changes from our initializer; or if the source is a tuple + if (src != initializer && src !is TupleDeclaration) { 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 (!TypeManager.getInstance().isUnknown(type)) { - return - } else { - src.type.dereference() + + // 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) { + // 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 { - src.propagationType + // Otherwise, we can just set the type directly. + type = newType + } + } else { + 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) } - setType(newType, root) - if (previous != type) { - type.typeOrigin = Type.Origin.DATAFLOW } } - override fun possibleSubTypesChanged(src: HasType, root: MutableList) { - if (!TypeManager.isTypeSystemActive()) { - return + override fun assignedTypeChanged(assignedTypes: Set, src: HasType) { + // Propagate the assigned types from our initializer into the declaration + if (src == initializer) { + addAssignedTypes(assignedTypes) } - 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) - .append("location", location) - .append("initializer", initializer) - .toString() } override fun equals(other: Any?): Boolean { 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 c839eec489..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 @@ -84,6 +85,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) } @@ -100,7 +102,7 @@ open class PropertyEdge : Persistable { } fun addProperties(propertyMap: Map?) { - properties.putAll(propertyMap!!) + propertyMap?.let { properties.putAll(it) } } override fun equals(other: Any?): Boolean { @@ -161,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) { - var 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 } @@ -246,7 +254,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 @@ -296,10 +304,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) } } @@ -365,10 +373,28 @@ 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)) + } + } +} + +/** 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).toList(), outgoing).toMutableSet() + } + + operator fun setValue(thisRef: S, property: KProperty<*>, value: MutableSet) { + if (edge is KMutableProperty1) { + 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/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/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/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/scopes/LoopScope.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/scopes/LoopScope.kt index 5a4f0dcf68..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 @@ -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() } @@ -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/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/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/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 fd67cd5ba2..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 @@ -26,13 +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 de.fraunhofer.aisec.cpg.graph.statements.expressions.Block import java.util.Objects -class CatchClause : Statement() { +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 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/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 702547223d..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 @@ -25,15 +25,25 @@ */ 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.Block +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Reference 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 Reference) { + value.access = AccessValues.WRITE + } + field = value + } /** This field contains the iteration subject of the loop. */ @AST var iterable: Statement? = null @@ -41,6 +51,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 Block) { + val block = newBlock() + statement?.let { block.addStatement(it) } + block.addStatement(s) + statement = block + } else { + (statement as? Block)?.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/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/IfStatement.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/IfStatement.kt index 53dcc68045..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 @@ -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,18 +45,17 @@ 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 - /** - * 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/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/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/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 ceaea1b8aa..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 @@ -26,13 +26,16 @@ 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() { +class WhileStatement : Statement(), BranchingNode, ArgumentHolder { /** C++ allows defining a declaration instead of a pure logical expression as condition */ @AST var conditionDeclaration: Declaration? = null @@ -41,10 +44,13 @@ class WhileStatement : Statement() { /** * 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 + override val branchedBy: Node? + get() = condition ?: conditionDeclaration + override fun toString(): String { return ToStringBuilder(this, TO_STRING_STYLE) .appendSuper(super.toString()) @@ -53,6 +59,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/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/AssignExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/AssignExpression.kt index 3938e91ece..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 @@ -27,8 +27,11 @@ 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 +import org.slf4j.LoggerFactory /** * Represents an assignment of a group of expressions (in the simplest case: one) from the right @@ -47,26 +50,28 @@ import de.fraunhofer.aisec.cpg.graph.types.Type * [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 = "=" + override var operatorCode: String = "=" - @AST var lhs: List = listOf() + @AST + var lhs: List = listOf() + set(value) { + field = value + if (operatorCode == "=") { + field.forEach { (it as? Reference)?.access = AccessValues.WRITE } + } else { + field.forEach { (it as? Reference)?.access = AccessValues.READWRITE } + } + } @AST var rhs: List = listOf() set(value) { - // Unregister any old type listeners - field.forEach { it.unregisterTypeListener(this) } + field.forEach { it.unregisterTypeObserver(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 - } - } + value.forEach { it.registerTypeObserver(this) } } /** @@ -74,7 +79,7 @@ class AssignExpression : Expression(), AssignmentHolder, HasType.TypeListener { * 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] @@ -92,12 +97,12 @@ 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() { - return arrayOf("*=", "/=", "%=", "+=", "-=", "<<=", ">>=", "&=", "^=", "|=") - .contains(operatorCode) && isSingleValue + return operatorCode in (language?.compoundAssignmentOperators ?: setOf()) && + isSingleValue } /** @@ -110,88 +115,47 @@ class AssignExpression : Expression(), AssignmentHolder, HasType.TypeListener { */ override var declarations = mutableListOf() - override fun typeChanged(src: HasType, root: MutableList, oldType: Type) { - if (!TypeManager.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 (!TypeManager.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? { - if (lhs.size > 1) { - return rhs.singleOrNull() + 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) { - return null + null + } else { + rhs.getOrNull(idx) } - - return rhs.getOrNull(idx) } } /** 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 - 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) + val idx = rhs.indexOf(rhsExpression) if (idx == -1) { - return listOf() + listOf() + } else { + listOfNotNull(lhs.getOrNull(idx)) } - - return listOfNotNull(lhs.getOrNull(idx)) } } @@ -205,4 +169,63 @@ class AssignExpression : Expression(), AssignmentHolder, HasType.TypeListener { return list } + + 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 f7a06a3234..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 @@ -25,21 +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 -import org.neo4j.ogm.annotation.Transient /** * 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. */ -class BinaryOperator : - Expression(), HasType.TypeListener, AssignmentHolder, HasBase, ArgumentHolder { +open class BinaryOperator : + Expression(), HasBase, HasOperatorCode, ArgumentHolder, HasType.TypeObserver { /** The left-hand expression. */ @AST var lhs: Expression = ProblemExpression("could not parse lhs") @@ -57,13 +57,17 @@ class BinaryOperator : field = value connectNewRhs(value) } + /** The operator code. */ override var operatorCode: String? = null set(value) { field = value - if (compoundOperators.contains(operatorCode) || operatorCode == "=") { - NodeBuilder.LOGGER.warn( - "Creating a BinaryOperator with an assignment operator code is deprecated. The class AssignExpression should be used instead." + if ( + (operatorCode in (language?.compoundAssignmentOperators ?: setOf())) || + (operatorCode == "=") + ) { + throw TranslationException( + "Creating a BinaryOperator with an assignment operator code is not allowed. The class AssignExpression should be used instead." ) } } @@ -73,35 +77,20 @@ 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 (compoundOperators.contains(operatorCode)) { - 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 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 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 } } 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? { @@ -109,70 +98,11 @@ 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 (!TypeManager.isTypeSystemActive()) { - return - } - 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!! - ) { - // 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.getUnknownType(language) - if (resultingType !is UnknownType) { - setType(resultingType, root) - } - } - - if (previous != type) { - type.typeOrigin = Type.Origin.DATAFLOW - } - } - - override fun possibleSubTypesChanged(src: HasType, root: MutableList) { - if (!TypeManager.isTypeSystemActive()) { - return - } - val subTypes: MutableList = ArrayList(possibleSubTypes) - subTypes.addAll(src.possibleSubTypes) - setPossibleSubTypes(subTypes, root) + rhs.unregisterTypeObserver(this) } override fun toString(): String { @@ -183,15 +113,29 @@ 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) { @@ -208,15 +152,6 @@ 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 @@ -245,10 +180,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/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 aadba8dc06..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 @@ -26,20 +26,17 @@ 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.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 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.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.graph.edge.PropertyEdge.Companion.wrap +import de.fraunhofer.aisec.cpg.graph.types.* import de.fraunhofer.aisec.cpg.passes.CallResolver import de.fraunhofer.aisec.cpg.passes.VariableUsageResolver import java.util.* @@ -50,10 +47,13 @@ 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]. */ - @Relationship(value = "INVOKES", direction = Relationship.Direction.OUTGOING) +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] + */ @PopulatedByPass(CallResolver::class) + @Relationship(value = "INVOKES", direction = Relationship.Direction.OUTGOING) var invokeEdges = mutableListOf>() protected set @@ -61,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) var invokes: List get(): List { val targets: MutableList = ArrayList() @@ -70,9 +71,9 @@ open class CallExpression : Expression(), HasType.TypeListener, SecondaryTypeEdg return Collections.unmodifiableList(targets) } set(value) { - unwrap(invokeEdges).forEach { it.unregisterTypeListener(this) } - invokeEdges = transformIntoOutgoingPropertyEdgeList(value, this) - value.forEach { it.registerTypeListener(this) } + unwrap(invokeEdges).forEach { it.unregisterTypeObserver(this) } + invokeEdges = wrap(value, this) + value.forEach { it.registerTypeObserver(this) } } /** @@ -90,9 +91,8 @@ open class CallExpression : Expression(), HasType.TypeListener, SecondaryTypeEdg /** * 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 @@ -186,26 +186,6 @@ open class CallExpression : Expression(), HasType.TypeListener, SecondaryTypeEdg 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!![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]. @@ -223,7 +203,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 } } @@ -236,7 +216,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) == @@ -247,9 +227,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( @@ -257,7 +237,7 @@ open class CallExpression : Expression(), HasType.TypeListener, SecondaryTypeEdg TemplateInitialization.UNKNOWN ) ) - templateParameterEdges!!.add(propertyEdge) + templateParameterEdges?.add(propertyEdge) } } @@ -265,48 +245,34 @@ open class CallExpression : Expression(), HasType.TypeListener, SecondaryTypeEdg return templateInstantiation != null || templateParameterEdges != null || template } - override fun typeChanged(src: HasType, root: MutableList, oldType: Type) { - if (!TypeManager.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) { - 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.getUnknownType(language) - val commonType = TypeManager.getInstance().getCommonType(types, this).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 - } - } + .toSet() + val alternative = if (types.isNotEmpty()) types.first() else unknownType() + val commonType = types.commonType ?: alternative - override fun possibleSubTypesChanged(src: HasType, root: MutableList) { - if (!TypeManager.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 { @@ -319,8 +285,6 @@ open class CallExpression : Expression(), HasType.TypeListener, 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 && @@ -328,16 +292,6 @@ open class CallExpression : Expression(), HasType.TypeListener, SecondaryTypeEdg } // TODO: Not sure if we can add the template, templateParameters, templateInstantiation fields - // here - override fun hashCode() = Objects.hash(super.hashCode(), arguments, invokes) - - override fun updateType(typeState: Collection) { - for (t in typeTemplateParameters) { - for (t2 in typeState) { - if (t2 == t) { - replaceTypeTemplateParameter(t, t2) - } - } - } - } + // here + override fun hashCode() = Objects.hash(super.hashCode(), arguments) } 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..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 @@ -25,51 +25,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.Name -import de.fraunhofer.aisec.cpg.graph.TypeManager +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.UnknownType 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") - - var castType: Type = UnknownType.getUnknownType() +class CastExpression : Expression(), ArgumentHolder, HasType.TypeObserver { + @AST + var expression: Expression = ProblemExpression("could not parse inner expression") set(value) { + field.unregisterTypeObserver(this) field = value - type = value + value.registerTypeObserver(this) } - override fun updateType(type: Type) { - super.updateType(type) - castType = type - } - - override fun typeChanged(src: HasType, root: MutableList, oldType: Type) { - if (!TypeManager.isTypeSystemActive()) { - return - } - val previous = type - if (TypeManager.getInstance().isSupertypeOf(castType, src.propagationType, this)) { - setType(src.propagationType, root) - } else { - resetTypes(castType) - } - if (previous != type) { - type.typeOrigin = Type.Origin.DATAFLOW - } - } - - override fun possibleSubTypesChanged(src: HasType, root: MutableList) { - if (!TypeManager.isTypeSystemActive()) { - return + var castType: Type = unknownType() + set(value) { + field = value + type = value } - setPossibleSubTypes(ArrayList(src.possibleSubTypes), root) - } fun setCastOperator(operatorCode: Int) { var localName: String? = null @@ -86,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 2445483588..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 @@ -25,12 +25,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.TypeManager +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.UnknownType -import java.util.ArrayList import java.util.Objects import org.apache.commons.lang3.builder.ToStringBuilder @@ -38,62 +36,64 @@ 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(), ArgumentHolder, BranchingNode, HasType.TypeObserver { @AST var condition: Expression = ProblemExpression("could not parse condition expression") @AST - var thenExpr: Expression? = null + var thenExpression: Expression? = null set(value) { - field?.unregisterTypeListener(this) + field?.unregisterTypeObserver(this) field = value - value?.registerTypeListener(this) + value?.registerTypeObserver(this) } @AST - var elseExpr: Expression? = null + var elseExpression: 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 (!TypeManager.isTypeSystemActive()) { - return - } - val previous = type - val types: MutableList = ArrayList() + override fun toString(): String { + return ToStringBuilder(this, TO_STRING_STYLE) + .appendSuper(super.toString()) + .append("condition", condition) + .append("thenExpr", thenExpression) + .append("elseExpr", elseExpression) + .build() + } - thenExpr?.propagationType?.let { types.add(it) } - elseExpr?.propagationType?.let { types.add(it) } + override val branchedBy: Node + get() = condition - val subTypes: MutableList = ArrayList(possibleSubTypes) - subTypes.remove(oldType) - subTypes.addAll(types) - val alternative = if (types.isNotEmpty()) types[0] else UnknownType.getUnknownType() - setType(TypeManager.getInstance().getCommonType(types, this).orElse(alternative), root) - setPossibleSubTypes(subTypes, root) - if (previous != type) { - type.typeOrigin = Type.Origin.DATAFLOW - } + override fun addArgument(expression: Expression) { + // Do nothing } - override fun possibleSubTypesChanged(src: HasType, root: MutableList) { - if (!TypeManager.isTypeSystemActive()) { - return - } - val subTypes: MutableList = ArrayList(possibleSubTypes) - subTypes.addAll(src.possibleSubTypes) - possibleSubTypes = subTypes + override fun replaceArgument(old: Expression, new: Expression): Boolean { + // Do nothing + return false } - override fun toString(): String { - return ToStringBuilder(this, TO_STRING_STYLE) - .appendSuper(super.toString()) - .append("condition", condition) - .append("thenExpr", thenExpr) - .append("elseExpr", elseExpr) - .build() + override fun typeChanged(newType: Type, src: HasType) { + val types = mutableSetOf() + + 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 + } + + override fun assignedTypeChanged(assignedTypes: Set, src: HasType) { + // Merge and propagate the assigned types of our branches + if (src == thenExpression || src == elseExpression) { + val types = mutableSetOf() + thenExpression?.assignedTypes?.let { types.addAll(it) } + elseExpression?.assignedTypes?.let { types.addAll(it) } + addAssignedTypes(types) + } } override fun equals(other: Any?): Boolean { @@ -101,9 +101,10 @@ class ConditionalExpression : Expression(), HasType.TypeListener { 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 ef53ed66a9..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 @@ -26,13 +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.TypeManager +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.TypeParser import de.fraunhofer.aisec.cpg.graph.types.UnknownType import de.fraunhofer.aisec.cpg.passes.CallResolver import java.util.* @@ -44,7 +39,8 @@ 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 { +// TODO Merge and/or refactor +class ConstructExpression : CallExpression() { /** * The link to the [ConstructorDeclaration]. This is populated by the * [de.fraunhofer.aisec.cpg.passes.CallResolver] later. @@ -80,50 +76,10 @@ class ConstructExpression : CallExpression(), HasType.TypeListener { set(value) { field = value if (value != null && this.type is UnknownType) { - type = TypeParser.createFrom(value.name, language) + type = objectType(value.name) } } - /** - * 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 (!TypeManager.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/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/Expression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/Expression.kt index 6947ae30a6..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 @@ -27,13 +27,9 @@ 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 de.fraunhofer.aisec.cpg.helpers.identitySetOf import org.apache.commons.lang3.builder.ToStringBuilder -import org.neo4j.ogm.annotation.Relationship import org.neo4j.ogm.annotation.Transient /** @@ -48,203 +44,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: MutableSet = identitySetOf() - @Relationship("TYPE") private var _type: Type = UnknownType.getUnknownType() - - /** The type of the value after evaluation. */ - override var type: Type - get() { - val result: Type = - if (TypeManager.isTypeSystemActive()) { - _type - } else { - TypeManager.getInstance() - .typeCache - .computeIfAbsent(this) { mutableListOf() } - .stream() - .findAny() - .orElse(UnknownType.getUnknownType()) - } - 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 (!TypeManager.isTypeSystemActive()) { - TypeManager.getInstance().typeCache.getOrDefault(this, emptyList()) - } 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.getUnknownType() - } else type - } - - @Override - override fun setType(type: Type, root: MutableList?) { - var type: Type = type - var root: 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) - return - } - - if (root == null) { - root = 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) - ) { - return - } - - val oldType = this.type - // Backup to check if something changed - - type = type.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) - } - } - - subTypes.add(type) - - // 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)) - - // 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)) + 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 == type) { - // 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 (l in typeListeners) { - if (l != this) { - l.typeChanged(this, root, 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 possibleSubTypes = possibleSubTypes - possibleSubTypes = - possibleSubTypes - .filterNot { type -> TypeManager.getInstance().isUnknown(type) } - .distinct() - .toMutableList() - if (!TypeManager.isTypeSystemActive()) { - possibleSubTypes.forEach { t -> TypeManager.getInstance().cacheType(this, t) } - return - } - if (root.contains(this)) { - return - } - val oldSubTypes = this.possibleSubTypes - this._possibleSubTypes = possibleSubTypes - - if (HashSet(oldSubTypes).containsAll(possibleSubTypes)) { - // 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) } + field = value + informObservers(HasType.TypeObserver.ChangeType.ASSIGNED_TYPE) } - 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 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) { - 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) @@ -260,9 +88,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 331be3cbb2..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,84 +26,38 @@ 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.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.isEmpty()) { - 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 (!TypeManager.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 (!TypeManager.isTypeSystemActive()) { - return - } - 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..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 @@ -25,100 +25,77 @@ */ 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.* 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]. + */ +// TODO Merge and/or refactor +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) - 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 toString(): String { + return ToStringBuilder(this, TO_STRING_STYLE) + .appendSuper(super.toString()) + .append("initializers", initializers) + .toString() } - override fun typeChanged(src: HasType, root: MutableList, oldType: Type) { - if (!TypeManager.isTypeSystemActive()) { - return - } - if (!TypeManager.getInstance().isUnknown(type) && src.propagationType == oldType) { - return - } - val previous = type - val newType: Type - val subTypes: MutableList - if (initializers.contains(src)) { - val types = - initializers - .map { - TypeManager.getInstance() - .registerType(it.type.reference(PointerOrigin.ARRAY)) - } - .toSet() - val alternative = - if (types.isNotEmpty()) types.iterator().next() else UnknownType.getUnknownType() - newType = TypeManager.getInstance().getCommonType(types, this).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 addArgument(expression: Expression) { + this.initializers += expression } - override fun possibleSubTypesChanged(src: HasType, root: MutableList) { - if (!TypeManager.isTypeSystemActive()) { - return + 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()) } - 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()) - .append("initializers", initializers) - .toString() + 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 { @@ -129,5 +106,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/LambdaExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/LambdaExpression.kt index 136d5358eb..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,18 +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.TypeManager import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.ValueDeclaration -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 /** * 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 @@ -51,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 (!TypeManager.isTypeSystemActive()) { + override fun typeChanged(newType: Type, src: HasType) { + // Make sure our src is the function + if (src != function) { return } - if (!TypeManager.getInstance().isUnknown(type) && 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, returnType, this.language) - - 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 495accef77..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 @@ -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 @@ -39,17 +39,18 @@ 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) { - field.unregisterTypeListener(this) + field.unregisterTypeObserver(this) field = value updateName() - value.registerTypeListener(this) + value.registerTypeObserver(this) } override var operatorCode: String? = null + override fun toString(): String { return ToStringBuilder(this, TO_STRING_STYLE) .appendSuper(super.toString()) @@ -57,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 @@ -81,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/ArrayCreationExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/NewArrayExpression.kt similarity index 72% 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 395db86cdc..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 @@ -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.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.types.Type import java.util.* import org.neo4j.ogm.annotation.Relationship @@ -40,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(), HasType.TypeListener { +// 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. @@ -48,9 +46,7 @@ class ArrayCreationExpression : Expression(), HasType.TypeListener { @AST var initializer: Expression? = null set(value) { - field?.unregisterTypeListener(this) field = value - value?.registerTypeListener(this) } /** @@ -63,7 +59,7 @@ class ArrayCreationExpression : Expression(), HasType.TypeListener { 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) { @@ -72,7 +68,7 @@ class ArrayCreationExpression : Expression(), HasType.TypeListener { 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 && @@ -80,24 +76,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 (!TypeManager.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 (!TypeManager.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/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/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..7fb5c2e839 --- /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 + * [SubscriptExpression] as the [SubscriptExpression.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/statements/expressions/DeclaredReferenceExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/Reference.kt similarity index 52% 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 524678b130..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 @@ -25,38 +25,39 @@ */ 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.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.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 [Reference]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 Reference : 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 @@ -65,12 +66,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 @@ -82,7 +80,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 [Reference] 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 @@ -91,35 +96,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 - } - - override fun typeChanged(src: HasType, root: MutableList, oldType: Type) { - if (!TypeManager.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 (!TypeManager.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) + return if (refersTo?.javaClass?.let { clazz.isAssignableFrom(it) } == true) + clazz.cast(refersTo) + else null } override fun toString(): String { @@ -129,15 +108,47 @@ 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 } - if (other !is DeclaredReferenceExpression) { + if (other !is Reference) { return false } 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/CompoundStatementExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ShortCircuitOperator.kt similarity index 57% rename from cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/CompoundStatementExpression.kt rename to cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ShortCircuitOperator.kt index 584a5a8748..1829a2d993 100644 --- 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/ShortCircuitOperator.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 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. @@ -25,23 +25,18 @@ */ 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 +import de.fraunhofer.aisec.cpg.graph.BranchingNode +import de.fraunhofer.aisec.cpg.graph.Node /** - * 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]. + * 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 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) +class ShortCircuitOperator : BinaryOperator(), BranchingNode { + override val branchedBy: Node + get() = lhs } 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 56% 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 5fd45de90b..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 @@ -26,32 +26,30 @@ 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 { - /** - * 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) { + field.unregisterTypeObserver(this) field = value type = getSubscriptType(value.type) - value.registerTypeListener(this) + value.registerTypeObserver(this) } /** * 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 ([Reference]), a [Literal] or a + * [RangeExpression]. */ @AST var subscriptExpression: Expression = ProblemExpression("could not parse index expression") @@ -61,38 +59,61 @@ 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) { - if (!TypeManager.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 (!TypeManager.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 { 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 5e2829acd3..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 @@ -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.TypeManager -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,115 +57,59 @@ 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 == "--") { access = AccessValues.READWRITE } - if (input is DeclaredReferenceExpression) { - (input as? DeclaredReferenceExpression)?.access = access - } - } - - private fun getsDataFromInput( - curr: HasType.TypeListener, - target: HasType.TypeListener - ): Boolean { - val worklist: MutableList = ArrayList() - worklist.add(curr) - while (!worklist.isEmpty()) { - 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) - } - } + if (input is Reference) { + (input as? Reference)?.access = access } - 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 (!TypeManager.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 } - input.setType(newType!!, mutableListOf(this)) - } - if (previous != type) { - type.typeOrigin = Type.Origin.DATAFLOW - } + this.type = type } - override fun possibleSubTypesChanged(src: HasType, root: MutableList) { - if (!TypeManager.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 { @@ -179,17 +119,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/AutoType.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/AutoType.kt new file mode 100644 index 0000000000..681790d96d --- /dev/null +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/AutoType.kt @@ -0,0 +1,46 @@ +/* + * 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() + } +} 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 82% 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 index a81cb7363b..2acab30cfc 100644 --- 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 @@ -26,17 +26,11 @@ 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) { - - override fun duplicate(): Type { - return BooleanType(this.name, bitWidth, language, modifier) - } -} +) : NumericType(typeName, bitWidth, language, modifier) 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 82% 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 index e34c4a01c6..542cf881d5 100644 --- 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 @@ -26,17 +26,11 @@ 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) { - - 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 new file mode 100644 index 0000000000..92c1aa4304 --- /dev/null +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/FunctionPointerType.kt @@ -0,0 +1,109 @@ +/* + * 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.graph.edge.PropertyEdge +import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.propertyEqualsList +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.* +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 = wrap(parameters, this) + this.returnType = returnType + } + + constructor( + type: Type, + parameters: List = listOf(), + language: Language<*>? = null, + returnType: Type = UnknownType.getUnknownType(language) + ) : super(type) { + parametersPropertyEdge = wrap(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 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 72% 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..55550fc462 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 @@ -26,9 +26,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.frontends.TranslationException import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration +import de.fraunhofer.aisec.cpg.graph.unknownType /** * A type representing a function. It contains a list of parameters and one or more return types. @@ -36,36 +36,27 @@ 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.firstOrNull() ?: unknownType(), + ) } override fun dereference(): Type { - return UnknownType.getUnknownType(language) - } - - override fun duplicate(): Type { - return FunctionType(typeName, parameters.toList(), returnTypes.toList(), language) + return unknownType() } companion object { @@ -84,7 +75,8 @@ class FunctionType : Type { func.language ) - return TypeManager.getInstance().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/HasSecondaryTypeEdge.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/HasSecondaryTypeEdge.kt new file mode 100644 index 0000000000..a4a47c667a --- /dev/null +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/HasSecondaryTypeEdge.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 + +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. [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 new file mode 100644 index 0000000000..dccdae4da2 --- /dev/null +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/HasType.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.graph.types + +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.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 + +/** + * 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 { + + /** + * 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 [Reference] + * + * An implementation of this must be sure to invoke [informObservers]. + */ + var type: Type + + /** + * 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. + * + * 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. + */ + var assignedTypes: Set + + /** + * Adds [type] to the list of [HasType.assignedTypes] and informs all observers about the + * change. + */ + 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) + } + } + + /** + * 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) + } + } + + /** + * A list of [TypeObserver] objects that will be informed about type changes, usually by + * [informObservers]. + */ + val typeObservers: MutableSet + + /** + * 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) + } + + /** + * 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) + } + } + } + + /** + * Registers the given [typeObservers] to be informed about type updates. This also immediately + * invokes both [TypeObserver.typeChanged] and [TypeObserver.assignedTypeChanged]. + */ + 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) + } + } + + /** Unregisters the given [typeObservers] from the list of observers. */ + fun unregisterTypeObserver(typeObserver: TypeObserver) { + typeObservers -= typeObserver + } +} 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..b16dd5ee02 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,37 @@ * \______/ \__| \______/ * */ -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) - public IncompleteType() { - super("void", null); - } + constructor(type: Type?) : super(type) - public IncompleteType(Type type) { - super(type); - } + /** @return PointerType to a IncompleteType, e.g. void* */ + override fun reference(pointer: PointerOrigin?): Type { + return PointerType(this, pointer) + } - /** - * @return PointerType to a IncompleteType, e.g. void* - */ - @Override - public Type reference(PointerType.PointerOrigin pointerOrigin) { - return new PointerType(this, pointerOrigin); - } + /** @return dereferencing void results in void therefore the same type is returned */ + override fun dereference(): Type { + return 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 82% 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 index 353ac3e599..f63d600f4a 100644 --- 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 @@ -26,17 +26,11 @@ 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) { - - override fun duplicate(): Type { - return IntegerType(this.name, bitWidth, language, modifier) - } -} +) : NumericType(typeName, bitWidth, language, modifier) 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 83% 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..0b0d4a046e 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 @@ -26,25 +26,18 @@ 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) { - - 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. TODO: maybe replace with a flag "signed" or - * "unsigned"? + * neither of the signed/unsigned option. */ enum class Modifier { SIGNED, @@ -55,5 +48,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..85571b78b1 --- /dev/null +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/ObjectType.kt @@ -0,0 +1,118 @@ +/* + * 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.PopulatedByPass +import de.fraunhofer.aisec.cpg.frontends.Language +import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration +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 +import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdgeDelegate +import de.fraunhofer.aisec.cpg.graph.types.PointerType.PointerOrigin +import de.fraunhofer.aisec.cpg.graph.unknownType +import de.fraunhofer.aisec.cpg.passes.TypeResolver +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 { + /** + * Reference from the [ObjectType] to its class ([RecordDeclaration]), only if the class is + * available. This is set by the [TypeResolver]. + */ + @PopulatedByPass(TypeResolver::class) var recordDeclaration: RecordDeclaration? = null + + @Relationship(value = "GENERICS", direction = Relationship.Direction.OUTGOING) + var genericsPropertyEdges: MutableList> = mutableListOf() + private set + + var generics by PropertyEdgeDelegate(ObjectType::genericsPropertyEdges) + private set + + constructor( + typeName: CharSequence, + generics: List, + primitive: Boolean, + language: Language<*>? + ) : super(typeName, language) { + this.genericsPropertyEdges = wrap(generics, this) + isPrimitive = primitive + this.language = language + } + + constructor( + type: Type?, + generics: List, + primitive: Boolean, + language: Language<*>? + ) : super(type) { + this.language = language + this.genericsPropertyEdges = wrap(generics, this) + isPrimitive = primitive + } + + /** Empty default constructor for use in Neo4J persistence. */ + constructor() : super() { + genericsPropertyEdges = ArrayList() + isPrimitive = false + } + + /** @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 de-referencing an + * ObjectType, as it is just some memory and its interpretation is unknown + */ + override fun dereference(): Type { + return unknownType() + } + + 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-language-go/src/main/golang/language.go b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/ParameterizedType.kt similarity index 58% rename from cpg-language-go/src/main/golang/language.go rename to cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/ParameterizedType.kt index b0cb95bf1b..12a8ebc2ce 100644 --- a/cpg-language-go/src/main/golang/language.go +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/ParameterizedType.kt @@ -23,33 +23,29 @@ * \______/ \__| \______/ * */ -package cpg +package de.fraunhofer.aisec.cpg.graph.types -import ( - "tekao.net/jnigi" -) +import de.fraunhofer.aisec.cpg.frontends.Language +import de.fraunhofer.aisec.cpg.graph.types.PointerType.PointerOrigin -type Language Node - -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 -} +/** + * 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) { + language = type.language + } -func (l *Language) ConvertToJava() (obj *jnigi.ObjectRef, err error) { - return (*jnigi.ObjectRef)(l), nil -} + constructor(typeName: String?, language: Language<*>?) : super(typeName) { + this.language = language + } -func (l *Language) GetClassName() string { - return LanguageClass -} + override fun reference(pointer: PointerOrigin?): Type { + return PointerType(this, pointer) + } -func (l *Language) IsArray() bool { - return false + override fun dereference(): Type { + return 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..945925d50c --- /dev/null +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/PointerType.kt @@ -0,0 +1,138 @@ +/* + * 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 + 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 + name = + if (pointerOrigin == PointerOrigin.ARRAY) { + elementType.name.append("[]") + } else { + 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 + } + + 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/ProblemType.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/ProblemType.kt new file mode 100644 index 0000000000..bb193d35f8 --- /dev/null +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/ProblemType.kt @@ -0,0 +1,36 @@ +/* + * 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 + } +} 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..b4096c7020 --- /dev/null +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/ReferenceType.kt @@ -0,0 +1,91 @@ +/* + * 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.types.PointerType.PointerOrigin +import de.fraunhofer.aisec.cpg.graph.unknownType +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 = unknownType() + + 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 isSimilar(t: Type?): Boolean { + return t is ReferenceType && t.elementType == this && super.isSimilar(t) + } + + 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 82% 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 index 633a1dbf8d..1b2fda27ce 100644 --- 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 @@ -26,15 +26,9 @@ 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) { - - 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 027a1e540f..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 @@ -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,14 +44,10 @@ 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") - } - - override fun duplicate(): Type { - return TupleType(types.map { it.duplicate() }) + return unknownType() } } 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..055cc0ef37 --- /dev/null +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/Type.kt @@ -0,0 +1,223 @@ +/* + * 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.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 +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 + +/** + * 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. */ + @PopulatedByPass(TypeHierarchyResolver::class) + @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 ?: UNKNOWN_TYPE_STRING) + typeOrigin = Origin.UNRESOLVED + } + + constructor(type: Type?) { + type?.name?.let { name = it.clone() } + typeOrigin = type?.typeOrigin + } + + constructor(typeName: CharSequence, language: Language<*>?) { + name = + if (this is FunctionType) { + Name(typeName.toString(), null, language) + } else { + 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 + } + + /** + * 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 + + /** + * @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 + } + } + } + + 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 AutoType || + this is UnknownType || + this is FunctionType || + this is ProblemType || + 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 other is Type && 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" + } + + /** + * 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/UnknownType.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/UnknownType.kt new file mode 100644 index 0000000000..7b849dc96d --- /dev/null +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/UnknownType.kt @@ -0,0 +1,86 @@ +/* + * 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.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 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 { + name = Name(UNKNOWN_TYPE_STRING, null, language) + } + + /** + * @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 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 = 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 + 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..42d6b36824 --- /dev/null +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/WrapState.kt @@ -0,0 +1,64 @@ +/* + * 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 +import java.util.* + +/** + * 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 { + /** 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 + + /** 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 + } + + override fun hashCode(): Int { + return Objects.hash(depth, isReference, pointerOrigins.contentHashCode(), referenceType) + } +} 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 83% 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 index 86564b1111..6a49444e33 100644 --- 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 @@ -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 new file mode 100644 index 0000000000..0b025bf368 --- /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 { file -> + file.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/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..1c5d322f75 --- /dev/null +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/EOGWorklist.kt @@ -0,0 +1,306 @@ +/* + * 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() + + // 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) + } + } + } + } + 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() + + // 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) + } + } + } + } + return worklist.mop() +} 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 68% 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..49644cfa7d 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 @@ -28,18 +28,11 @@ 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 -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.helpers.Util.reverse -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 @@ -48,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 @@ -71,7 +63,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)) @@ -225,7 +217,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. * @@ -236,44 +228,29 @@ 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 } - 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() - } - } - } - ) - } - /** * 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 * 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 { @@ -307,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 @@ -324,18 +299,17 @@ 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) }) + 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 + // need to exit a scope (todo as ArrayDeque>).push(Pair(current, parent)) onNodeVisit.forEach(Consumer { c: Consumer -> c.accept(current) }) onNodeVisit2.forEach( @@ -347,7 +321,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) @@ -363,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 { @@ -378,25 +352,17 @@ 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. + * 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 - constructor(lang: LanguageFrontend) { + constructor(lang: LanguageFrontend<*, *>) { scopeManager = lang.scopeManager } @@ -408,18 +374,19 @@ 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,87 +399,22 @@ object SubgraphWalker { */ fun iterate(root: Node) { walker = IterativeGraphWalker() - handlers.forEach( - Consumer { h: TriConsumer -> - walker!!.registerOnNodeVisit { n: Node -> handleNode(n, h) } - } - ) - walker!!.registerOnScopeExit { exiting: Node -> leaveScope(exiting) } - walker!!.iterate(root) + handlers.forEach { h -> walker?.registerOnNodeVisit { n -> handleNode(n, h) } } + walker?.iterate(root) } private fun handleNode( current: Node, - handler: TriConsumer + handler: TriConsumer ) { - scopeManager.enterScopeIfExists(current) - 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) { - var parentBlock: Node? = null - - // get containing Record or Compound - for (node in walker!!.backlog!!) { - if ( - node is RecordDeclaration || - node is CompoundStatement || - node is FunctionDeclaration // can also be a translationunit for global (c) - // functions - || - node is TranslationUnitDeclaration - ) { - 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) - } + // Jump to the node's scope, if it is different from ours. + if (scopeManager.currentScope != current.scope) { + scopeManager.jumpTo(current.scope) } - } - /** - * @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 ( - currentScope != null && - nodeToParentBlockAndContainedValueDeclarations.containsKey(scope) - ) { - val entry = nodeToParentBlockAndContainedValueDeclarations[currentScope]!! - for (`val` in entry.right) { - if (predicate.test(`val`)) { - return Optional.of(`val`) - } - } - currentScope = entry.left - } - return Optional.empty() + val parent = walker?.backlog?.peek() + + handler.accept(scopeManager.currentRecord, parent, current) } } } 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 78% 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..381005c1d0 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,23 +25,13 @@ */ 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 import de.fraunhofer.aisec.cpg.graph.edge.Properties import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression import de.fraunhofer.aisec.cpg.sarif.PhysicalLocation -import java.io.ByteArrayOutputStream -import java.io.File -import java.io.IOException -import java.io.InputStream -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 { @@ -152,37 +142,9 @@ object Util { else refNodes.containsAll(nodeSide) } - @Throws(IOException::class) - fun inputStreamToString(inputStream: InputStream): String { - ByteArrayOutputStream().use { result -> - 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( - lang: LanguageFrontend, - astNode: S, + inline fun warnWithFileLocation( + lang: LanguageFrontend, + astNode: AstNode, log: Logger, format: String?, vararg arguments: Any? @@ -190,17 +152,16 @@ object Util { log.warn( String.format( "%s: %s", - PhysicalLocation.locationLink(lang.getLocationFromRawNode(astNode)), + PhysicalLocation.locationLink(lang.locationOf(astNode)), format ), *arguments ) } - @JvmStatic - fun errorWithFileLocation( - lang: LanguageFrontend, - astNode: S, + inline fun errorWithFileLocation( + lang: LanguageFrontend, + astNode: AstNode, log: Logger, format: String?, vararg arguments: Any? @@ -208,29 +169,49 @@ object Util { log.error( String.format( "%s: %s", - PhysicalLocation.locationLink(lang.getLocationFromRawNode(astNode)), + PhysicalLocation.locationLink(lang.locationOf(astNode)), format ), *arguments ) } - @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 @@ -257,9 +238,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 +251,7 @@ object Util { } } if (currPart.isNotEmpty()) { - result.add(currPart.toString().strip()) + result.add(currPart.toString().trim()) } return result } @@ -287,9 +268,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 +313,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,44 +324,20 @@ 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 } else { - param.addPrevDFG(arguments[j]!!) + param.addPrevDFG(arguments[j]) } } j++ } } - // 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 +349,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. * @@ -424,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/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..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 @@ -25,13 +25,12 @@ */ 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.* -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 de.fraunhofer.aisec.cpg.isDerivedFrom import java.util.HashMap import java.util.regex.Pattern @@ -41,17 +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( - callSignature: List, - functionSignature: List, - provider: ScopeProvider -): 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 && - !TypeManager.getInstance() - .isSupertypeOf(functionSignature[i], callSignature[i], provider) + callSignature[i].isPrimitive != functionSignature[i].isPrimitive && + !callSignature[i].isDerivedFrom(functionSignature[i]) ) { return false } @@ -71,7 +65,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( @@ -105,12 +99,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)) { val implicitCastTargets = signatureWithImplicitCastTransformation( + call, getCallSignatureWithDefaults(call, functionDeclaration), call.arguments, - functionDeclaration.signatureTypes + functionDeclaration.signatureTypes, ) if (implicitCasts == null) { implicitCasts = implicitCastTargets @@ -119,7 +114,7 @@ fun resolveWithImplicitCast( // to the same target type checkMostCommonImplicitCast(implicitCasts, implicitCastTargets) } - if (compatibleSignatures(call.signature, functionDeclaration.signatureTypes, call)) { + if (compatibleSignatures(call.signature, functionDeclaration.signatureTypes)) { invocationTargetsWithImplicitCast.add(functionDeclaration) } else { invocationTargetsWithImplicitCastAndDefaults.add(functionDeclaration) @@ -238,7 +233,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() } @@ -272,14 +267,14 @@ fun resolveConstructorWithImplicitCast( compatibleSignatures( constructExpression.signature, constructorDeclaration.signatureTypes, - constructExpression ) ) { val implicitCasts = signatureWithImplicitCastTransformation( + constructExpression, constructExpression.signature, constructExpression.arguments, - constructorDeclaration.signatureTypes + constructorDeclaration.signatureTypes, ) applyImplicitCastToArguments(constructExpression, implicitCasts) return constructorDeclaration @@ -287,14 +282,14 @@ fun resolveConstructorWithImplicitCast( compatibleSignatures( workingSignature, constructorDeclaration.signatureTypes, - constructExpression ) ) { val implicitCasts = signatureWithImplicitCastTransformation( + constructExpression, getCallSignatureWithDefaults(constructExpression, constructorDeclaration), constructExpression.arguments, - constructorDeclaration.signatureTypes + constructorDeclaration.signatureTypes, ) applyImplicitCastToArguments(constructExpression, implicitCasts) return constructorDeclaration @@ -309,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 @@ -327,12 +322,13 @@ fun applyTemplateInstantiation( function: FunctionDeclaration, initializationSignature: Map, initializationType: Map, - orderedInitializationSignature: Map + orderedInitializationSignature: Map, + ctx: TranslationContext ): List { val templateInstantiationParameters = mutableListOf(*orderedInitializationSignature.keys.toTypedArray()) for ((key, value) in orderedInitializationSignature) { - templateInstantiationParameters[value] = initializationSignature[key]!! + initializationSignature[key]?.let { templateInstantiationParameters[value] = it } } templateCall.templateInstantiation = functionTemplateDeclaration @@ -355,9 +351,10 @@ fun applyTemplateInstantiation( val templateCallSignature = templateCall.signature val callSignatureImplicit = signatureWithImplicitCastTransformation( + templateCall, templateCallSignature, templateCall.arguments, - templateFunctionSignature + templateFunctionSignature, ) for (i in callSignatureImplicit.indices) { val cast = callSignatureImplicit[i] @@ -366,12 +363,11 @@ 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) { - declaration.addPrevDFG(initializationSignature[declaration]!!) - initializationSignature[declaration]!!.addNextDFG(declaration) + if (declaration is ParameterDeclaration) { + initializationSignature[declaration]?.let { declaration.addPrevDFG(it) } } } @@ -392,9 +388,10 @@ fun applyTemplateInstantiation( * FunctionDeclaration cannot be reached through implicit casts */ fun signatureWithImplicitCastTransformation( + call: CallExpression, callSignature: List, arguments: List, - functionSignature: List + functionSignature: List, ): MutableList { val implicitCasts = mutableListOf() if (callSignature.size != functionSignature.size) return implicitCasts @@ -402,8 +399,9 @@ 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.ctx = call.ctx implicitCast.isImplicit = true implicitCast.castType = funcType implicitCast.language = funcType.language @@ -423,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 } } @@ -456,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( @@ -518,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( @@ -537,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 @@ -563,22 +561,21 @@ 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 || - TypeManager.getInstance() - .isSupertypeOf(templateParameter.type, callParameter.type, callParameterArg) + callParameter.type.isDerivedFrom(templateParameter.type) } else { false } @@ -628,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 @@ -638,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 2858265036..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 @@ -25,12 +25,8 @@ */ package de.fraunhofer.aisec.cpg.passes -import de.fraunhofer.aisec.cpg.TranslationResult -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.TranslationContext +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 @@ -64,53 +60,48 @@ 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(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 * [getOverridingCandidates] and should probably be replaced through a scope manager call. */ - private val containingType = mutableMapOf() + protected val containingType = mutableMapOf() override fun cleanup() { 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) } walker.registerHandler { node, _ -> findTemplates(node) } walker.registerHandler { currentClass, _, currentNode -> 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) } } - private fun registerMethods(currentClass: RecordDeclaration?, currentNode: Node) { + protected fun registerMethods(currentClass: RecordDeclaration?, currentNode: Node?) { if (currentNode is MethodDeclaration && currentClass != null) { - containingType[currentNode] = - TypeParser.createFrom(currentClass.name, currentClass.language) + containingType[currentNode] = currentNode.objectType(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 @@ -118,6 +109,7 @@ open class CallResolver : SymbolResolverPass() { 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 { @@ -133,6 +125,7 @@ open class CallResolver : SymbolResolverPass() { 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 @@ -142,13 +135,13 @@ open class CallResolver : SymbolResolverPass() { } } - protected fun handleNode(node: Node) { + protected fun handleNode(node: Node?) { when (node) { 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 @@ -167,7 +160,7 @@ open class CallResolver : SymbolResolverPass() { } } - 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 @@ -186,7 +179,7 @@ open class CallResolver : SymbolResolverPass() { curClass, call, true, - scopeManager, + ctx, currentTU ) @@ -201,7 +194,24 @@ 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, 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, ctx = ctx) + is NamespaceDeclaration -> start.inferFunction(call, ctx = ctx) + else -> null + } + + listOfNotNull(func) } else { createMethodDummies(suitableBases, call) } @@ -213,7 +223,7 @@ open class CallResolver : SymbolResolverPass() { // 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() } } @@ -224,14 +234,14 @@ open class CallResolver : SymbolResolverPass() { * * In case a resolution is not possible, `null` can be returned. */ - private fun resolveCallee( + protected fun resolveCallee( callee: Expression?, curClass: RecordDeclaration?, call: CallExpression ): 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, @@ -251,10 +261,10 @@ open class CallResolver : SymbolResolverPass() { } } - private fun handleArguments(call: CallExpression) { + protected 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) @@ -271,31 +281,31 @@ open class CallResolver : SymbolResolverPass() { } /** - * 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. */ - private fun resolveReferenceCallee( - callee: DeclaredReferenceExpression, + protected fun resolveReferenceCallee( + callee: Reference, curClass: RecordDeclaration?, call: CallExpression ): 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 = 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() } - return candidates + candidates } else { - return resolveMemberCallee(callee, curClass, call) + resolveMemberCallee(callee, curClass, call) } } @@ -307,20 +317,21 @@ open class CallResolver : SymbolResolverPass() { * delegates resolving of regular function calls within classes to this function (meh!) */ fun resolveMemberCallee( - callee: DeclaredReferenceExpression, + callee: Reference, curClass: RecordDeclaration?, call: CallExpression ): List { // 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 && - callee.base is DeclaredReferenceExpression && - isSuperclassReference(callee.base as DeclaredReferenceExpression) + curClass != null && + callee is MemberExpression && + callee.base is Reference && + isSuperclassReference(callee.base as Reference) ) { (callee.language as? HasSuperClasses)?.handleSuperCall( callee, - curClass!!, + curClass, scopeManager, recordMap ) @@ -336,7 +347,7 @@ open class CallResolver : SymbolResolverPass() { 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 = @@ -355,7 +366,7 @@ open class CallResolver : SymbolResolverPass() { return invocationCandidates } - private fun retrieveInvocationCandidatesFromCall( + protected fun retrieveInvocationCandidatesFromCall( call: CallExpression, curClass: RecordDeclaration?, possibleContainingTypes: Set @@ -366,7 +377,7 @@ open class CallResolver : SymbolResolverPass() { curClass, possibleContainingTypes, call, - scopeManager, + ctx, currentTU, this ) @@ -382,21 +393,21 @@ open class CallResolver : SymbolResolverPass() { * @param possibleContainingTypes * @param call */ - private fun createMethodDummies( + protected fun createMethodDummies( possibleContainingTypes: Set, call: CallExpression ): List { return possibleContainingTypes .mapNotNull { var record = recordMap[it.root.name] - if (record == null && config?.inferenceConfiguration?.inferRecords == true) { - record = it.startInference().inferRecordDeclaration(it, currentTU) + if (record == null && config.inferenceConfiguration.inferRecords == true) { + record = it.startInference(ctx).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 } - .map { record -> record.inferMethod(call) } + .map { record -> record.inferMethod(call, ctx = ctx) } } /** @@ -406,11 +417,11 @@ open class CallResolver : SymbolResolverPass() { * @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 @@ -418,7 +429,7 @@ open class CallResolver : SymbolResolverPass() { 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) @@ -454,29 +465,32 @@ open class CallResolver : SymbolResolverPass() { } } - private fun handleExplicitConstructorInvocation(eci: ExplicitConstructorInvocation) { - if (eci.containingClass != null) { - val recordDeclaration = recordMap[eci.parseName(eci.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 } } } - private fun getPossibleContainingTypes(node: Node?): Set { + protected 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.assignedTypes) + } } 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) } } @@ -494,7 +508,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) ) @@ -502,7 +516,8 @@ open class CallResolver : SymbolResolverPass() { (call.language as HasComplexCallResolution).refineInvocationCandidatesFromRecord( recordDeclaration, call, - namePattern + namePattern, + ctx ) } else { recordDeclaration.methods.filter { @@ -511,7 +526,7 @@ open class CallResolver : SymbolResolverPass() { } } - private fun getInvocationCandidatesFromParents( + protected fun getInvocationCandidatesFromParents( name: String?, call: CallExpression, possibleTypes: Set @@ -527,7 +542,7 @@ open class CallResolver : SymbolResolverPass() { // 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) } @@ -541,8 +556,13 @@ open class CallResolver : SymbolResolverPass() { } } - private fun getOverridingCandidates( - possibleSubTypes: Set, + protected val Language<*>?.isCPP: Boolean + get() { + return this != null && this::class.simpleName == "CPPLanguage" + } + + protected fun getOverridingCandidates( + possibleSubTypes: Set, declaration: FunctionDeclaration ): Set { return declaration.overriddenBy @@ -555,8 +575,8 @@ open class CallResolver : SymbolResolverPass() { * @param recordDeclaration matching the class the ConstructExpression wants to construct * @return ConstructorDeclaration that matches the provided signature */ - private fun getConstructorDeclarationDirectMatch( - signature: List, + protected fun getConstructorDeclarationDirectMatch( + signature: List, recordDeclaration: RecordDeclaration ): ConstructorDeclaration? { for (constructor in recordDeclaration.constructors) { @@ -574,11 +594,11 @@ open class CallResolver : SymbolResolverPass() { * 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 { - val signature: List = constructExpression.signature + val signature = constructExpression.signature var constructorCandidate = getConstructorDeclarationDirectMatch(signature, recordDeclaration) if (constructorCandidate == null && constructExpression.language is HasDefaultArguments) { @@ -586,9 +606,7 @@ open class CallResolver : SymbolResolverPass() { 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 = @@ -597,16 +615,16 @@ open class CallResolver : SymbolResolverPass() { return constructorCandidate ?: recordDeclaration - .startInference() + .startInference(ctx) .createInferredConstructor(constructExpression.signature) } - private fun getConstructorDeclarationForExplicitInvocation( - signature: List, + protected fun getConstructorDeclarationForExplicitInvocation( + signature: List, recordDeclaration: RecordDeclaration ): ConstructorDeclaration { return recordDeclaration.constructors.firstOrNull { it.hasSignature(signature) } - ?: recordDeclaration.startInference().createInferredConstructor(signature) + ?: recordDeclaration.startInference(ctx).createInferredConstructor(signature) } companion object { @@ -615,7 +633,7 @@ open class CallResolver : SymbolResolverPass() { /** * 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 new file mode 100644 index 0000000000..a1e12fb5df --- /dev/null +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPass.kt @@ -0,0 +1,270 @@ +/* + * 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 [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. + * 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(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( + functionDeclaration, + PrevEOGLattice(mapOf(Pair(functionDeclaration, setOf(functionDeclaration)))) + ) + val finalState = + iterateEOG(functionDeclaration.nextEOGEdges, startState, ::handleEdge) ?: return + + val branchingNodeConditionals = getBranchingNodeConditions(functionDeclaration) + + // 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 != 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) + 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(functionDeclaration: FunctionDeclaration) = + mapOf( + // For the function declaration, there's only the path through the function declaration + // itself. + Pair(functionDeclaration, setOf(functionDeclaration)), + *functionDeclaration + .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 72fd969122..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 @@ -25,40 +25,34 @@ */ package de.fraunhofer.aisec.cpg.passes -import de.fraunhofer.aisec.cpg.TranslationResult +import de.fraunhofer.aisec.cpg.TranslationContext 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.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 +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) @DependsOn(DFGPass::class) -open class ControlFlowSensitiveDFGPass : Pass() { +open class ControlFlowSensitiveDFGPass(ctx: TranslationContext) : TranslationUnitPass(ctx) { + override fun cleanup() { // Nothing to do } - override fun accept(translationResult: TranslationResult) { - val walker = IterativeGraphWalker() - walker.registerOnNodeVisit(::handle) - for (tu in translationResult.translationUnits) { - walker.iterate(tu) - } + override fun accept(tu: TranslationUnitDeclaration) { + tu.functions.forEach(::handle) } /** @@ -69,7 +63,35 @@ open class ControlFlowSensitiveDFGPass : Pass() { 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) { + 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 } + ) + } + } } } @@ -77,230 +99,197 @@ open class ControlFlowSensitiveDFGPass : Pass() { * Removes all the incoming and outgoing DFG edges for each variable declaration in the block of * code [node]. */ - private fun clearFlowsOfVariableDeclarations(node: Node) { - for (varDecl in node.variables) { + protected fun clearFlowsOfVariableDeclarations(node: Node) { + for (varDecl in node.variables.filter { it !is FieldDeclaration }) { varDecl.clearPrevDFG() varDecl.clearNextDFG() } } /** - * 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 [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. */ - private 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() }) + protected fun transfer( + currentEdge: PropertyEdge, + state: State>, + worklist: Worklist, Node, Set> + ): State> { + // We will set this if we write to a variable + var writtenDeclaration: 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. This also affects tuples. We split it up later. + state.push(currentNode, PowersetLattice(setOf(initializer))) + + 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)) ) - ) { - // 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) + } 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 + // 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? Reference)?.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 Reference + // We write to the variable in the input + writtenDeclaration = input.refersTo + + 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 + // 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 + val lhs = currentNode.lhs.singleOrNull() + writtenDeclaration = (lhs as? Reference)?.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 (writtenDeclaration != null && lhs != null) { + // Data flows from the last writes to the lhs variable to this node + state.push(lhs, doubleState.declarationsState[writtenDeclaration]) - // 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[writtenDeclaration] = PowersetLattice(setOf(lhs)) + } + } else if ( + (currentNode as? Reference)?.access == AccessValues.READ && + 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 { + // 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 + writtenDeclaration = + when (writtenTo) { + is Declaration -> writtenTo + is Reference -> 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 (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 = + 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()) } + } - 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) + 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[writtenDeclaration] = PowersetLattice(setOf(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) - } + } else if (currentNode is FunctionDeclaration) { + // We have to add the parameters + currentNode.parameters.forEach { + doubleState.pushToDeclarationsState(it, PowersetLattice(setOf(it))) } + } else if (currentNode is ReturnStatement) { + doubleState.returnStatements.push(currentNode, PowersetLattice(setOf(currentNode))) + } else { + doubleState.declarationsState.push( + currentNode, + doubleState.declarationsState[currentEdge.start] + ) } + return state + } - removeUnreachableImplicitReturnStatement(node, returnStatements) + /** + * Checks if the node performs an operation and an assignment at the same time e.g. with the + * operators +=, -=, *=, ... + */ + 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.singleOrNull() as? Reference)?.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) = + currentNode is UnaryOperator && + (currentNode.operatorCode == "++" || currentNode.operatorCode == "--") && + (currentNode.input as? Reference)?.refersTo != null + /** * 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 + 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 && @@ -310,163 +299,64 @@ open class ControlFlowSensitiveDFGPass : Pass() { } /** - * 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. + * A state which actually holds a state for all nodes, one only for declarations and one for + * ReturnStatements. */ - private 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 + 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()) + } - // 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) - } - } + override fun get(key: Node?): LatticeElement? { + return generalState[key] ?: declarationsState[key] } - // 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 - } - } + 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 true // We cover it in the respective subsets, so do not add this state again. } - return false - } - - /** - * Checks if the node performs an operation and an assignment at the same time e.g. with the - * operators +=, -=, *=, ... - */ - private fun isCompoundAssignment(currentNode: Node) = - currentNode is BinaryOperator && - currentNode.operatorCode in BinaryOperator.compoundOperators && - (currentNode.lhs as? DeclaredReferenceExpression)?.refersTo != null - - /** Checks if the node is a simple assignment of the form `var = ...` */ - private 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) = - currentNode is UnaryOperator && - (currentNode.operatorCode == "++" || currentNode.operatorCode == "--") && - (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 - */ - private fun loopDetection( - currentNode: Node, - writtenDecl: Declaration?, - currentWritten: Node, - previousWrites: MutableMap>, - loopPoints: MutableMap>> - ): Boolean { - 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()) + override fun needsUpdate(other: State): Boolean { + return if (other is DFGPassState) { + generalState.needsUpdate(other.generalState) || + declarationsState.needsUpdate(other.declarationsState) + } else { + generalState.needsUpdate(other) } } - return writtenDecl != null && - ((previousWrites[writtenDecl]?.filter { it == currentWritten }?.size ?: 0) >= 2) - } - /** - * 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( - 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) - } + override fun push(newNode: Node, newLatticeElement: LatticeElement?): Boolean { + return generalState.push(newNode, newLatticeElement) } - return result - } - private fun Node.hasOuterScopeOf(node: Node): Boolean { - var parentScope = node.scope?.parent - while (parentScope != null) { - if (this.scope == parentScope) { - return true - } - parentScope = parentScope.parent + /** Pushes the [newNode] and its [newLatticeElement] to the [declarationsState]. */ + fun pushToDeclarationsState( + newNode: Declaration, + newLatticeElement: LatticeElement? + ): Boolean { + return declarationsState.push(newNode, newLatticeElement) } - return false } } 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..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 @@ -25,16 +25,15 @@ */ 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.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.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 +41,14 @@ 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(ctx: TranslationContext) : ComponentPass(ctx) { + 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) } } @@ -59,23 +57,23 @@ class DFGPass : Pass() { // 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) 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 // 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) @@ -90,11 +88,12 @@ class DFGPass : Pass() { // Declarations is FieldDeclaration -> handleFieldDeclaration(node) is FunctionDeclaration -> handleFunctionDeclaration(node) + is TupleDeclaration -> handleTupleDeclaration(node) is VariableDeclaration -> handleVariableDeclaration(node) } } - 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 { @@ -119,16 +118,28 @@ class DFGPass : Pass() { /** * 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]. */ - private fun handleMemberExpression( + protected fun handleMemberExpression( node: MemberExpression, inferDfgForUnresolvedCalls: Boolean ) { if (node.refersTo == null && inferDfgForUnresolvedCalls) { node.addPrevDFG(node.base) } else { - handleDeclaredReferenceExpression(node) + handleReference(node) + } + } + + /** + * 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)) + } } } @@ -136,7 +147,7 @@ class DFGPass : Pass() { * 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) } } @@ -144,14 +155,14 @@ class DFGPass : Pass() { * 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) } } @@ -159,7 +170,7 @@ class DFGPass : Pass() { * 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) } } @@ -171,14 +182,14 @@ class DFGPass : Pass() { * 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) { - if (node.iterable != null) { + protected fun handleForEachStatement(node: ForEachStatement) { + 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) } @@ -188,7 +199,7 @@ class DFGPass : Pass() { * 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) } } @@ -197,7 +208,7 @@ class DFGPass : Pass() { * [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, @@ -210,7 +221,7 @@ class DFGPass : Pass() { * [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, @@ -223,7 +234,7 @@ class DFGPass : Pass() { * 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, @@ -236,7 +247,7 @@ class DFGPass : Pass() { * 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, @@ -248,7 +259,7 @@ class DFGPass : Pass() { * 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 == "--") { @@ -261,7 +272,7 @@ class DFGPass : Pass() { * 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) } } @@ -269,28 +280,23 @@ class DFGPass : Pass() { * 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) } } /** * Adds the DFG edges for an [InitializerListExpression]. All values in the initializer flow to * this expression. - * - * TODO: This change seems to have performance issues! */ - private fun handleInitializerListExpression(node: InitializerListExpression) { - node.initializers.forEach { - it.registerTypeListener(node) - node.addPrevDFG(it) - } + protected fun handleInitializerListExpression(node: InitializerListExpression) { + node.initializers.forEach { node.addPrevDFG(it) } } /** * 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) } } @@ -298,17 +304,17 @@ class DFGPass : Pass() { * 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) } } /** - * 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. */ - private fun handleDeclaredReferenceExpression(node: DeclaredReferenceExpression) { + protected fun handleReference(node: Reference) { node.refersTo?.let { when (node.access) { AccessValues.WRITE -> node.addNextDFG(it) @@ -325,23 +331,21 @@ class DFGPass : Pass() { * 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) { - node.thenExpr?.let { node.addPrevDFG(it) } - node.elseExpr?.let { node.addPrevDFG(it) } + protected fun handleConditionalExpression(node: ConditionalExpression) { + 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]`. */ - private 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. - */ - private 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) } } @@ -349,7 +353,7 @@ class DFGPass : Pass() { * 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) } @@ -358,20 +362,11 @@ class DFGPass : Pass() { // 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) } } - "*=", - "/=", - "%=", - "+=", - "-=", - "<<=", - ">>=", - "&=", - "^=", - "|=" -> { + in node.language?.compoundAssignmentOperators ?: setOf() -> { node.lhs.let { node.addPrevDFG(it) node.addNextDFG(it) @@ -388,7 +383,7 @@ class DFGPass : Pass() { /** * 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) } } @@ -401,11 +396,15 @@ class DFGPass : Pass() { 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) + } } } } @@ -415,11 +414,11 @@ class DFGPass : Pass() { * - from base (if available) to the CallExpression * - from all arguments to the CallExpression */ - private fun handleUnresolvedCalls(call: CallExpression) { + protected 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/main/kotlin/de/fraunhofer/aisec/cpg/passes/EdgeCachePass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EdgeCachePass.kt index fb4b77631c..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 @@ -25,7 +25,8 @@ */ package de.fraunhofer.aisec.cpg.passes -import de.fraunhofer.aisec.cpg.TranslationResult +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 import de.fraunhofer.aisec.cpg.processing.IVisitor @@ -81,34 +82,34 @@ object Edges { * * The cache itself is stored in the [Edges] object. */ -class EdgeCachePass : Pass() { - override fun accept(result: TranslationResult) { +class EdgeCachePass(ctx: TranslationContext) : ComponentPass(ctx) { + 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) } } ) } } - 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) @@ -120,7 +121,7 @@ class EdgeCachePass : Pass() { } } - 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 01a0360ae2..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 @@ -25,12 +25,11 @@ */ package de.fraunhofer.aisec.cpg.passes -import de.fraunhofer.aisec.cpg.TranslationResult +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 @@ -38,10 +37,12 @@ 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.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.* import org.slf4j.LoggerFactory @@ -59,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 @@ -71,26 +72,26 @@ import org.slf4j.LoggerFactory */ @Suppress("MemberVisibilityCanBePrivate") @DependsOn(CallResolver::class) -open class EvaluationOrderGraphPass : Pass() { +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(it) } + map[IncludeDeclaration::class.java] = { doNothing() } map[TranslationUnitDeclaration::class.java] = { handleTranslationUnitDeclaration(it as TranslationUnitDeclaration) } @@ -101,20 +102,17 @@ open class EvaluationOrderGraphPass : Pass() { map[FunctionDeclaration::class.java] = { handleFunctionDeclaration(it as FunctionDeclaration) } + map[TupleDeclaration::class.java] = { handleTupleDeclaration(it as TupleDeclaration) } map[VariableDeclaration::class.java] = { handleVariableDeclaration(it as VariableDeclaration) } 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[ArrayRangeExpression::class.java] = { - handleArrayRangeExpression(it as ArrayRangeExpression) + 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 : Pass() { 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,11 +155,11 @@ open class EvaluationOrderGraphPass : Pass() { 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) } } - private fun doNothing(node: Node) { + protected fun doNothing() { // Nothing to do for this node type } @@ -173,26 +168,26 @@ 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) } /** * 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. */ - private fun removeUnreachableEOGEdges(tu: TranslationUnitDeclaration) { - val eognodes = - SubgraphWalker.flattenAST(tu) - .filter { it.prevEOG.isNotEmpty() || it.nextEOG.isNotEmpty() } - .toMutableList() + protected fun removeUnreachableEOGEdges(tu: TranslationUnitDeclaration) { + // 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 || @@ -200,12 +195,14 @@ open class EvaluationOrderGraphPass : Pass() { 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) } @@ -240,6 +237,13 @@ open class EvaluationOrderGraphPass : Pass() { 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) @@ -261,7 +265,7 @@ open class EvaluationOrderGraphPass : Pass() { // 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) @@ -298,7 +302,7 @@ open class EvaluationOrderGraphPass : Pass() { pushToEOG(node) } - protected fun handleFunctionDeclaration(node: FunctionDeclaration) { + protected open fun handleFunctionDeclaration(node: FunctionDeclaration) { // reset EOG currentPredecessors.clear() var needToLeaveRecord = false @@ -345,18 +349,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) } } @@ -367,7 +371,7 @@ open class EvaluationOrderGraphPass : Pass() { * 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 @@ -412,7 +416,7 @@ open class EvaluationOrderGraphPass : Pass() { 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) @@ -420,7 +424,7 @@ open class EvaluationOrderGraphPass : Pass() { pushToEOG(node) } - protected fun handleArrayCreationExpression(node: ArrayCreationExpression) { + protected fun handleNewArrayExpression(node: NewArrayExpression) { for (dimension in node.dimensions) { createEOG(dimension) } @@ -428,10 +432,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) } @@ -517,7 +521,7 @@ open class EvaluationOrderGraphPass : Pass() { 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) @@ -533,53 +537,42 @@ open class EvaluationOrderGraphPass : Pass() { } protected fun handleUnaryOperator(node: UnaryOperator) { - val input = node.input - if (input != null) { - createEOG(input) - } + // 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") { - 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) - } - } - pushToEOG(node) - if (catchingScope is TryScope) { - catchingScope.catchesOrRelays[throwType] = ArrayList(currentPredecessors) - } else if (catchingScope is FunctionScope) { - catchingScope.catchesOrRelays[throwType] = ArrayList(currentPredecessors) - } - currentPredecessors.clear() + handleThrowOperator(node) } else { - pushToEOG(node) + handleUnspecificUnaryOperator(node) } } - protected fun handleCompoundStatementExpression(node: CompoundStatementExpression) { - createEOG(node.statement) + protected fun handleThrowOperator(node: UnaryOperator) { + val input = node.input + createEOG(input) + + 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) } @@ -599,24 +592,22 @@ 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) - ) { + } else if (throwType.isDerivedFrom(catchParam.type)) { currentPredecessors.addAll(eogEdges) toRemove.add(throwType) } } - toRemove.forEach { catchesOrRelays.remove(it) } + toRemove.forEach { catchesOrRelays?.remove(it) } createEOG(catchClause.body) tmpEOGNodes.addAll(currentPredecessors) } @@ -627,26 +618,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 @@ -684,8 +678,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) } } @@ -799,7 +793,7 @@ open class EvaluationOrderGraphPass : Pass() { * @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) @@ -808,14 +802,14 @@ open class EvaluationOrderGraphPass : Pass() { next.addPrevEOG(propertyEdge) } - private fun addMultipleIncomingEOGEdges(prevs: List, next: Node) { + protected fun addMultipleIncomingEOGEdges(prevs: List, next: Node) { prevs.forEach { prev -> addEOGEdge(prev, next) } } protected fun handleSynchronizedStatement(node: SynchronizedStatement) { createEOG(node.expression) pushToEOG(node) - createEOG(node.blockStatement) + createEOG(node.block) } protected fun handleConditionalExpression(node: ConditionalExpression) { @@ -825,11 +819,11 @@ open class EvaluationOrderGraphPass : Pass() { 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) } @@ -931,9 +925,9 @@ open class EvaluationOrderGraphPass : Pass() { 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) { @@ -974,7 +968,7 @@ open class EvaluationOrderGraphPass : Pass() { } 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 3b4e6dd07a..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 @@ -25,29 +25,28 @@ */ package de.fraunhofer.aisec.cpg.passes -import de.fraunhofer.aisec.cpg.TranslationResult +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 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(ctx: TranslationContext) : TranslationUnitPass(ctx) { + override fun accept(tu: TranslationUnitDeclaration) { + val file = tu.name.toString() + tu.file = file + 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(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/ImportResolver.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ImportResolver.kt index 4d3afeb017..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 @@ -25,11 +25,9 @@ */ package de.fraunhofer.aisec.cpg.passes -import de.fraunhofer.aisec.cpg.TranslationResult -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 @@ -38,7 +36,7 @@ import java.util.* import java.util.regex.Pattern @DependsOn(TypeHierarchyResolver::class) -open class ImportResolver : Pass() { +open class ImportResolver(ctx: TranslationContext) : ComponentPass(ctx) { protected val records: MutableList = ArrayList() protected val importables: MutableMap = HashMap() @@ -47,8 +45,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) { @@ -59,9 +57,11 @@ open class ImportResolver : Pass() { } } - 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("(?.*)\\.(?.*)") @@ -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..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,100 +25,70 @@ */ 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.* import de.fraunhofer.aisec.cpg.frontends.Language import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend -import de.fraunhofer.aisec.cpg.passes.order.* +import de.fraunhofer.aisec.cpg.frontends.TranslationException +import de.fraunhofer.aisec.cpg.graph.* +import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration +import de.fraunhofer.aisec.cpg.passes.order.RequiredFrontend 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(ctx: TranslationContext) : Pass(ctx) - /** - * 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(ctx: TranslationContext) : Pass(ctx) - /** - * 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(ctx: TranslationContext) : Pass(ctx) - 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 + +/** + * 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(final override val ctx: TranslationContext) : + Consumer, ContextProvider { + var name: String + protected set - lateinit var scopeManager: ScopeManager - protected var config: TranslationConfiguration? = null + val config: TranslationConfiguration = ctx.config + val scopeManager: ScopeManager = ctx.scopeManager + val typeManager: TypeManager = ctx.typeManager 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() - /** - * 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 { - 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. @@ -126,7 +96,7 @@ abstract class Pass protected constructor() : Consumer { * @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) { @@ -136,6 +106,91 @@ 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>, + ctx: TranslationContext, + 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(ctx) + ?: throw TranslationException("Could not create prototype pass") + + when (prototype) { + is TranslationResultPass -> { + executePass((prototype as TranslationResultPass)::class, ctx, result, executedFrontends) + } + is ComponentPass -> { + for (component in result.components) { + executePass((prototype as ComponentPass)::class, ctx, component, executedFrontends) + } + } + is TranslationUnitPass -> { + for (component in result.components) { + for (tu in component.translationUnits) { + executePass( + (prototype as TranslationUnitPass)::class, + ctx, + tu, + executedFrontends + ) + } + } + } + } +} + +inline fun executePass( + cls: KClass>, + ctx: TranslationContext, + target: T, + executedFrontends: Collection> +): Pass? { + val language = + if (target is LanguageProvider) { + target.language + } else { + null + } + + val realClass = checkForReplacement(cls, language, ctx.config) + + val pass = realClass.primaryConstructor?.call(ctx) + 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/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-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/main/kotlin/de/fraunhofer/aisec/cpg/passes/StatisticsCollectionPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/StatisticsCollectionPass.kt index 62a4a040d1..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,6 +25,7 @@ */ package de.fraunhofer.aisec.cpg.passes +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 @@ -36,13 +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 : Pass() { +class StatisticsCollectionPass(ctx: TranslationContext) : TranslationResultPass(ctx) { - /** 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(ctx.scopeManager) walker.registerHandler { _: RecordDeclaration?, _: Node?, currNode: Node? -> nodes++ if (currNode is ProblemNode) { @@ -50,12 +51,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 b1db052099..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 @@ -25,14 +25,23 @@ */ package de.fraunhofer.aisec.cpg.passes +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 -abstract class SymbolResolverPass : Pass() { +/** + * 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 @@ -42,23 +51,23 @@ 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) + val type = node.objectType(node.name) enumMap.putIfAbsent(type, node) } } /** Caches all TemplateDeclarations in [templateList] */ - protected fun findTemplates(node: Node) { + protected fun findTemplates(node: Node?) { if (node is TemplateDeclaration) { templateList.add(node) } @@ -68,7 +77,7 @@ abstract class SymbolResolverPass : Pass() { protected fun FunctionDeclaration.matches( name: Name, returnType: Type, - signature: List + signature: List ): Boolean { val thisReturnType = if (this.returnTypes.isEmpty()) { @@ -90,7 +99,7 @@ abstract class SymbolResolverPass : Pass() { /** * 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 4cd9b87a33..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,12 +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 @@ -46,7 +47,7 @@ import de.fraunhofer.aisec.cpg.graph.types.Type */ fun addRecursiveDefaultTemplateArgs( constructExpression: ConstructExpression, - template: ClassTemplateDeclaration + template: RecordTemplateDeclaration ) { var templateParameters: Int do { @@ -83,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 } } @@ -108,46 +109,55 @@ fun handleExplicitTemplateParameters( * default (no recursive) */ fun applyMissingParams( - template: ClassTemplateDeclaration, + template: RecordTemplateDeclaration, constructExpression: ConstructExpression, 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 - constructExpression.addTemplateParameter( - templateParametersExplicitInitialization[missingParam]!!, - TemplateDeclaration.TemplateInitialization.DEFAULT + with(constructExpression) { + val missingParams: List = + template.parameterDefaults.subList( + constructExpression.templateParameters.size, + template.parameterDefaults.size ) - // 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 - ) + for (m in missingParams) { + var missingParam = m + if (missingParam is Reference) { + missingParam = missingParam.refersTo } - } 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) { - (constructExpression.type as ObjectType).addGeneric( - (templateParametersExplicitInitialization[missingParam] as TypeExpression?) - ?.type - ) + 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)) + } } } } @@ -162,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 && @@ -177,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 b42f1cd88c..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 @@ -25,20 +25,22 @@ */ package de.fraunhofer.aisec.cpg.passes -import de.fraunhofer.aisec.cpg.TranslationResult +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 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.helpers.SubgraphWalker +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.* /** - * 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 @@ -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() { +@DependsOn(TypeResolver::class) +open class TypeHierarchyResolver(ctx: TranslationContext) : ComponentPass(ctx) { 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) { @@ -69,40 +72,43 @@ open class TypeHierarchyResolver : Pass() { } 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 } - - translationResult.translationUnits.forEach { SubgraphWalker.refreshType(it) } } 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) } } } ) } - private fun getAllMethodsFromSupertypes( + protected fun getAllMethodsFromSupertypes( supertypeRecords: Set ): List { return supertypeRecords.map { it.methods }.flatten() } - protected fun findSupertypeRecords(recordDecl: RecordDeclaration): Set { - val superTypeDeclarations = recordDecl.superTypes.mapNotNull { recordMap[it.name] }.toSet() - recordDecl.superTypeDeclarations = superTypeDeclarations + protected fun findSupertypeRecords( + recordDeclaration: RecordDeclaration + ): Set { + val 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/TypeResolver.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TypeResolver.kt index 88fa875a79..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 @@ -25,220 +25,40 @@ */ package de.fraunhofer.aisec.cpg.passes -import de.fraunhofer.aisec.cpg.TranslationResult -import de.fraunhofer.aisec.cpg.graph.HasType -import de.fraunhofer.aisec.cpg.graph.HasType.SecondaryTypeEdge +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.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 +import de.fraunhofer.aisec.cpg.processing.IVisitor +import de.fraunhofer.aisec.cpg.processing.strategy.Strategy -@DependsOn(CallResolver::class) -open class TypeResolver : Pass() { - 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 - */ - private 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]!!) { - typeState[type.root]?.add(type) - addType((type as SecondOrderType).elementType) - } - } else { - addType(type.root) - addType(type) - } - } - - protected fun removeDuplicateTypes() { - val typeManager = TypeManager.getInstance() - // 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 translationResult - */ - override fun accept(translationResult: TranslationResult) { - removeDuplicateTypes() - val walker = IterativeGraphWalker() - walker.registerOnNodeVisit(::ensureUniqueType) - walker.registerOnNodeVisit(::handle) - walker.registerOnNodeVisit(::ensureUniqueSecondaryTypeEdge) - - for (tu in translationResult.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) - // 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? - 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 types = - if (type.isFirstOrderType) { - typeState.keys - } else { - typeState.computeIfAbsent(type.root, ::mutableListOf) - } - updateType(node, types) - node.updatePossibleSubtypes(ensureUniqueSubTypes(node.possibleSubTypes)) - } - } - - /** - * 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] - */ - 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) - * - * @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 +/** + * 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) { + override fun accept(component: Component) { + 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 + } + } } } - } + ) } override fun cleanup() { - firstOrderTypes.clear() - typeState.clear() - TypeManager.reset() + // Nothing to do } } 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..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 @@ -25,18 +25,19 @@ */ package de.fraunhofer.aisec.cpg.passes -import de.fraunhofer.aisec.cpg.TranslationResult +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.* 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 +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 @@ -45,30 +46,26 @@ 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 : SymbolResolverPass() { - - override fun accept(result: TranslationResult) { - scopeManager = result.scopeManager - config = result.config +open class VariableUsageResolver(ctx: TranslationContext) : SymbolResolverPass(ctx) { + 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) } walker.registerHandler { node, _ -> findRecords(node) } walker.registerHandler { node, _ -> findEnums(node) } walker.iterate(currentTU) @@ -76,24 +73,25 @@ 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) } } - private 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: Reference, + type: FunctionPointerType + ): ValueDeclaration { val parent = reference.name.parent return handleUnknownFunction( @@ -103,35 +101,54 @@ open class VariableUsageResolver : SymbolResolverPass() { null }, reference.name, - fptrType + type ) } - private fun resolveLocalVarUsage( + protected 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 + 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 - // 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) { - recordDeclType = TypeParser.createFrom(currentClass.name, currentClass.language) + 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 @@ -179,18 +196,18 @@ open class VariableUsageResolver : SymbolResolverPass() { * 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 - 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) + current.objectType(parentName) } else { - return UnknownType.getUnknownType() + current.unknownType() } } - private fun resolveFieldUsages(curClass: RecordDeclaration?, parent: Node?, current: Node) { + protected fun resolveFieldUsages(curClass: RecordDeclaration?, parent: Node?, current: Node?) { if (current !is MemberExpression) { return } @@ -206,8 +223,8 @@ open class VariableUsageResolver : SymbolResolverPass() { } 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 @@ -220,7 +237,7 @@ open class VariableUsageResolver : SymbolResolverPass() { "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.objectType(Any::class.java.name) } else { // We need to connect this super reference to the receiver of this // method @@ -232,19 +249,22 @@ open class VariableUsageResolver : SymbolResolverPass() { 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 { // 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.objectType(Any::class.java.name) base.type = objectType } } else { - baseTarget = resolveBase(current.base as DeclaredReferenceExpression) + baseTarget = resolveBase(current.base as Reference) base.refersTo = baseTarget } if (baseTarget is EnumDeclaration) { @@ -255,13 +275,13 @@ open class VariableUsageResolver : SymbolResolverPass() { return } } else if (baseTarget is RecordDeclaration) { - var baseType = TypeParser.createFrom(baseTarget.name, baseTarget.language) + 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 = TypeParser.createFrom(fqnResolvedType, baseTarget.language) + baseType = baseTarget.objectType(fqnResolvedType) } } current.refersTo = resolveMember(baseType, current) @@ -272,13 +292,13 @@ open class VariableUsageResolver : SymbolResolverPass() { 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.objectType(fqnResolvedType) } } current.refersTo = resolveMember(baseType, current) } - private fun resolveBase(reference: DeclaredReferenceExpression): Declaration? { + protected fun resolveBase(reference: Reference): Declaration? { val declaration = scopeManager.resolveReference(reference) if (declaration != null) { return declaration @@ -294,10 +314,7 @@ open class VariableUsageResolver : SymbolResolverPass() { } } - private 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 @@ -306,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 } @@ -322,21 +339,21 @@ open class VariableUsageResolver : SymbolResolverPass() { .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 - private fun handleUnknownField(base: Type, name: Name, type: Type): FieldDeclaration? { + protected fun handleUnknownField(base: Type, ref: Reference): 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 = @@ -345,7 +362,7 @@ open class VariableUsageResolver : SymbolResolverPass() { } else { "class" } - val record = base.startInference().inferRecordDeclaration(base, currentTU, kind) + val record = base.startInference(ctx).inferRecordDeclaration(base, currentTU, kind) // update the record map if (record != null) recordMap[base.name] = record } @@ -366,7 +383,8 @@ open class VariableUsageResolver : SymbolResolverPass() { val declaration = recordDeclaration.newFieldDeclaration( name.localName, - type, + // we will set the type later through the type inference observer + unknownType(), listOf(), "", null, @@ -375,6 +393,11 @@ open class VariableUsageResolver : SymbolResolverPass() { ) 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 } } @@ -385,7 +408,7 @@ open class VariableUsageResolver : SymbolResolverPass() { * 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 @@ -404,7 +427,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(ctx) .createInferredFunctionDeclaration( name, null, @@ -415,6 +438,6 @@ open class VariableUsageResolver : SymbolResolverPass() { } companion object { - private val log = LoggerFactory.getLogger(VariableUsageResolver::class.java) + protected val log = LoggerFactory.getLogger(VariableUsageResolver::class.java) } } 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..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 @@ -25,15 +25,17 @@ */ 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 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 @@ -48,95 +50,132 @@ 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 { - val log: Logger = LoggerFactory.getLogger(Inference::class.java) +class Inference(val start: Node, override val ctx: TranslationContext) : + LanguageProvider, ScopeProvider, IsInferredProvider, ContextProvider { - override val language: Language? + override val language: Language<*>? get() = start.language + override val isInferred: Boolean + get() = true + + val scopeManager = ctx.scopeManager + val typeManager = ctx.typeManager + + override val scope: Scope? + get() = scopeManager.currentScope + fun createInferredFunctionDeclaration( name: CharSequence?, code: String?, isStatic: Boolean, signature: List, returnType: Type?, + hint: CallExpression? = null ): 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) { + val inferred: FunctionDeclaration = + if (record != null) { + newMethodDeclaration(name ?: "", code, isStatic, record) + } else { + newFunctionDeclaration(name ?: "", code) + } - 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" - } + debugWithFileLocation( + hint, + log, + "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) + 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 - val declarationHolder = (record ?: tu) - val parameters = createInferredParameters(signature) - val inferred: FunctionDeclaration = 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 - } - - // TODO: Handle multiple return values? - if (declarationHolder is RecordDeclaration) { - declarationHolder.addMethod(inferred as MethodDeclaration) - if (isStatic) { - declarationHolder.staticImports.add(inferred) + // "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" } - } else { - declarationHolder?.addDeclaration(inferred) - } - return inferred + inferred + } } fun createInferredConstructor(signature: List): ConstructorDeclaration { - val inferred = - newConstructorDeclaration( - start.name.localName, - "", - start as? RecordDeclaration, - ) - inferred.parameters = createInferredParameters(signature) + return inferInScopeOf(start) { + val inferred = + newConstructorDeclaration( + start.name.localName, + "", + start as? RecordDeclaration, + ) + createInferredParameters(inferred, signature) - (start as? RecordDeclaration)?.addConstructor(inferred) - return inferred + scopeManager.addDeclaration(inferred) + + inferred + } } - 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) + /** + * 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) + } + + 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) + + for (i in signature.indices) { + val targetType = signature[i] ?: UnknownType.getUnknownType(function.language) + val paramName = generateParamName(i, targetType) + val param = newParameterDeclaration(paramName, targetType, false, "") + param.argumentIndex = i + + scopeManager.addDeclaration(param) + } + + scopeManager.leaveScope(function) } - return params } /** Generates a name for an inferred function parameter based on the type. */ @@ -165,7 +204,7 @@ class Inference(val start: Node) : LanguageProvider, IsInferredProvider { } } val paramName = StringBuilder() - while (!hierarchy.isEmpty()) { + while (hierarchy.isNotEmpty()) { val part = hierarchy.pop() if (part.isEmpty()) { continue @@ -183,7 +222,7 @@ class Inference(val start: Node) : LanguageProvider, IsInferredProvider { return paramName.toString() } - fun inferNonTypeTemplateParameter(name: String): ParamVariableDeclaration { + private fun inferNonTypeTemplateParameter(name: String): ParameterDeclaration { val expr = start as? Expression ?: throw UnsupportedOperationException( @@ -191,17 +230,16 @@ class Inference(val start: Node) : LanguageProvider, IsInferredProvider { ) // Non-Type Template Parameter - return newParamVariableDeclaration(name, expr.type, false, name) + return newParameterDeclaration(name, expr.type, false, name) } - fun inferTemplateParameter( + private fun inferTemplateParameter( name: String, - ): TypeParamDeclaration { + ): TypeParameterDeclaration { val parameterizedType = ParameterizedType(name, language) - TypeManager.getInstance() - .addTypeParameter(start as? FunctionTemplateDeclaration, parameterizedType) + typeManager.addTypeParameter(start as FunctionTemplateDeclaration, parameterizedType) - val decl = newTypeParamDeclaration(name, name) + val decl = newTypeParameterDeclaration(name, name) decl.type = parameterizedType return decl @@ -211,7 +249,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 */ @@ -232,16 +269,16 @@ class Inference(val start: Node) : LanguageProvider, IsInferredProvider { val inferred = newFunctionTemplateDeclaration(name, code) inferred.isInferred = true - val inferredRealization: FunctionDeclaration = + val inferredRealization = if (record != null) { record.addDeclaration(inferred) - record.inferMethod(call) + record.inferMethod(call, ctx = ctx) } else { - tu!!.addDeclaration(inferred) - tu.inferFunction(call) + tu?.addDeclaration(inferred) + tu?.inferFunction(call, ctx = ctx) } - inferred.addRealization(inferredRealization) + inferredRealization?.let { inferred.addRealization(it) } var typeCounter = 0 var nonTypeCounter = 0 @@ -250,15 +287,15 @@ class Inference(val start: Node) : LanguageProvider, IsInferredProvider { // Template Parameter val inferredTypeIdentifier = "T$typeCounter" val typeParamDeclaration = - inferred.startInference().inferTemplateParameter(inferredTypeIdentifier) + inferred.startInference(ctx).inferTemplateParameter(inferredTypeIdentifier) typeCounter++ inferred.addParameter(typeParamDeclaration) } else if (node is Expression) { val inferredNonTypeIdentifier = "N$nonTypeCounter" val paramVariableDeclaration = - node.startInference().inferNonTypeTemplateParameter(inferredNonTypeIdentifier) - - paramVariableDeclaration.addPrevDFG(node) + node + .startInference(ctx) + .inferNonTypeTemplateParameter(inferredNonTypeIdentifier) node.addNextDFG(paramVariableDeclaration) nonTypeCounter++ inferred.addParameter(paramVariableDeclaration) @@ -269,8 +306,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, @@ -278,12 +314,12 @@ class Inference(val start: Node) : LanguageProvider, IsInferredProvider { 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" ) @@ -300,8 +336,80 @@ class Inference(val start: Node) : LanguageProvider, IsInferredProvider { return declaration } - override val isInferred: Boolean - get() = true + 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) { + Companion.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 + } + } + + /** + * This class implements a [HasType.TypeObserver] and uses the observed type to set the + * [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. + * + * 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. */ @@ -310,36 +418,58 @@ interface IsInferredProvider : MetadataProvider { } /** Returns a new [Inference] object starting from this node. */ -fun Node.startInference() = Inference(this) +fun Node.startInference(ctx: TranslationContext) = Inference(this, ctx) /** Tries to infer a [FunctionDeclaration] from a [CallExpression]. */ fun TranslationUnitDeclaration.inferFunction( call: CallExpression, - isStatic: Boolean = false + isStatic: Boolean = false, + ctx: TranslationContext ): FunctionDeclaration { - return Inference(this) + return Inference(this, ctx) .createInferredFunctionDeclaration( call.name.localName, call.code, isStatic, call.signature, // TODO: Is the call's type the return value's type? - call.type + call.type, + call + ) +} + +/** Tries to infer a [FunctionDeclaration] from a [CallExpression]. */ +fun NamespaceDeclaration.inferFunction( + call: CallExpression, + isStatic: Boolean = false, + ctx: TranslationContext +): FunctionDeclaration { + return Inference(this, ctx) + .createInferredFunctionDeclaration( + call.name, + call.code, + isStatic, + call.signature, + // TODO: Is the call's type the return value's type? + call.type, + call ) } /** Tries to infer a [MethodDeclaration] from a [CallExpression]. */ fun RecordDeclaration.inferMethod( call: CallExpression, - isStatic: Boolean = false + isStatic: Boolean = false, + ctx: TranslationContext ): MethodDeclaration { - return Inference(this) + return Inference(this, ctx) .createInferredFunctionDeclaration( call.name.localName, call.code, isStatic, call.signature, // TODO: Is the call's type the return value's type? - call.type + call.type, + call ) as MethodDeclaration } 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..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,11 +25,17 @@ */ 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 -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/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/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..7739aed58f 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,26 @@ * \______/ \__| \______/ * */ -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<>(); +abstract class IVisitor> { + @JvmField val visited = 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 + 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/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/GraphExamples.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/GraphExamples.kt index a91e4b77c4..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 @@ -25,13 +25,779 @@ */ 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.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 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()) + val language = config.languages.filterIsInstance().first() + return TestLanguageFrontend(language.namespaceDelimiter, language, ctx) + } + + fun getInferenceRecordPtr( + config: TranslationConfiguration = + TranslationConfiguration.builder() + .defaultPasses() + .registerLanguage(StructTestLanguage(".")) + .inferenceConfiguration( + InferenceConfiguration.builder().inferRecords(true).build() + ) + .build() + ) = + testFrontend(config).build { + translationResult { + translationUnit("record.cpp") { + // The main method + function("main", t("int")) { + body { + declare { variable("node", t("T").reference(POINTER)) } + 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() + ) = + 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 = + 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") } + + 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() + ) = + testFrontend(config).build { + translationResult { + 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() + ) = + testFrontend(config).build { + translationResult { + 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() + ) = + testFrontend(config).build { + translationResult { + 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) + ) + } assignAsExpr { literal(2, t("int")) }, + ref("b") { + location = + PhysicalLocation( + URI("conditional_expression.cpp"), + Region(5, 23, 5, 24) + ) + } assignAsExpr { 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() + ) = + testFrontend(config).build { + translationResult { + 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() + ) = + testFrontend(config).build { + translationResult { + 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() + ) = + testFrontend(config).build { + translationResult { + 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() + ) = + testFrontend(config).build { + translationResult { + 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() + ) = + testFrontend(config).build { + translationResult { + 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() + ) = + testFrontend(config).build { + translationResult { + 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() + ) = + testFrontend(config).build { + translationResult { + 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() + ) = + testFrontend(config).build { + translationResult { + 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() @@ -39,8 +805,8 @@ class GraphExamples { .registerLanguage(TestLanguage(".")) .build() ) = - TestLanguageFrontend(ScopeManager(), ".").build { - translationResult(config) { + testFrontend(config).build { + translationResult { translationUnit("RecordDeclaration.java") { namespace("compiling") { record("SimpleClass", "class") { @@ -85,12 +851,12 @@ 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")) } - constructor() { + constructor { isImplicit = true receiver = newVariableDeclaration("this", t("Dataflow")) body { returnStmt { isImplicit = true } } @@ -123,6 +889,7 @@ class GraphExamples { // The main method method("main") { + this.isStatic = true param("args", t("String[]")) body { declare { @@ -147,12 +914,12 @@ 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")) } - constructor() { + constructor { receiver = newVariableDeclaration("this", t("ShortcutClass")) isImplicit = true body { returnStmt { isImplicit = true } } @@ -218,6 +985,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/InferenceTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/InferenceTest.kt index 4360bd8320..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 @@ -25,57 +25,58 @@ */ 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.* 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 +import kotlin.test.* 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() - ) - } + 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 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-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/graph/FluentTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/FluentTest.kt index d7852eb94d..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 @@ -44,10 +43,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")) @@ -62,7 +60,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) } } @@ -70,7 +69,6 @@ class FluentTest { } } } - val tu = result.translationUnits.firstOrNull() // Let's assert that we did this correctly val main = result.functions["main"] @@ -83,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 @@ -131,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) @@ -139,17 +137,17 @@ class FluentTest { assertNotNull(lit1) assertEquals(1, lit1.value) - // Third line is the 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) @@ -158,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) @@ -169,9 +167,10 @@ class FluentTest { assertNotNull(lit2.scope) assertEquals(2, lit2.value) - VariableUsageResolver().accept(result) + 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 4c14aff028..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 @@ -25,30 +25,17 @@ */ 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 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.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 @@ -81,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) @@ -100,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") ) } @@ -116,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)) } @@ -143,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) @@ -159,15 +144,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)) } @@ -179,42 +174,45 @@ 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 BinaryOperator).lhs) - expected.add(((nestedThen.statements[0] as BinaryOperator).lhs as MemberExpression).base) - expected.add((nestedThen.statements[0] as BinaryOperator).rhs) - val nestedElse = thenStatement.elseStatement as CompoundStatement + 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 Block 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 Block).statements[0]) expected.add( - ((ifStatement.elseStatement as CompoundStatement).statements[0] as BinaryOperator).lhs + ((ifStatement.elseStatement as Block).statements[0] as AssignExpression).lhs.first() ) expected.add( - (((ifStatement.elseStatement as CompoundStatement).statements[0] as BinaryOperator).lhs + (((ifStatement.elseStatement as Block).statements[0] as AssignExpression).lhs.first() as MemberExpression) .base ) expected.add( - ((ifStatement.elseStatement as CompoundStatement).statements[0] as BinaryOperator).rhs + ((ifStatement.elseStatement as Block).statements[0] as AssignExpression).rhs.first() ) assertTrue(expected.containsAll(actual)) @@ -227,7 +225,7 @@ class ShortcutsTest { GraphExamples.getShortcutClass( TranslationConfiguration.builder() .defaultPasses() - .registerPass(EdgeCachePass()) + .registerPass() .registerLanguage(TestLanguage(".")) .build() ) @@ -239,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() @@ -261,11 +258,11 @@ 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 BinaryOperator) + as AssignExpression) .lhs + .first() val paramPassed2 = aAssignment2.followPrevDFGEdgesUntilHit { it is Literal<*> } assertEquals(1, paramPassed2.fulfilled.size) @@ -276,11 +273,11 @@ 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 BinaryOperator) + as AssignExpression) .lhs + .first() val paramPassed = attrAssignment.followPrevDFGEdgesUntilHit { it is Literal<*> } assertEquals(1, paramPassed.fulfilled.size) @@ -296,11 +293,11 @@ 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 BinaryOperator) + as AssignExpression) .lhs + .first() val paramPassed = attrAssignment.followPrevEOGEdgesUntilHit { it is Literal<*> } assertEquals(1, paramPassed.fulfilled.size) @@ -319,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 BinaryOperator && + it is AssignExpression && it.operatorCode == "=" && - (it.rhs 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) @@ -341,11 +337,11 @@ 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 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/frontends/LanguageFrontendTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/TypeTest.kt similarity index 50% rename from cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/LanguageFrontendTest.kt rename to cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/TypeTest.kt index 74f9be3cd2..fcc512f89e 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/LanguageFrontendTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/TypeTest.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,31 +23,36 @@ * \______/ \__| \______/ * */ -package de.fraunhofer.aisec.cpg.frontends +package de.fraunhofer.aisec.cpg.graph -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 de.fraunhofer.aisec.cpg.assertLocalName +import de.fraunhofer.aisec.cpg.frontends.TestLanguageFrontend +import de.fraunhofer.aisec.cpg.frontends.TranslationException import kotlin.test.Test -import kotlin.test.assertEquals +import org.junit.jupiter.api.assertThrows -internal class LanguageFrontendTest : BaseTest() { +class TypeTest { @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) + 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/WalkerTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/WalkerTest.kt index ee6d757367..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 @@ -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 = Block() - 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() 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..c452f6ee82 --- /dev/null +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/TupleDeclarationTest.kt @@ -0,0 +1,189 @@ +/* + * 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.Reference +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(newReference("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(newReference("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/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..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 @@ -25,15 +25,11 @@ */ 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 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.* @@ -41,20 +37,20 @@ import kotlin.test.* class AssignExpressionTest { @Test fun propagateSimple() { - with(TestLanguage()) { - val refA = newDeclaredReferenceExpression("a") - val refB = newDeclaredReferenceExpression("b") + with(TestLanguageFrontend()) { + 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)) // Type listeners should be configured - assertContains(refB.typeListeners, stmt) + assertContains(refB.typeObservers, stmt) - // Suddenly, we now we know the type of b. - refB.type = parseType("MyClass") - // It should now propagate to a - assertLocalName("MyClass", refA.type) + // Suddenly, we now we know the type of "b". + refB.type = objectType("MyClass") + // It should now propagate to the assigned type of "a" + assertContains(refA.assignedTypes, objectType("MyClass")) val assignments = stmt.assignments assertEquals(1, assignments.size) @@ -65,17 +61,18 @@ class AssignExpressionTest { fun propagateTuple() { with(TestLanguageFrontend()) { val result = build { - translationResult(TranslationConfiguration.builder().build()) { + translationResult { translationUnit { val func = function( "func", - returnTypes = listOf(parseType("MyClass"), parseType("error")) + returnTypes = listOf(objectType("MyClass"), objectType("error")) ) + 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) @@ -83,39 +80,44 @@ class AssignExpressionTest { val stmt = newAssignExpression(lhs = listOf(refA, refErr), rhs = listOf(call)) - body = newCompoundStatement() - body as CompoundStatement += stmt + body = newBlock() + body as Block += stmt } } } } 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().accept(result) - - 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 d3bc26420c..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 @@ -25,16 +25,17 @@ */ package de.fraunhofer.aisec.cpg.graph.types -import de.fraunhofer.aisec.cpg.ScopeManager -import de.fraunhofer.aisec.cpg.TranslationConfiguration +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.expressions.DeclaredReferenceExpression +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.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 import kotlin.test.* @@ -43,7 +44,7 @@ class TypePropagationTest { fun testBinopTypePropagation() { val result = TestLanguageFrontend().build { - translationResult(TranslationConfiguration.builder().build()) { + translationResult { translationUnit("test") { function("main", t("int")) { body { @@ -61,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) @@ -75,50 +85,350 @@ class TypePropagationTest { @Test fun testAssignTypePropagation() { - // TODO: This test is related to issue 1071 (it models case 2). - val scopeManager = ScopeManager() + 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(scopeManager).build { - translationResult(TranslationConfiguration.builder().build()) { + frontend.build { + translationResult { translationUnit("test") { function("main", t("int")) { body { declare { variable("intVar", t("int")) {} } declare { variable("shortVar", t("short")) {} } ref("shortVar") assign ref("intVar") - returnStmt { literal(0) } + returnStmt { ref("shortVar") } } } } } } - VariableUsageResolver().accept(result) - val binaryOp = - (result.functions["main"]?.body as? CompoundStatement)?.statements?.get(2) - as? BinaryOperator - assertNotNull(binaryOp) + 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 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) + with(frontend) { + val main = result.functions["main"] + assertNotNull(main) - 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 assign = (main.body as? Block)?.statements?.get(2) as? AssignExpression + assertNotNull(assign) + + 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? Reference + assertNotNull(rhs) + assertIs(rhs.type) + assertLocalName("int", rhs.type) + assertEquals(32, (rhs.type as IntegerType).bitWidth) + + 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. + 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 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 Block).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 Block).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/graph/types/TypeTests.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/types/TypeTests.kt deleted file mode 100644 index aed2585c2c..0000000000 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/types/TypeTests.kt +++ /dev/null @@ -1,521 +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.BaseTest -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.graph.* -import java.nio.file.Path -import java.util.* -import kotlin.test.* - -internal class TypeTests : BaseTest() { - @Test - fun reference() { - val objectType: Type = IntegerType("int", 32, CPPLanguage(), NumericType.Modifier.SIGNED) - val pointerType: Type = PointerType(objectType, PointerType.PointerOrigin.POINTER) - val unknownType: Type = UnknownType.getUnknownType(CPPLanguage()) - val incompleteType: Type = IncompleteType() - val parameterList = - listOf(IntegerType("int", 32, CPPLanguage(), NumericType.Modifier.SIGNED)) - val functionPointerType: Type = - FunctionPointerType(parameterList, IncompleteType(), CPPLanguage()) - - // Test 1: ObjectType becomes PointerType containing the original ObjectType as ElementType - assertEquals( - PointerType(objectType, PointerType.PointerOrigin.POINTER), - objectType.reference(PointerType.PointerOrigin.POINTER) - ) - - // Test 2: Existing PointerType adds one level more of references as ElementType - assertEquals( - PointerType(pointerType, PointerType.PointerOrigin.POINTER), - pointerType.reference(PointerType.PointerOrigin.POINTER) - ) - - // Test 3: UnknownType cannot be referenced - assertEquals(unknownType, unknownType.reference(null)) - - // Test 4: IncompleteType can be refereced e.g. void* - assertEquals( - PointerType(incompleteType, PointerType.PointerOrigin.POINTER), - incompleteType.reference(PointerType.PointerOrigin.POINTER) - ) - - // Test 5: Create reference to function pointer = pointer to function pointer - assertEquals( - PointerType(functionPointerType, PointerType.PointerOrigin.POINTER), - functionPointerType.reference(PointerType.PointerOrigin.POINTER) - ) - } - - @Test - fun dereference() { - val objectType: Type = IntegerType("int", 32, CPPLanguage(), NumericType.Modifier.SIGNED) - val pointerType: Type = PointerType(objectType, PointerType.PointerOrigin.POINTER) - val unknownType: Type = UnknownType.getUnknownType(CPPLanguage()) - val incompleteType: Type = IncompleteType() - val parameterList = - listOf(IntegerType("int", 32, CPPLanguage(), NumericType.Modifier.SIGNED)) - val functionPointerType: Type = - FunctionPointerType(parameterList, IncompleteType(), CPPLanguage()) - - // Test 1: Dereferencing an ObjectType results in an UnknownType, since we cannot track the - // type - // of the corresponding memory - assertEquals(UnknownType.getUnknownType(CPPLanguage()), objectType.dereference()) - - // Test 2: Dereferencing a PointerType results in the corresponding elementType of the - // PointerType (can also be another PointerType) - assertEquals(objectType, pointerType.dereference()) - - // Test 3: Dereferecing unknown or incomplete type results in the same type - assertEquals(unknownType, unknownType.dereference()) - assertEquals(incompleteType, incompleteType.dereference()) - - // Test 5: Due to the definition in the C-Standard dereferencing function pointer yields the - // same function pointer - assertEquals(functionPointerType, functionPointerType.dereference()) - } - - @Test - 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, IncompleteType(), CPPLanguage()) - 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( - 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) - - // Test 7: Array of const pointer to static const char - typeString = "static const char * const somearray []" - result = TypeParser.createFrom(typeString, CPPLanguage()) - 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 = TypeParser.createFrom(typeString, CPPLanguage()) - 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 = TypeParser.createFrom(typeString, CPPLanguage()) - var generics: MutableList = ArrayList() - 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) - } - - /** - * Test for usage of getTypeStringFromDeclarator to determine function pointer raw type string - * - * @throws Exception Any exception thrown during the analysis process - */ - @Test - @Throws(Exception::class) - fun testFunctionPointerTypes() { - 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 oneParamType = - FunctionPointerType( - listOf(IntegerType("int", 32, CPPLanguage(), NumericType.Modifier.SIGNED)), - IncompleteType(), - CPPLanguage() - ) - val twoParamType = - FunctionPointerType( - listOf( - IntegerType("int", 32, CPPLanguage(), NumericType.Modifier.SIGNED), - IntegerType("unsigned long", 64, CPPLanguage(), NumericType.Modifier.UNSIGNED) - ), - IntegerType("int", 32, CPPLanguage(), NumericType.Modifier.SIGNED), - CPPLanguage() - ) - val variables = tu.variables - val localTwoParam = findByUniqueName(variables, "local_two_param") - assertNotNull(localTwoParam) - assertEquals(twoParamType, localTwoParam.type) - - val localOneParam = findByUniqueName(variables, "local_one_param") - assertNotNull(localOneParam) - assertEquals(oneParamType, localOneParam.type) - - val globalNoParam = findByUniqueName(variables, "global_no_param") - assertNotNull(globalNoParam) - assertEquals(noParamType, globalNoParam.type) - - val globalNoParamVoid = findByUniqueName(variables, "global_no_param_void") - assertNotNull(globalNoParamVoid) - assertEquals(noParamType, globalNoParamVoid.type) - - val globalTwoParam = findByUniqueName(variables, "global_two_param") - assertNotNull(globalTwoParam) - assertEquals(twoParamType, globalTwoParam.type) - - val globalOneParam = findByUniqueName(variables, "global_one_param") - assertNotNull(globalOneParam) - assertEquals(oneParamType, globalOneParam.type) - } - - @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) - } - - // 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) - ) - } - assertEquals( - Optional.empty(), - TypeManager.getInstance().getCommonType(listOf(root, level0b), provider) - ) - for (t in listOf(level0, level1, level2)) { - assertEquals( - Optional.empty(), - TypeManager.getInstance().getCommonType(listOf(t, level0b), provider) - ) - } - 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) - ) - } - @Test - @Throws(Exception::class) - fun graphTest() { - val topLevel = Path.of("src", "test", "resources", "types") - val result = analyze("cpp", topLevel, true) - val variableDeclarations = result.variables - - // Test PointerType chain with pointer - val regularInt = findByUniqueName(variableDeclarations, "regularInt") - val ptr = findByUniqueName(variableDeclarations, "ptr") - assertTrue(ptr.type is PointerType) - assertEquals((ptr.type as PointerType).elementType, regularInt.type) - - // Test type Propagation (auto) UnknownType - val unknown = findByUniqueName(variableDeclarations, "unknown") - assertEquals(UnknownType.getUnknownType(CPPLanguage()), unknown.type) - - // Test type Propagation auto - val propagated = findByUniqueName(variableDeclarations, "propagated") - assertEquals(regularInt.type, propagated.type) - } - - private fun getCommonTypeTestGeneral( - root: Type, - level0: Type, - level1: Type, - level1b: Type, - level2: Type, - unrelated: Type, - result: TranslationResult - ) { - /* - Type hierarchy: - Root - | - Level0 - / \ - Level1 Level1B - | - Level2 - */ - val provider = result.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) - ) - } - - // 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) - ) - } - - // 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) - ) - } - - // Level1 and Level1B have Level0 as common ancestor - assertEquals( - Optional.of(level0), - TypeManager.getInstance().getCommonType(listOf(level1, level1b), provider) - ) - - // Level2 and Level1B have Level0 as common ancestor - assertEquals( - Optional.of(level0), - TypeManager.getInstance().getCommonType(listOf(level2, level1b), provider) - ) - - // Level1 and Level2 have Level1 as common ancestor - assertEquals( - Optional.of(level1), - TypeManager.getInstance().getCommonType(listOf(level1, level2), provider) - ) - - // 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) - ) - } - } -} 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/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-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/ControlDependenceGraphPassTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPassTest.kt new file mode 100644 index 0000000000..eac598e55b --- /dev/null +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPassTest.kt @@ -0,0 +1,195 @@ +/* + * 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.TypeManager +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.expressions.Block +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 Block).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 Block).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-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 60% 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..5b3228ed42 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 lhsA = binaryOperatorAssignment.lhs.first() as Reference + val rhsA = binaryOperatorAddition.lhs as Reference + val b = TestUtils.findByUniqueName(result.refs, "b") assertNotNull(b) val literal0 = result.literals[{ it.value == 0 }] @@ -231,7 +148,7 @@ internal 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)) @@ -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,237 @@ 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 Reference + val literal4 = result.literals[{ it.value == 4 }] + assertNotNull(literal4) + val a4 = ab.prevDFG.first { it is Reference } + 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 Reference 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 Reference 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 Reference + 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 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) }] + 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. + } + + @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/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")) } + } + } + } + } + } + } +} 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..8270b09cf3 --- /dev/null +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ReplaceTest.kt @@ -0,0 +1,92 @@ +/* + * 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.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 +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(ctx: TranslationContext): TestLanguageFrontend { + return ReplaceTestLanguageFrontend() + } + } + + class ReplacedPass(ctx: TranslationContext) : EvaluationOrderGraphPass(ctx) + + @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/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..b4984cd78e --- /dev/null +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/UnresolvedDFGPassTest.kt @@ -0,0 +1,167 @@ +/* + * 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.GraphExamples.Companion.testFrontend +import de.fraunhofer.aisec.cpg.InferenceConfiguration +import de.fraunhofer.aisec.cpg.TranslationConfiguration +import de.fraunhofer.aisec.cpg.TranslationResult +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.Literal +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Reference +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? 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 + ) + 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): 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") } + 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", 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")) { literal(4, t("int")) } + } + } + declare { + variable("duc", t("DfgUnresolvedCalls")) { + new { + construct("DfgUnresolvedCalls") { + literal(3, t("int")) + } + } + } + } + declare { + variable("i", t("int")) { + memberCall("knownFunction", ref("duc")) { + 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 83% 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..01f1534076 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,29 +26,26 @@ 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 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 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") @@ -77,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) @@ -88,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 6068a24d66..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 @@ -25,21 +25,13 @@ */ 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.frontends.TranslationException -import de.fraunhofer.aisec.cpg.frontends.cpp.CPPLanguage -import de.fraunhofer.aisec.cpg.frontends.cpp.CXXLanguageFrontend +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.* -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 @@ -48,51 +40,12 @@ 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 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(), - TranslationConfiguration.builder().build(), - s1, - ) + TestLanguageFrontend("::", TestLanguage(), TranslationContext(config, s1, tm)) s1.resetToGlobal(frontend1.newTranslationUnitDeclaration("f1.cpp", null)) // build a namespace declaration in f1.cpp with the namespace A @@ -104,11 +57,7 @@ internal class ScopeManagerTest : BaseTest() { val s2 = ScopeManager() val frontend2 = - CXXLanguageFrontend( - CPPLanguage(), - TranslationConfiguration.builder().build(), - s2, - ) + TestLanguageFrontend("::", TestLanguage(), TranslationContext(config, s2, tm)) s2.resetToGlobal(frontend2.newTranslationUnitDeclaration("f1.cpp", null)) // and do the same in the other file @@ -121,11 +70,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, - ) + TestLanguageFrontend("::", TestLanguage(), TranslationContext(config, final, tm)) final.mergeFrom(listOf(s1, s2)) // in the final scope manager, there should only be one NameScope "A" @@ -146,14 +91,13 @@ 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( - 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) @@ -163,11 +107,7 @@ internal class ScopeManagerTest : BaseTest() { fun testScopeFQN() { val s = ScopeManager() val frontend = - CXXLanguageFrontend( - CPPLanguage(), - TranslationConfiguration.builder().build(), - s, - ) + TestLanguageFrontend("::", TestLanguage(), TranslationContext(config, s, TypeManager())) s.resetToGlobal(frontend.newTranslationUnitDeclaration("file.cpp", null)) assertNull(s.currentNamespace) 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..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,23 @@ 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 @Throws( @@ -144,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/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-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/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 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 16f28fa977..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 @@ -27,14 +27,10 @@ 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 -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 @@ -42,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 { @@ -137,7 +131,6 @@ object TestUtils { .disableCleanup() .debugParser(true) .failOnError(true) - .typeSystemActiveInFrontend(false) .useParallelFrontends(true) .defaultLanguages() if (usePasses) { @@ -194,13 +187,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 @@ -224,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") } @@ -235,15 +221,16 @@ 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) } /** * 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. @@ -253,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) } } @@ -285,22 +272,30 @@ 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) } } } 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-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..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 @@ -25,15 +25,13 @@ */ 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.TypeManager import de.fraunhofer.aisec.cpg.graph.Node 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.graph.unknownType import de.fraunhofer.aisec.cpg.sarif.PhysicalLocation import java.io.File import java.util.function.Supplier @@ -43,10 +41,13 @@ 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 + final 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), @@ -57,47 +58,52 @@ class TestLanguage(namespaceDelimiter: String = "::") : Language = TestLanguage(namespaceDelimiter), + ctx: TranslationContext = + TranslationContext( + TranslationConfiguration.builder().build(), + ScopeManager(), + TypeManager() + ), +) : 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 unknownType() + } + + 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") } } -class TestHandler : - Handler( - Supplier { ProblemExpression() }, - TestLanguageFrontend() - ) +class TestHandler(frontend: TestLanguageFrontend) : + Handler(Supplier { ProblemExpression() }, frontend) diff --git a/cpg-language-cxx/build.gradle.kts b/cpg-language-cxx/build.gradle.kts new file mode 100644 index 0000000000..ba55a29ba6 --- /dev/null +++ b/cpg-language-cxx/build.gradle.kts @@ -0,0 +1,46 @@ +/* + * 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") + } + } + } +} + +dependencies { + api(libs.javaparser) + + testImplementation(libs.junit.params) +} 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 new file mode 100644 index 0000000000..5d7f0cc4cb --- /dev/null +++ b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CLanguage.kt @@ -0,0 +1,158 @@ +/* + * 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.frontends.cxx + +import com.fasterxml.jackson.annotation.JsonIgnore +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 +import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration +import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression +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 +import kotlin.reflect.KClass +import org.neo4j.ogm.annotation.Transient + +/** The C language. */ +open class CLanguage : + Language(), + HasComplexCallResolution, + HasStructs, + HasFunctionPointers, + HasQualifier, + HasElaboratedTypeSpecifier, + HasShortCircuitOperators { + override val fileExtensions = listOf("c", "h") + override val namespaceDelimiter = "::" + @Transient override val frontend: KClass = CXXLanguageFrontend::class + override val qualifiers = listOf("const", "volatile", "restrict", "atomic") + override val elaboratedTypeSpecifier = listOf("struct", "union", "enum") + 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("+=", "-=", "*=", "/=", "%=", "<<=", ">>=", "&=", "|=", "^=") + + /** + * 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( + // Integer types + "char" to IntegerType("char", 8, this, NumericType.Modifier.NOT_APPLICABLE), + "signed char" to IntegerType("signed char", 8, this, NumericType.Modifier.SIGNED), + "unsigned char" to IntegerType("unsigned char", 8, this, NumericType.Modifier.UNSIGNED), + "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), + "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), + + // 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 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( + call: CallExpression, + ctx: TranslationContext, + currentTU: TranslationUnitDeclaration + ): List { + val invocationCandidates = ctx.scopeManager.resolveFunction(call).toMutableList() + if (invocationCandidates.isEmpty()) { + // Check for implicit casts + invocationCandidates.addAll(resolveWithImplicitCastFunc(call, ctx)) + } + return invocationCandidates + } + + override fun refineMethodCallResolution( + curClass: RecordDeclaration?, + possibleContainingTypes: Set, + call: CallExpression, + ctx: TranslationContext, + currentTU: TranslationUnitDeclaration, + callResolver: CallResolver + ): List = emptyList() + + override fun refineInvocationCandidatesFromRecord( + recordDeclaration: RecordDeclaration, + call: CallExpression, + namePattern: Pattern, + ctx: TranslationContext + ): 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, + ctx: TranslationContext, + ): List { + val initialInvocationCandidates = + listOf( + *ctx.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-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CPPLanguage.kt similarity index 78% 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 1d8c477cf4..8a39561a65 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,14 +23,10 @@ * \______/ \__| \______/ * */ -package de.fraunhofer.aisec.cpg.frontends.cpp +package de.fraunhofer.aisec.cpg.frontends.cxx -import de.fraunhofer.aisec.cpg.ScopeManager +import de.fraunhofer.aisec.cpg.TranslationContext 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 @@ -48,43 +44,66 @@ class CPPLanguage : HasDefaultArguments, HasTemplates, HasComplexCallResolution, + HasStructs, HasClasses, HasUnknownType { override val fileExtensions = listOf("cpp", "cc", "cxx", "hpp", "hh") 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), + "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), + + // 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), + + // 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), ) /** @@ -96,7 +115,7 @@ class CPPLanguage : curClass: RecordDeclaration?, possibleContainingTypes: Set, call: CallExpression, - scopeManager: ScopeManager, + ctx: TranslationContext, currentTU: TranslationUnitDeclaration, callResolver: CallResolver ): List { @@ -110,11 +129,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 } @@ -123,7 +142,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, @@ -139,7 +158,8 @@ class CPPLanguage : override fun refineInvocationCandidatesFromRecord( recordDeclaration: RecordDeclaration, call: CallExpression, - namePattern: Pattern + namePattern: Pattern, + ctx: TranslationContext ): List { val invocationCandidate = mutableListOf( @@ -177,21 +197,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 } @@ -201,7 +220,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 @@ -216,10 +235,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) @@ -241,10 +260,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() @@ -288,7 +308,8 @@ class CPPLanguage : function, initializationSignature, initializationType, - orderedInitializationSignature + orderedInitializationSignature, + ctx ) return Pair(true, candidates) } @@ -300,7 +321,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(ctx).createInferredFunctionTemplate(templateCall) templateCall.templateInstantiation = functionTemplateDeclaration val edges = templateCall.templateParameterEdges // Set instantiation propertyEdges @@ -316,18 +337,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) - } } 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 91% 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 ca74e87681..abe28bed70 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,19 +23,21 @@ * \______/ \__| \______/ * */ -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 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) { + /** * 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. @@ -57,14 +59,14 @@ 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) { - frontend.setCodeAndLocation(node, ctx) + if (node.location == null) { + frontend.setCodeAndLocation(node, ctx) } frontend.setComment(node, ctx) - if (node != null) { - frontend.process(ctx, node) - } + frontend.process(ctx, node) + + this.lastNode = node return 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 67% 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 4779342f98..ab0713f3d3 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,24 +23,22 @@ * \______/ \__| \______/ * */ -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.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.* 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.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 import de.fraunhofer.aisec.cpg.sarif.Region import java.io.File @@ -60,7 +58,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 @@ -76,11 +77,9 @@ 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) { +@RegisterExtraPass(FunctionPointerCallResolver::class) +class CXXLanguageFrontend(language: Language, ctx: TranslationContext) : + LanguageFrontend(language, ctx) { /** * The dialect used by this language frontend, either [GCCLanguage] for C or [GPPLanguage] for @@ -200,12 +199,11 @@ class CXXLanguageFrontend( // 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) + includePaths.addAll(config.includePaths.map { it.toAbsolutePath().toString() }) config.compilationDatabase?.getIncludePaths(file)?.let { includePaths.addAll(it) } @@ -269,21 +267,13 @@ class CXXLanguageFrontend( } } - 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") @@ -316,19 +306,19 @@ class CXXLanguageFrontend( } // 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 } @@ -337,10 +327,10 @@ class CXXLanguageFrontend( 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 } @@ -348,7 +338,7 @@ class CXXLanguageFrontend( // 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) } /** @@ -396,27 +386,16 @@ class CXXLanguageFrontend( val expression: Expression = when (token.tokenType) { 1 -> // a variable - newDeclaredReferenceExpression(code, UnknownType.getUnknownType(language), code) + newReference(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) } @@ -449,28 +428,25 @@ class CXXLanguageFrontend( } } - 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) } /** @@ -496,20 +472,13 @@ class CXXLanguageFrontend( 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 @@ -518,16 +487,17 @@ class CXXLanguageFrontend( // 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)) @@ -535,33 +505,155 @@ class CXXLanguageFrontend( // 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 -> { - UnknownType.getUnknownType(language) + unknownType() } } - type = TypeManager.getInstance().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 + specifier.type == IASTSimpleDeclSpecifier.t_auto -> { + 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 -> { + 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 + 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) + } + } + } + } + + 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 @@ -570,27 +662,21 @@ class CXXLanguageFrontend( 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 @@ -636,13 +722,25 @@ class CXXLanguageFrontend( 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.getInstance().registerType(type) + return type } 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) { @@ -665,3 +763,59 @@ class CXXLanguageFrontend( } } } + +/** + * 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_wchar_t -> parts += "wchar_t" + IASTSimpleDeclSpecifier.t_char16_t -> parts += "char16_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 -> { + if (isSigned || isUnsigned) { + parts += "int" + } + } + IASTSimpleDeclSpecifier.t_auto -> parts = mutableListOf("auto") + else -> { + LanguageFrontend.Companion.log.error("Unknown C/C++ simple type: {}", type) + } + } + + return parts.joinToString(" ") + } 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 72% 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 8bf31bbf19..95d4a92b9f 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,15 +23,17 @@ * \______/ \__| \______/ * */ -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.* 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.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 import org.eclipse.cdt.core.dom.ast.* @@ -75,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()) } /** @@ -87,8 +89,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) @@ -133,7 +134,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 ?: unknownType() declaration.isDefinition = true // We also need to set the return type, based on the function type. @@ -194,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 @@ -270,12 +271,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)) + newRecordTemplateDeclaration(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) @@ -309,41 +310,37 @@ 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 = - TypeManager.getInstance() - .createOrGetTypeParameter( - templateDeclaration, - templateParameter.name.toString(), - language - ) - typeParamDeclaration.type = parameterizedType + frontend.typeManager.createOrGetTypeParameter( + templateDeclaration, + templateParameter.name.toString(), + language + ) + typeParamDecl.type = parameterizedType if (templateParameter.defaultType != null) { - val defaultType = - TypeParser.createFrom( - templateParameter.defaultType.declSpecifier.rawSignature, - false, - frontend - ) - typeParamDeclaration.default = defaultType + val defaultType = frontend.typeOf(templateParameter.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( 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) @@ -375,24 +372,31 @@ 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()) { // 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, + ) } } @@ -405,25 +409,126 @@ class DeclarationHandler(lang: CXXLanguageFrontend) : private fun addParameterizedTypesToType( 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 { - 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? Reference + 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 = @@ -450,6 +555,7 @@ class DeclarationHandler(lang: CXXLanguageFrontend) : } } frontend.processAttributes(primaryDeclaration, ctx) + sequence.addDeclaration(primaryDeclaration) } } @@ -469,68 +575,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 - } - - // 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) - - if (ctx.isTypedef) { - // Handle typedefs. - val declaration = handleTypedef(declarator, ctx, type) - - sequence.addDeclaration(declaration) - } else { - 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) - } - - // 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 declrator 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( @@ -541,13 +622,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.codeOf(ctx), + type, + frontend.typeOf(nameDecl.name) + ) // Add the declaration to the current scope frontend.scopeManager.addDeclaration(declaration) @@ -563,7 +643,7 @@ class DeclarationHandler(lang: CXXLanguageFrontend) : val enum = newEnumDeclaration( name = declSpecifier.name.toString(), - location = frontend.getLocationFromRawNode(ctx), + location = frontend.locationOf(ctx), ) // Loop through its members @@ -571,12 +651,12 @@ 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 - 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 @@ -604,62 +684,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 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) - 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 = type - - // 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> @@ -726,7 +750,7 @@ class DeclarationHandler(lang: CXXLanguageFrontend) : val allIncludes = HashMap>() parseInclusions(dependencyTree.inclusions, allIncludes) - if (allIncludes.size == 0) { + if (allIncludes.isEmpty()) { return } @@ -749,8 +773,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/frontends/cpp/DeclaratorHandler.kt b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/DeclaratorHandler.kt similarity index 80% 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 779852c909..e66c277079 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.* @@ -35,11 +35,8 @@ 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 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.* @@ -51,9 +48,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 +102,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) @@ -118,18 +115,12 @@ class DeclaratorHandler(lang: CXXLanguageFrontend) : val declaration = newVariableDeclaration( ctx.name.toString(), - UnknownType.getUnknownType(language), // Type will be filled out later by + unknownType(), // Type will be filled out later by // handleSimpleDeclaration ctx.rawSignature, 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) @@ -144,32 +135,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, - UnknownType.getUnknownType(language), - emptyList(), - ctx.rawSignature, - frontend.getLocationFromRawNode(ctx), - initializer, - true - ) - } else { - newFieldDeclaration( - name, - UnknownType.getUnknownType(language), - emptyList(), - ctx.rawSignature, - frontend.getLocationFromRawNode(ctx), - initializer, - true - ) - } + newFieldDeclaration( + name.localName, + unknownType(), + emptyList(), + ctx.rawSignature, + frontend.locationOf(ctx), + initializer, + true + ) frontend.scopeManager.addDeclaration(declaration) @@ -190,17 +167,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 @@ -261,7 +250,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 @@ -273,9 +262,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 @@ -306,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()) { @@ -345,13 +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.getUnknownType(language), - true, - "" - ) + val varargs = newParameterDeclaration("va_args", unknownType(), true, "") varargs.isImplicit = true varargs.argumentIndex = i frontend.scopeManager.addDeclaration(varargs) @@ -376,7 +359,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 } @@ -394,12 +377,7 @@ class DeclaratorHandler(lang: CXXLanguageFrontend) : val recordDeclaration = declaration.recordDeclaration // 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) - } + 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 @@ -417,45 +395,29 @@ 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 val recordDeclaration = frontend.scopeManager.currentRecord if (recordDeclaration == null) { // variable - result = - newVariableDeclaration( - name, - UnknownType.getUnknownType(language), - ctx.rawSignature, - true - ) - result.initializer = initializer + result = newVariableDeclaration(name, unknownType(), ctx.rawSignature, true) } 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, - UnknownType.getUnknownType(language), + name, + unknownType(), emptyList(), code, - frontend.getLocationFromRawNode(ctx), - initializer, - true + frontend.locationOf(ctx), + null, + false, ) } - result.location = frontend.getLocationFromRawNode(ctx) + result.location = frontend.locationOf(ctx) frontend.scopeManager.addDeclaration(result) return result } @@ -476,14 +438,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) @@ -520,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-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 66% 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 b8976ecf4e..babe8116ab 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,16 +23,13 @@ * \______/ \__| \______/ * */ -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 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.graph.types.PointerType.PointerOrigin import de.fraunhofer.aisec.cpg.helpers.Util import de.fraunhofer.aisec.cpg.passes.CallResolver import java.math.BigInteger @@ -45,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 @@ -65,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) @@ -82,7 +81,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. @@ -97,14 +96,14 @@ 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) } } } } - // 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 || @@ -113,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) @@ -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 { @@ -141,19 +139,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 = UnknownType.getUnknownType(language) + 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 @@ -167,23 +165,22 @@ 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 } 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) + val arrayCreate = newNewArrayExpression(code) arrayCreate.type = t for (arrayMod in arrayMods) { val constant = handle(arrayMod.constantExpression) @@ -194,21 +191,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), frontend.language) - newExpression.templateParameters = templateParameters val initializer: Expression? if (init != null) { initializer = frontend.initializerHandler.handle(init) @@ -219,22 +210,22 @@ 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 // 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 } @@ -297,10 +288,8 @@ 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) } return castExpression @@ -326,7 +315,7 @@ class ExpressionHandler(lang: CXXLanguageFrontend) : return newMemberExpression( name, base, - UnknownType.getUnknownType(language), + unknownType(), if (ctx.isPointerDereference) "->" else ".", ctx.rawSignature ) @@ -357,11 +346,12 @@ 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)) { - val cast = newCastExpression(frontend.getCodeFromRawNode(ctx)) - cast.castType = parseType(typeName) - cast.expression = input ?: newProblemExpression("could not parse input") - cast.location = frontend.getLocationFromRawNode(ctx) + if (frontend.typeManager.typeExists(typeName)) { + val cast = newCastExpression(frontend.codeOf(ctx)) + 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 } } @@ -394,79 +384,86 @@ class ExpressionHandler(lang: CXXLanguageFrontend) : private fun handleFunctionCallExpression(ctx: IASTFunctionCallExpression): Expression { val reference = handle(ctx.functionNameExpression) val callExpression: CallExpression - if (reference is MemberExpression) { - val baseType: Type = 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 = newReference(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 + reference.expression = + ctx.arguments.firstOrNull()?.let { handle(it) } + ?: newProblemExpression("could not parse argument for cast") + return reference + } + else -> { + callExpression = + newCallExpression(reference, reference?.name, ctx.rawSignature, false) + } } 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 } - 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.getUnknownType(language), - ctx.rawSignature - ) + return newReference(ctx.name.toString(), unknownType(), ctx.rawSignature) } private fun handleExpressionList(exprList: IASTExpressionList): ExpressionList { @@ -477,46 +474,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 = @@ -532,38 +510,42 @@ 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 Reference) { + (rhs.input as Reference).resolutionHelper = lhs + } + + return assign + } + private fun handleLiteralExpression(ctx: IASTLiteralExpression): Expression { 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), UnknownType.getUnknownType(), ctx.rawSignature) - } - } - - 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) - } + 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) } - - return expression } private fun handleCXXDesignatedInitializer( @@ -582,19 +564,16 @@ class ExpressionHandler(lang: CXXLanguageFrontend) : } is CPPASTFieldDesignator -> { oneLhs = - newDeclaredReferenceExpression( - des.name.toString(), - UnknownType.getUnknownType(language), - des.getRawSignature() - ) + newReference(des.name.toString(), unknownType(), des.getRawSignature()) } is CPPASTArrayRangeDesignator -> { oneLhs = - newArrayRangeExpression( + newRangeExpression( handle(des.rangeFloor), handle(des.rangeCeiling), des.getRawSignature() ) + oneLhs.operatorCode = "..." } else -> { Util.errorWithFileLocation( @@ -635,19 +614,16 @@ class ExpressionHandler(lang: CXXLanguageFrontend) : } is CPPASTFieldDesignator -> { oneLhs = - newDeclaredReferenceExpression( - des.name.toString(), - UnknownType.getUnknownType(language), - des.getRawSignature() - ) + newReference(des.name.toString(), unknownType(), des.getRawSignature()) } is CPPASTArrayRangeDesignator -> { oneLhs = - newArrayRangeExpression( + newRangeExpression( handle(des.rangeFloor), handle(des.rangeCeiling), des.getRawSignature() ) + oneLhs.operatorCode = "..." } else -> { Util.errorWithFileLocation( @@ -679,15 +655,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 @@ -699,61 +679,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") - } + 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 int" + numberValue is BigInteger -> "unsigned long long int" + numberValue is Long && "ll" == suffix -> "long long int" + numberValue is Long -> "long int" + else -> "int" + } + ) return newLiteral(numberValue, type, ctx.rawSignature) } catch (ex: NumberFormatException) { @@ -767,14 +751,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 @@ -784,18 +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.getUnknownType() + 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) + return newReference("this", pointerType, ctx.rawSignature, ctx) } private val IASTLiteralExpression.valueWithSuffix: Pair 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 59% 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 7ba28fe46e..dd7be1da52 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,12 +23,19 @@ * \______/ \__| \______/ * */ -package de.fraunhofer.aisec.cpg.frontends.cpp +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,16 +58,42 @@ 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) - arg!!.argumentIndex = i - constructExpression.addArgument(arg) + arg?.let { + it.argumentIndex = i + constructExpression.addArgument(it) + } } 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-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 84% 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..79e0c240f2 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,12 +23,12 @@ * \______/ \__| \______/ * */ -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 +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-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 91% 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 6c49cdb7b8..dc759a1f01 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,13 +23,14 @@ * \______/ \__| \______/ * */ -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 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 @@ -233,7 +239,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 } @@ -253,7 +259,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` @@ -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-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 64% 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 226bc77caa..76c565bf26 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 @@ -25,22 +25,15 @@ */ 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.TranslationContext +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.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.pointer +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.helpers.IdentitySet import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker.ScopedWalker import de.fraunhofer.aisec.cpg.passes.order.DependsOn @@ -62,20 +55,16 @@ import java.util.function.Consumer @DependsOn(CallResolver::class) @DependsOn(DFGPass::class) @RequiredFrontend(CXXLanguageFrontend::class) -class FunctionPointerCallResolver : Pass() { +class FunctionPointerCallResolver(ctx: TranslationContext) : ComponentPass(ctx) { 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) - walker.registerHandler { _: RecordDeclaration?, _: Node?, currNode: Node -> - walker.collectDeclarations(currNode) - } + override fun accept(component: Component) { + inferDfgForUnresolvedCalls = config.inferenceConfiguration.inferDfgForUnresolvedSymbols + walker = ScopedWalker(scopeManager) walker.registerHandler { node, _ -> resolve(node) } - for (tu in t.translationUnits) { + for (tu in component.translationUnits) { walker.iterate(tu) } } @@ -88,21 +77,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,17 +99,18 @@ 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() work.push(pointer) - while (!work.isEmpty()) { + while (work.isNotEmpty()) { val curr = work.pop() if (!seen.add(curr)) { continue } + val isLambda = curr is VariableDeclaration && curr.initializer is LambdaExpression val currentFunction = if (isLambda) { @@ -141,17 +123,7 @@ 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.pointer() if ( isLambda && currentFunction.returnTypes.isEmpty() && @@ -159,11 +131,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 @@ -177,7 +145,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(ctx).handleCallExpression(call, inferDfgForUnresolvedCalls) } override fun cleanup() { 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 95% 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..c8b4fcb8ae 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 @@ -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() { @@ -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/ScopeManagerCXXTest.kt b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/ScopeManagerCXXTest.kt new file mode 100644 index 0000000000..a4155abe96 --- /dev/null +++ b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/ScopeManagerCXXTest.kt @@ -0,0 +1,77 @@ +/* + * 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.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 93% 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 index c3f70f3a47..faf20fe1ec 100644 --- 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 @@ -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() @@ -956,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()) @@ -977,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) @@ -990,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) @@ -1003,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 @@ -1030,10 +1001,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/calls/ConstructorsTest.kt b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/calls/ConstructorsTest.kt similarity index 99% 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 index 3b1c84e10a..fabfa500eb 100644 --- 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 @@ -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/enhancements/calls/FunctionPointerTest.kt b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/calls/FunctionPointerTest.kt similarity index 98% 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 index 7ac666565a..a911cf74a5 100644 --- 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 @@ -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-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 84% 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 index 47a9d2f850..a2a2d791c6 100644 --- 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 @@ -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,21 +144,24 @@ 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 receiver = pair.byNameOrNull("Pair")?.receiver + val constructor = pair.constructors["Pair"] + assertNotNull(constructor) + + val receiver = constructor.receiver assertNotNull(receiver) - val pairConstructorDeclaration = + val pairConstructorDecl = findByUniqueName(result.allChildren(), "Pair") val constructExpression = findByUniquePredicate(result.allChildren()) { c: ConstructExpression -> @@ -176,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, @@ -195,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") @@ -207,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 = @@ -218,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]) @@ -242,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, @@ -298,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]) @@ -328,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]) @@ -342,8 +339,8 @@ internal class ClassTemplateTest : BaseTest() { testStructTemplateWithSameDefaultTypeInvocation( template, pair, - pairConstructorDeclaration, - constructExpression, + pairConstructorDecl, + constructExpr, point1 ) } @@ -356,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]) @@ -413,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") @@ -428,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) @@ -492,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) @@ -522,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]) } @@ -545,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-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 93% 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 2a195f3255..62b9ce738c 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 @@ -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(), x.type) - - val declaredReferenceExpressions = result.refs - val xDeclaredReferenceExpression = findByUniqueName(declaredReferenceExpressions, "x") - assertEquals(UnknownType.getUnknownType(), xDeclaredReferenceExpression.type) - - val binaryOperators = result.allChildren() - val dependentOperation = - findByUniquePredicate(binaryOperators) { b: BinaryOperator -> b.code == "val * N" } - assertEquals(UnknownType.getUnknownType(), 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( @@ -99,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) @@ -118,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)) @@ -126,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()) @@ -543,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 -> @@ -569,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 -> @@ -583,7 +579,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-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 new file mode 100644 index 0000000000..873a2bc158 --- /dev/null +++ b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/types/TypeTests.kt @@ -0,0 +1,300 @@ +/* + * 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.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.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.* + +internal class TypeTests : BaseTest() { + @Test + fun reference() { + val objectType: Type = IntegerType("int", 32, CPPLanguage(), NumericType.Modifier.SIGNED) + val pointerType: Type = PointerType(objectType, PointerType.PointerOrigin.POINTER) + val unknownType: Type = UnknownType.getUnknownType(CPPLanguage()) + val incompleteType: Type = IncompleteType() + val parameterList = + listOf(IntegerType("int", 32, CPPLanguage(), NumericType.Modifier.SIGNED)) + val functionPointerType: Type = + FunctionPointerType(parameterList, CPPLanguage(), IncompleteType()) + + // Test 1: ObjectType becomes PointerType containing the original ObjectType as ElementType + assertEquals( + PointerType(objectType, PointerType.PointerOrigin.POINTER), + objectType.reference(PointerType.PointerOrigin.POINTER) + ) + + // Test 2: Existing PointerType adds one level more of references as ElementType + assertEquals( + PointerType(pointerType, PointerType.PointerOrigin.POINTER), + pointerType.reference(PointerType.PointerOrigin.POINTER) + ) + + // Test 3: UnknownType cannot be referenced + assertEquals(unknownType, unknownType.reference(null)) + + // Test 4: IncompleteType can be refereced e.g. void* + assertEquals( + PointerType(incompleteType, PointerType.PointerOrigin.POINTER), + incompleteType.reference(PointerType.PointerOrigin.POINTER) + ) + + // Test 5: Create reference to function pointer = pointer to function pointer + assertEquals( + PointerType(functionPointerType, PointerType.PointerOrigin.POINTER), + functionPointerType.reference(PointerType.PointerOrigin.POINTER) + ) + } + + @Test + fun dereference() { + val objectType: Type = IntegerType("int", 32, CPPLanguage(), NumericType.Modifier.SIGNED) + val pointerType: Type = PointerType(objectType, PointerType.PointerOrigin.POINTER) + val unknownType: Type = UnknownType.getUnknownType(CPPLanguage()) + val incompleteType: Type = IncompleteType() + val parameterList = + listOf(IntegerType("int", 32, CPPLanguage(), NumericType.Modifier.SIGNED)) + val functionPointerType: Type = + FunctionPointerType(parameterList, CPPLanguage(), IncompleteType()) + + // Test 1: Dereferencing an ObjectType results in an UnknownType, since we cannot track the + // type + // of the corresponding memory + assertEquals(UnknownType.getUnknownType(CPPLanguage()), objectType.dereference()) + + // Test 2: Dereferencing a PointerType results in the corresponding elementType of the + // PointerType (can also be another PointerType) + assertEquals(objectType, pointerType.dereference()) + + // Test 3: Dereferecing unknown or incomplete type results in the same type + assertEquals(unknownType, unknownType.dereference()) + assertEquals(incompleteType, incompleteType.dereference()) + + // Test 5: Due to the definition in the C-Standard dereferencing function pointer yields the + // same function pointer + assertEquals(functionPointerType, functionPointerType.dereference()) + } + + /** + * Test for usage of getTypeStringFromDeclarator to determine function pointer raw type string + * + * @throws Exception Any exception thrown during the analysis process + */ + @Test + @Throws(Exception::class) + fun testFunctionPointerTypes() { + val topLevel = Path.of("src", "test", "resources", "types") + val tu = + analyzeAndGetFirstTU(listOf(topLevel.resolve("fptr_type.cpp").toFile()), topLevel, true) + val noParamType = FunctionPointerType(emptyList(), CPPLanguage(), IncompleteType()) + val oneParamType = + FunctionPointerType( + listOf(tu.primitiveType("int")), + CPPLanguage(), + IncompleteType() + ) + val twoParamType = + FunctionPointerType( + listOf(tu.primitiveType("int"), tu.primitiveType("unsigned long int")), + CPPLanguage(), + IntegerType("int", 32, CPPLanguage(), NumericType.Modifier.SIGNED) + ) + val variables = tu.variables + val localTwoParam = findByUniqueName(variables, "local_two_param") + assertNotNull(localTwoParam) + assertEquals(twoParamType, localTwoParam.type) + + val localOneParam = findByUniqueName(variables, "local_one_param") + assertNotNull(localOneParam) + assertEquals(oneParamType, localOneParam.type) + + val globalNoParam = findByUniqueName(variables, "global_no_param") + assertNotNull(globalNoParam) + assertEquals(noParamType, globalNoParam.type) + + val globalNoParamVoid = findByUniqueName(variables, "global_no_param_void") + assertNotNull(globalNoParamVoid) + assertEquals(noParamType, globalNoParamVoid.type) + + val globalTwoParam = findByUniqueName(variables, "global_two_param") + assertNotNull(globalTwoParam) + assertEquals(twoParamType, globalTwoParam.type) + + val globalOneParam = findByUniqueName(variables, "global_one_param") + assertNotNull(globalOneParam) + assertEquals(oneParamType, globalOneParam.type) + } + + @Throws(Exception::class) + @Test + fun testCommonTypeTestCpp() { + 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 = 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) + } + } + + // level2 and level2b have two intersections, both root and level0 -> level0 is lower + @Throws(Exception::class) + @Test + fun testCommonTypeTestCppMultiInheritance() { + 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() + + /* + 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 + @Throws(Exception::class) + fun graphTest() { + val topLevel = Path.of("src", "test", "resources", "types") + val result = analyze("cpp", topLevel, true) + val variableDeclarations = result.variables + + // Test PointerType chain with pointer + val regularInt = findByUniqueName(variableDeclarations, "regularInt") + val ptr = findByUniqueName(variableDeclarations, "ptr") + assertTrue(ptr.type is PointerType) + assertEquals((ptr.type as PointerType).elementType, regularInt.type) + + // Unresolved auto type propagation + val unknown = findByUniqueName(variableDeclarations, "unknown") + assertIs(unknown.type) + + // Resolved auto type propagation + val propagated = findByUniqueName(variableDeclarations, "propagated") + assertEquals(regularInt.type, propagated.type) + } + + private fun getCommonTypeTestGeneral( + root: Type, + level0: Type, + level1: Type, + level1b: Type, + level2: Type, + unrelated: Type, + result: TranslationResult + ) { + /* + Type hierarchy: + Root + | + Level0 + / \ + Level1 Level1B + | + Level2 + */ + 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(t, setOf(t).commonType) + } + + // Root is the root of all types + for (t in listOf(level0, level1, level1b, level2)) { + assertEquals(root, setOf(t, root).commonType) + } + + // Level0 is above all types but Root + for (t in listOf(level1, level1b, level2)) { + assertEquals(level0, setOf(t, level0).commonType) + } + + // Level1 and Level1B have Level0 as common ancestor + assertEquals(level0, setOf(level1, level1b).commonType) + + // Level2 and Level1B have Level0 as common ancestor + assertEquals(level0, setOf(level2, level1b).commonType) + + // Level1 and Level2 have Level1 as common ancestor + assertEquals(level1, setOf(level1, level2).commonType) + + // Check unrelated type behavior: No common root class + for (t in listOf(root, level0, level1, level1b, level2)) { + assertEquals(null, setOf(unrelated, t).commonType) + } + } +} 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 90% 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 4b1acdd0de..b9bab4af9e 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,15 +23,16 @@ * \______/ \__| \______/ * */ -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 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.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.* @@ -72,7 +73,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) @@ -92,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.getInstance().checkArrayAndPointer(l1arr.type, l2arr.type)) - assertTrue(TypeManager.getInstance().checkArrayAndPointer(l1arr.type, l3arr.type)) - assertTrue(TypeManager.getInstance().checkArrayAndPointer(l1arr.type, l4arr.type)) } @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 97% 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 index cef82e7c9b..5a0ea31a7c 100644 --- 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 @@ -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-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 91% 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..291c29a372 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 @@ -31,9 +31,10 @@ 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 +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Reference import de.fraunhofer.aisec.cpg.sarif.Region import java.io.File import kotlin.test.* @@ -74,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"] @@ -110,7 +111,7 @@ internal class CXXIncludeTest : BaseTest() { .topLevel(file.parentFile) .loadIncludes(true) .debugParser(true) - .defaultLanguages() + .registerLanguage() .includeBlocklist(File("src/test/resources/include.h").absolutePath) .failOnError(true) ) @@ -137,7 +138,7 @@ internal class CXXIncludeTest : BaseTest() { .topLevel(file.parentFile) .loadIncludes(true) .debugParser(true) - .defaultLanguages() + .registerLanguage() .includeBlocklist("include.h") .failOnError(true) ) @@ -164,7 +165,7 @@ internal class CXXIncludeTest : BaseTest() { .topLevel(file.parentFile) .loadIncludes(true) .debugParser(true) - .defaultLanguages() + .registerLanguage() .includeWhitelist(File("src/test/resources/include.h").absolutePath) .failOnError(true) ) @@ -191,7 +192,7 @@ internal class CXXIncludeTest : BaseTest() { .topLevel(file.parentFile) .loadIncludes(true) .debugParser(true) - .defaultLanguages() + .registerLanguage() .includeWhitelist("include.h") .failOnError(true) ) @@ -218,7 +219,7 @@ internal class CXXIncludeTest : BaseTest() { .topLevel(file.parentFile) .loadIncludes(true) .debugParser(true) - .defaultLanguages() + .registerLanguage() .includeBlocklist("include.h") // blacklist entries take priority .includeWhitelist("include.h") .includeWhitelist("another-include.h") @@ -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( @@ -249,13 +250,20 @@ internal class CXXIncludeTest : BaseTest() { .topLevel(file.parentFile) .loadIncludes(false) .debugParser(true) - .defaultLanguages() + .registerLanguage() .failOnError(true) ) 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-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 64% 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 b17c1f1b74..c2efd1901e 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,23 +23,23 @@ * \______/ \__| \______/ * */ -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.* 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 import de.fraunhofer.aisec.cpg.sarif.Region @@ -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()) @@ -62,14 +62,14 @@ 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.objectType("std::vector", listOf(tu.objectType("int"))), ls.type) assertLocalName("ls", ls) val forEachStatement = decl.getBodyStatementAs(1, ForEachStatement::class.java) 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 @@ -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 @@ -122,78 +122,79 @@ 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(createTypeFrom("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(createTypeFrom("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(createTypeFrom("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 @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) - val main = tu.getDeclarationAs(0, FunctionDeclaration::class.java) - val e = - Objects.requireNonNull(main!!.getBodyStatementAs(0, DeclarationStatement::class.java)) - ?.singleDeclaration as VariableDeclaration - assertNotNull(e) - assertEquals(createTypeFrom("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) - - // initializer - var cast = b.initializer as? CastExpression - assertNotNull(cast) - assertEquals(createTypeFrom("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(createTypeFrom("int", true), cast.castType) + with(tu) { + val main = tu.functions["main"] + assertNotNull(main) + + val e = main.variables["e"] + assertNotNull(e) + assertEquals(objectType("ExtendedClass").pointer(), e.type) + + val b = main.variables["b"] + 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, AssignExpression::class.java) + assertNotNull(staticCast) + cast = staticCast.rhs() + assertNotNull(cast) + assertLocalName("static_cast", cast) + + val reinterpretCast = main.getBodyStatementAs(3, AssignExpression::class.java) + assertNotNull(reinterpretCast) + cast = reinterpretCast.rhs() + assertNotNull(cast) + assertLocalName("reinterpret_cast", cast) + + val d = main.variables["d"] + assertNotNull(d) + + cast = d.initializer as? CastExpression + assertNotNull(cast) + assertEquals(primitiveType("int"), cast.castType) + } } @Test @@ -202,47 +203,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(createTypeFrom("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 Block + + // 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 SubscriptExpression + assertNotNull(ase) + assertEquals(x, (ase.arrayExpression as Reference).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 @@ -266,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) @@ -278,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) @@ -315,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) @@ -324,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 @@ -361,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 @@ -380,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 @@ -400,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) @@ -413,132 +410,133 @@ 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 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 + val tu = analyzeAndGetFirstTU(listOf(file), file.parentFile.toPath(), true) + 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(createTypeFrom("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(createTypeFrom("int*", false), b.type) - - val c = twoDeclarations[1] as VariableDeclaration - assertNotNull(c) - assertLocalName("c", c) - assertEquals(createTypeFrom("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(createTypeFrom("int*", true), withoutInitializer.type) - assertLocalName("d", withoutInitializer) - assertNull(initializer) - - val qualifiedType = - (statements[4] as DeclarationStatement).getSingleDeclarationAs( - VariableDeclaration::class.java - ) - assertEquals(createTypeFrom("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(createTypeFrom("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 @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) + assertRefersTo(lhs, a) - val declareB = statements[2] - assertTrue(declareB is DeclarationStatement) - - 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) + assertTrue(rhs is Reference) 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) } @@ -548,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) } @@ -610,8 +608,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) @@ -621,52 +621,63 @@ internal class CXXLanguageFrontendTest : BaseTest() { @Test @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 file = File("src/test/resources/cxx/binaryoperator.cpp") + val tu = analyzeAndGetFirstTU(listOf(file), file.parentFile.toPath(), true) + + 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 Reference) + 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 - assertEquals(createTypeFrom("std::string*", true), decl.type) + 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 @@ -686,7 +697,7 @@ internal class CXXLanguageFrontendTest : BaseTest() { val constant = recordDeclaration.fields["CONSTANT"] assertNotNull(constant) - assertEquals(createTypeFrom("void*", true), field.type) + assertEquals(tu.incompleteType().reference(POINTER), field.type) assertEquals(3, recordDeclaration.methods.size) val method = recordDeclaration.methods[0] @@ -704,12 +715,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.primitiveType("int"), methodWithParam.parameters[0].type) assertEquals( FunctionType( "(int)void*", - listOf(createTypeFrom("int", true)), - listOf(createTypeFrom("void*", true)), + listOf(tu.primitiveType("int")), + listOf(tu.incompleteType().reference(POINTER)), CPPLanguage() ), methodWithParam.type @@ -725,7 +736,12 @@ 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.incompleteType().reference(POINTER)), + CPPLanguage() + ), inlineMethod.type ) assertTrue(inlineMethod.hasBody()) @@ -736,7 +752,7 @@ internal class CXXLanguageFrontendTest : BaseTest() { FunctionType( "()SomeClass", listOf(), - listOf(createTypeFrom("SomeClass", true)), + listOf(tu.objectType("SomeClass")), CPPLanguage() ), inlineConstructor.type @@ -746,12 +762,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.primitiveType("int"), constructorDefinition.parameters[0].type) assertEquals( FunctionType( "(int)SomeClass", - listOf(createTypeFrom("int", false)), - listOf(createTypeFrom("SomeClass", true)), + listOf(tu.primitiveType("int")), + listOf(tu.objectType("SomeClass")), CPPLanguage() ), constructorDefinition.type @@ -771,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) @@ -841,7 +857,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) @@ -856,10 +872,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) @@ -876,8 +892,9 @@ 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) + assertNotNull(z) + with(tu) { assertEquals(primitiveType("int").array(), z.type) } initializer = z.initializer assertNotNull(initializer) @@ -891,60 +908,61 @@ 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) - - // get the main method - val main = declaration.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(createTypeFrom("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) - - // auto (Integer) m - val m = - (statement.statements[6] as DeclarationStatement).singleDeclaration - as VariableDeclaration - // type should be Integer* - assertEquals(createTypeFrom("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(createTypeFrom("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) - - // 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) + val tu = analyzeAndGetFirstTU(listOf(file), file.parentFile.toPath(), true) + assertNotNull(tu) + with(tu) { + // get the main method + val main = tu.getDeclarationAs(3, FunctionDeclaration::class.java) + val statement = main!!.body as Block + + // 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 constructExpr = i.initializer as? ConstructExpression + assertNotNull(constructExpr) + // type of the construct expression should also be Integer + assertEquals(tu.objectType("Integer"), constructExpr.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 = constructExpr.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 + constructExpr = newExpression.initializer as? ConstructExpression + assertNotNull(constructExpr) + // type of the construct expression should be Integer + assertEquals(objectType("Integer"), constructExpr.type) + + // argument should be named k and of type m + val k = constructExpr.arguments[0] as Reference + assertLocalName("k", k) + // type of the construct expression should also be Integer + assertEquals(primitiveType("int"), k.type) + } } private val FunctionDeclaration.statements: List? get() { - return (this.body as? CompoundStatement)?.statements + return (this.body as? Block)?.statements } @Test @@ -953,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) @@ -979,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) @@ -998,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) @@ -1023,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) @@ -1074,12 +1092,12 @@ 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.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) @@ -1124,7 +1142,7 @@ internal class CXXLanguageFrontendTest : BaseTest() { .sourceLocations(listOf(file)) .topLevel(file.parentFile) .defaultPasses() - .defaultLanguages() + .registerLanguage() .processAnnotations(true) .symbols( mapOf( @@ -1181,7 +1199,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) @@ -1197,22 +1215,21 @@ 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] 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) } } ) @@ -1242,6 +1259,7 @@ internal class CXXLanguageFrontendTest : BaseTest() { assertNotNull(initializer) assertTrue(initializer is CastExpression) assertLocalName("size_t", initializer.castType) + assertLiteralValue(42, initializer.expression) } @Test @@ -1261,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") @@ -1276,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) @@ -1290,7 +1308,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) @@ -1298,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 @@ -1309,16 +1327,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) @@ -1330,7 +1348,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()) @@ -1414,6 +1432,96 @@ 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() + 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"] + 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 Reference } + assertInvokes(assertNotNull(noParamCall), target) + + val noParamNoInitCall = + tu.calls("no_param_uninitialized").firstOrNull { it.callee is Reference } + 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() + 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"] + 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 Reference } + assertInvokes(assertNotNull(noParamCall), target) + + val noParamNoInitCall = + tu.calls("no_param_uninitialized").firstOrNull { it.callee is Reference } + assertInvokes(assertNotNull(noParamNoInitCall), target) + + val targetCall = tu.calls["target"] + assertInvokes(assertNotNull(targetCall), target) + } + @Test @Throws(Exception::class) fun testNamespacedFunction() { @@ -1453,6 +1561,43 @@ internal class CXXLanguageFrontendTest : BaseTest() { assertNotNull(tu) } - private fun createTypeFrom(typename: String, resolveAlias: Boolean) = - TypeParser.createFrom(typename, CPPLanguage(), resolveAlias, null) + @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()) + } + + @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) + } + + @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-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 83% 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 74090fff97..d3761416d7 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 @@ -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.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), - createTypeFrom("unsigned long long"), + tu.primitiveType("unsigned long long int"), 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.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, - createTypeFrom("long long"), + tu.primitiveType("long long int"), funcDecl, "l_long_long_with_suffix" ) assertLiteral( BigInteger("9223372036854775809"), - createTypeFrom("unsigned long"), + tu.primitiveType("unsigned long int"), funcDecl, "l_unsigned_long_with_suffix" ) assertLiteral( BigInteger("9223372036854775808"), - createTypeFrom("unsigned long long"), + tu.primitiveType("unsigned long long int"), funcDecl, "l_long_long_implicit" ) assertLiteral( BigInteger("9223372036854775809"), - createTypeFrom("unsigned long long"), + tu.primitiveType("unsigned long long int"), 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.primitiveType("int"), funcDecl, "i") + assertLiteral(42L, tu.primitiveType("long int"), funcDecl, "l_with_suffix") assertLiteral( BigInteger.valueOf(42), - createTypeFrom("unsigned long long"), + tu.primitiveType("unsigned long long int"), 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.primitiveType("int"), funcDecl, "i") + assertLiteral(42L, tu.primitiveType("long int"), funcDecl, "l_with_suffix") assertLiteral( BigInteger.valueOf(42), - createTypeFrom("unsigned long long"), + tu.primitiveType("unsigned long long int"), 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/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 81% 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 15b3afff40..ccd406702c 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,18 +23,16 @@ * \______/ \__| \______/ * */ -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.ScopeManager -import de.fraunhofer.aisec.cpg.TranslationConfiguration -import de.fraunhofer.aisec.cpg.assertLocalName +import de.fraunhofer.aisec.cpg.* +import de.fraunhofer.aisec.cpg.TypeManager 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 @@ -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) @@ -62,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) @@ -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/frontends/cpp/LambdaTest.kt b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/LambdaTest.kt similarity index 95% 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..f223455397 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,12 +23,13 @@ * \______/ \__| \______/ * */ -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 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 new file mode 100644 index 0000000000..1d283bf9b5 --- /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/cxx/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/cxx/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 98% 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 495dc17549..4e9ea44441 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 @@ -42,8 +42,8 @@ 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.TypeParser import de.fraunhofer.aisec.cpg.graph.types.UnknownType import java.io.File import java.nio.file.Path @@ -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) @@ -153,9 +154,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.primitiveType("int") + val stringType = tu.primitiveType("char").reference(PointerType.PointerOrigin.POINTER) testMethods(records, intType, stringType) testOverriding(records) @@ -282,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); @@ -363,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-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/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/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-language-cxx/src/test/resources/c/func_ptr_call.c b/cpg-language-cxx/src/test/resources/c/func_ptr_call.c new file mode 100644 index 0000000000..85abddcac3 --- /dev/null +++ b/cpg-language-cxx/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(); +} 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-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-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 93% rename from cpg-core/src/test/resources/cfg/if.cpp rename to cpg-language-cxx/src/test/resources/cfg/if.cpp index 2add3aa2ff..5bc2501dec 100644 --- a/cpg-core/src/test/resources/cfg/if.cpp +++ b/cpg-language-cxx/src/test/resources/cfg/if.cpp @@ -11,4 +11,5 @@ int main(void){ else i = 1; printf("\n"); + return i; } \ No newline at end of file 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/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/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/assignmentexpression.cpp b/cpg-language-cxx/src/test/resources/cxx/assignmentexpression.cpp similarity index 100% rename from cpg-core/src/test/resources/assignmentexpression.cpp rename to cpg-language-cxx/src/test/resources/cxx/assignmentexpression.cpp diff --git a/cpg-core/src/test/resources/binaryoperator.cpp b/cpg-language-cxx/src/test/resources/cxx/binaryoperator.cpp similarity index 100% rename from cpg-core/src/test/resources/binaryoperator.cpp rename to cpg-language-cxx/src/test/resources/cxx/binaryoperator.cpp diff --git a/cpg-core/src/test/resources/components/castexpr.cpp b/cpg-language-cxx/src/test/resources/cxx/castexpr.cpp similarity index 100% rename from cpg-core/src/test/resources/components/castexpr.cpp rename to cpg-language-cxx/src/test/resources/cxx/castexpr.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-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/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 diff --git a/cpg-core/src/test/resources/components/foreachstmt.cpp b/cpg-language-cxx/src/test/resources/cxx/foreachstmt.cpp similarity index 100% rename from cpg-core/src/test/resources/components/foreachstmt.cpp rename to cpg-language-cxx/src/test/resources/cxx/foreachstmt.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-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 c789e338ba..0000000000 --- a/cpg-language-go/src/main/golang/basic_types.go +++ /dev/null @@ -1,82 +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 { - o, err := env.NewObject("java/lang/String", []byte(s)) - if err != nil { - log.Fatal(err) - - } - - return o -} - -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 74d376e09e..0000000000 --- a/cpg-language-go/src/main/golang/declarations.go +++ /dev/null @@ -1,174 +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 (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 (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 51851bff0d..0000000000 --- a/cpg-language-go/src/main/golang/expressions.go +++ /dev/null @@ -1,211 +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 ConstructExpression Expression -type InitializerListExpression Expression -type MemberCallExpression CallExpression -type MemberExpression Expression -type BinaryOperator Expression -type UnaryOperator Expression -type Literal Expression -type DeclaredReferenceExpression Expression -type KeyValueExpression Expression - -func (e *Expression) SetType(t *Type) { - (*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 (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) { - (*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 (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) AddInitializer(e *Expression) { - (*jnigi.ObjectRef)(c).CallMethod(env, "addInitializer", nil, (*jnigi.ObjectRef)(e).Cast(ExpressionClass)) -} - -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)) -} 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 4965cacb36..0000000000 --- a/cpg-language-go/src/main/golang/frontend/declaration_builder.go +++ /dev/null @@ -1,92 +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) *cpg.FunctionDeclaration { - return (*cpg.FunctionDeclaration)(frontend.NewDeclaration("FunctionDeclaration", fset, astNode, name)) -} - -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 06df6661b7..0000000000 --- a/cpg-language-go/src/main/golang/frontend/expression_builder.go +++ /dev/null @@ -1,131 +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) 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) 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") - } - - 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) 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 b70c070a5b..0000000000 --- a/cpg-language-go/src/main/golang/frontend/frontend.go +++ /dev/null @@ -1,175 +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 - - 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) 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 updateCode(fset *token.FileSet, node *cpg.Node, astNode ast.Node) { - var codeBuf bytes.Buffer - _ = printer.Fprint(&codeBuf, fset, astNode) - - node.SetCode(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 f7cd9dedb0..0000000000 --- a/cpg-language-go/src/main/golang/frontend/handler.go +++ /dev/null @@ -1,1364 +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" - "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) - - // 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) - - } - } - } - - // 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.LogDebug("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.LogDebug("Comments: %+v", comment) - } -} - -func (this *GoLanguageFrontend) handleDecl(fset *token.FileSet, decl ast.Decl) (d *cpg.Declaration) { - this.LogDebug("Handling declaration (%T): %+v", decl, decl) - - switch v := decl.(type) { - case *ast.FuncDecl: - d = (*cpg.Declaration)(this.handleFuncDecl(fset, v)) - case *ast.GenDecl: - d = (*cpg.Declaration)(this.handleGenDecl(fset, v)) - default: - this.LogError("Not parsing declaration of type %T yet: %+v", v, v) - // no match - d = nil - } - - 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) - - 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(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.LogDebug("Record: %+v", record) - - err = record.AddMethod(m) - if err != nil { - log.Fatal(err) - - } - } - } - - f = (*cpg.FunctionDeclaration)(m) - } else { - f = this.NewFunctionDeclaration(fset, funcDecl, funcDecl.Name.Name) - } - - // enter scope for function - scope.EnterScope((*cpg.Node)(f)) - - if receiver != nil { - this.LogDebug("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 returnTypes []*cpg.Type = []*cpg.Type{} - - if funcDecl.Type.Results != nil { - for _, returnVariable := range funcDecl.Type.Results.List { - returnTypes = append(returnTypes, this.handleType(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)) - - // add parameter to scope - this.GetScopeManager().AddDeclaration((*cpg.Declaration)(p)) - } - } - } - - this.LogDebug("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.LogDebug("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) - - p.SetType(this.handleType(param.Type)) - - // add parameter to scope - this.GetScopeManager().AddDeclaration((*cpg.Declaration)(p)) - - this.handleComments((*cpg.Node)(p), param) - } - - this.LogDebug("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 (*jnigi.ObjectRef)(f) -} - -func (this *GoLanguageFrontend) handleGenDecl(fset *token.FileSet, genDecl *ast.GenDecl) *jnigi.ObjectRef { - // TODO: Handle multiple declarations - for _, spec := range genDecl.Specs { - switch v := spec.(type) { - case *ast.ValueSpec: - return (*jnigi.ObjectRef)(this.handleValueSpec(fset, v)) - case *ast.TypeSpec: - return (*jnigi.ObjectRef)(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))*/ - default: - this.LogError("Not parsing specication of type %T yet: %+v", v, v) - } - } - - return nil -} - -func (this *GoLanguageFrontend) handleValueSpec(fset *token.FileSet, valueDecl *ast.ValueSpec) *cpg.Declaration { - // TODO: more names - var ident = valueDecl.Names[0] - - d := this.NewVariableDeclaration(fset, valueDecl, ident.Name) - - if valueDecl.Type != nil { - t := this.handleType(valueDecl.Type) - - 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]) - - err := d.SetInitializer(expr) - if err != nil { - log.Fatal(err) - } - } - - return (*cpg.Declaration)(d) -} - -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.LogInfo("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(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) - - name = typeName - } else { - this.LogDebug("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(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.LogDebug("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.LogDebug("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) - - 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.LogDebug("Handling for statement: %+v", *forStmt) - - f := this.NewForStatement(fset, forStmt) - - var scope = this.GetScopeManager() - - scope.EnterScope((*cpg.Node)(f)) - - if initStatement := this.handleStmt(fset, forStmt.Init); initStatement != nil { - f.SetInitializerStatement(initStatement) - } - - if condition := this.handleExpr(fset, forStmt.Cond); condition != nil { - f.SetCondition(condition) - } - - if iter := this.handleStmt(fset, forStmt.Post); iter != nil { - f.SetIterationStatement(iter) - } - - if body := this.handleStmt(fset, forStmt.Body); body != nil { - 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) - - 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.LogDebug("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) (s *cpg.Statement) { - this.LogDebug("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)) - case *ast.DeclStmt: - s = (*cpg.Statement)(this.handleDeclStmt(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.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 - } - - if s != nil { - this.handleComments((*cpg.Node)(s), stmt) - } - - return -} - -func (this *GoLanguageFrontend) handleExpr(fset *token.FileSet, expr ast.Expr) (e *cpg.Expression) { - this.LogDebug("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.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.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: - this.LogError("Could not parse expression of type %T: %+v", v, v) - // TODO: return an error instead? - e = nil - } - - if e != nil { - this.handleComments((*cpg.Node)(e), expr) - } - - return -} - -func (this *GoLanguageFrontend) handleAssignStmt(fset *token.FileSet, assignStmt *ast.AssignStmt) (expr *cpg.Expression) { - this.LogDebug("Handling assignment statement: %+v", assignStmt) - - // TODO: more than one Rhs?! - rhs := this.handleExpr(fset, assignStmt.Rhs[0]) - - 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 name = assignStmt.Lhs[0].(*ast.Ident).Name - - // TODO: assignment of multiple values - d := this.NewVariableDeclaration(fset, assignStmt, name) - - if rhs != nil { - d.SetInitializer(rhs) - } - - this.GetScopeManager().AddDeclaration((*cpg.Declaration)(d)) - - stmt.SetSingleDeclaration((*cpg.Declaration)(d)) - - expr = (*cpg.Expression)(stmt) - } else { - lhs := this.handleExpr(fset, assignStmt.Lhs[0]) - - b := this.NewBinaryOperator(fset, assignStmt, "=") - - if lhs != nil { - b.SetLHS(lhs) - } - - if rhs != nil { - b.SetRHS(rhs) - } - - expr = (*cpg.Expression)(b) - } - - return -} - -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) - - stmt.SetSingleDeclaration((*cpg.Declaration)(d)) - - this.GetScopeManager().AddDeclaration(d) - - return (*cpg.Expression)(stmt) -} - -func (this *GoLanguageFrontend) handleIfStmt(fset *token.FileSet, ifStmt *ast.IfStmt) (expr *cpg.Expression) { - this.LogDebug("Handling if statement: %+v", *ifStmt) - - stmt := this.NewIfStatement(fset, ifStmt) - - var scope = this.GetScopeManager() - - scope.EnterScope((*cpg.Node)(stmt)) - - init := this.handleStmt(fset, ifStmt.Init) - if init != nil { - 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.") - } - - then := this.handleBlockStmt(fset, ifStmt.Body) - stmt.SetThenStatement((*cpg.Statement)(then)) - - els := this.handleStmt(fset, ifStmt.Else) - if els != nil { - 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.LogDebug("Handling switch statement: %+v", *switchStmt) - - s := this.NewSwitchStatement(fset, switchStmt) - - if switchStmt.Init != nil { - s.SetInitializerStatement(this.handleStmt(fset, switchStmt.Init)) - } - - 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.LogDebug("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) - - 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 - // 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.LogDebug("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) - - c = this.NewCallExpression(fset, callExpr, reference, name) - } - - for _, arg := range callExpr.Args { - e := this.handleExpr(fset, arg) - - if e != nil { - c.AddArgument(e) - } - } - - // reference.disconnectFromGraph() - - return (*cpg.Expression)(c) -} - -func (this *GoLanguageFrontend) handleIndexExpr(fset *token.FileSet, indexExpr *ast.IndexExpr) *cpg.Expression { - a := this.NewArraySubscriptionExpression(fset, indexExpr) - - a.SetArrayExpression(this.handleExpr(fset, indexExpr.X)) - a.SetSubscriptExpression(this.handleExpr(fset, indexExpr.Index)) - - return (*cpg.Expression)(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]) - - // new is a pointer, so need to reference the type with a pointer - 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(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) - - if lhs != nil { - b.SetLHS(lhs) - } - - if rhs != nil { - 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.LogDebug("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.LogDebug("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.LogDebug("Handling literal %+v", *lit) - - 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) - case token.INT: - i, _ := strconv.ParseInt(lit.Value, 10, 64) - value = cpg.NewInteger(int(i)) - t = cpg.TypeParser_createFrom("int", lang) - case token.FLOAT: - // default seems to be float64 - f, _ := strconv.ParseFloat(lit.Value, 64) - value = cpg.NewDouble(f) - t = cpg.TypeParser_createFrom("float64", lang) - case token.IMAG: - case token.CHAR: - value = cpg.NewString(lit.Value) - 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.LogDebug("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) - - 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)) - - for _, elem := range lit.Elts { - expr := this.handleExpr(fset, elem) - - if expr != nil { - l.AddInitializer(expr) - } - } - - return c -} - -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) - - (*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(assert.Type) - - cast.SetExpression(expr) - cast.SetCastType(typ) - - return cast -} - -func (this *GoLanguageFrontend) handleType(typeExpr ast.Expr) *cpg.Type { - var err error - - this.LogDebug("Parsing type %T: %+v", typeExpr, 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.LogDebug("non-fqn type: %s", name) - } else { - name = fmt.Sprintf("%s.%s", this.File.Name.Name, v.Name) - this.LogDebug("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) - return cpg.TypeParser_createFrom(fqn, lang) - case *ast.StarExpr: - t := this.handleType(v.X) - - var i = jnigi.NewObjectRef(cpg.PointerOriginClass) - err = env.GetStaticField(cpg.PointerOriginClass, "POINTER", i) - if err != nil { - log.Fatal(err) - } - - this.LogDebug("Pointer to %s", t.GetName()) - - return t.Reference(i) - case *ast.ArrayType: - t := this.handleType(v.Elt) - - var i = jnigi.NewObjectRef(cpg.PointerOriginClass) - err = env.GetStaticField(cpg.PointerOriginClass, "ARRAY", i) - if err != nil { - log.Fatal(err) - } - - this.LogDebug("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) - - // TODO(oxisto): Find a better way to represent casts - (&(cpg.ObjectType{Type: *t})).AddGeneric(keyType) - (&(cpg.ObjectType{Type: *t})).AddGeneric(valueType) - - return t - case *ast.ChanType: - // handle them similar to maps - t := cpg.TypeParser_createFrom("chan", lang) - chanType := this.handleType(v.Value) - - (&(cpg.ObjectType{Type: *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(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(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} - } - - return &cpg.UnknownType_getUnknown(lang).Type -} - -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 f483536e5e..0000000000 --- a/cpg-language-go/src/main/golang/frontend/statement_builder.go +++ /dev/null @@ -1,88 +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) 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/go.mod b/cpg-language-go/src/main/golang/go.mod index 6074e03f76..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 ( - golang.org/x/mod v0.8.0 - tekao.net/jnigi v0.0.0-20221227053512-56e0101fa996 + github.com/mattn/go-pointer v0.0.1 + golang.org/x/mod v0.12.0 ) diff --git a/cpg-language-go/src/main/golang/go.sum b/cpg-language-go/src/main/golang/go.sum index 11c27b29a9..4ffefdd5d6 100644 --- a/cpg-language-go/src/main/golang/go.sum +++ b/cpg-language-go/src/main/golang/go.sum @@ -1,8 +1,27 @@ -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= -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= +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/golang/helper.go b/cpg-language-go/src/main/golang/helper.go deleted file mode 100644 index 4f1513421b..0000000000 --- a/cpg-language-go/src/main/golang/helper.go +++ /dev/null @@ -1,57 +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/lib/cpg/main.go b/cpg-language-go/src/main/golang/lib/cpg/main.go index 9ca8326023..f276a47a1e 100644 --- a/cpg-language-go/src/main/golang/lib/cpg/main.go +++ b/cpg-language-go/src/main/golang/lib/cpg/main.go @@ -1,110 +1,891 @@ -/* - * 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 ( - "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 } - fset := token.NewFileSet() - file, err := parser.ParseFile(fset, string(path), string(src), parser.ParseComments) - if err != nil { - log.Fatal(err) + for _, c := range comments { + text := strings.TrimRight(c.Text(), "\n") + comment += text } - goFrontend.CommentMap = ast.NewCommentMap(fset, file, file.Comments) + return C.CString(comment) +} - _, err = goFrontend.ParseModule(string(topLevel)) - if err != nil { - goFrontend.LogError("Error occurred while looking for Go modules file: %v", err) +//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.File = file +//export GetIdentNamePos +func GetIdentNamePos(ptr unsafe.Pointer) C.int { + ident := pointer.Restore(ptr).(*ast.Ident) + return C.int(ident.NamePos) +} - tu, err := goFrontend.HandleFile(fset, file, string(path)) - if err != nil { - log.Fatal(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) + } +} + +//export GetNumFieldNames +func GetNumFieldNames(ptr unsafe.Pointer) C.int { + return num[*ast.Field](ptr, func(t *ast.Field) []*ast.Ident { + return t.Names + }) +} + +//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 e5a1fa4e1a..0000000000 --- a/cpg-language-go/src/main/golang/location.go +++ /dev/null @@ -1,61 +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 03c86dc51a..0000000000 --- a/cpg-language-go/src/main/golang/node.go +++ /dev/null @@ -1,140 +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 95b9312fd4..0000000000 --- a/cpg-language-go/src/main/golang/scope.go +++ /dev/null @@ -1,91 +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 481a2fe588..0000000000 --- a/cpg-language-go/src/main/golang/statements.go +++ /dev/null @@ -1,100 +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 - -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 (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 *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 73e6bd48cf..0000000000 --- a/cpg-language-go/src/main/golang/types.go +++ /dev/null @@ -1,171 +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 } - -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 -} - -type ObjectType struct { - Type -} - -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 TypeParser_createFrom(s string, l *Language) *Type { - var t Type - err := env.CallStaticMethod(TypeParserClass, "createFrom", &t, NewCharSequence(s), l) - if err != nil { - log.Fatal(err) - - } - - return &t -} - -func UnknownType_getUnknown(l *Language) *UnknownType { - var t UnknownType - err := env.CallStaticMethod(UnknownTypeClass, "getUnknownType", &t, l) - if err != nil { - log.Fatal(err) - - } - - return &t -} - -func (t *Type) GetRoot() *Type { - var root Type - err := t.CallMethod(env, "getRoot", &root) - if err != nil { - log.Fatal(err) - } - - return &root -} - -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 (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)) - 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..5aa878b488 --- /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.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 + +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 = newParameterDeclaration(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 Block) { + 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..ee5c702d98 --- /dev/null +++ b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/ExpressionHandler.kt @@ -0,0 +1,373 @@ +/* + * 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 = newReference(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): SubscriptExpression { + val ase = newSubscriptExpression(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 = newNewArrayExpression(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): 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 + // 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}" + + newReference(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 [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): SubscriptExpression { + val ase = newSubscriptExpression(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(type, 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..dc21160c7a --- /dev/null +++ b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoHandler.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.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) + + lastNode = 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/GoLanguage.kt b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoLanguage.kt index 7e25efa95f..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,16 +25,16 @@ */ 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 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 @@ -43,6 +43,13 @@ class GoLanguage : Language(), HasShortCircuitOperators, Has 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 = @@ -101,11 +108,4 @@ class GoLanguage : Language(), HasShortCircuitOperators, Has // 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 1c10b07a19..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 @@ -25,85 +25,257 @@ */ 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 import de.fraunhofer.aisec.cpg.frontends.TranslationException +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) -class GoLanguageFrontend( - language: Language, - config: TranslationConfiguration, - scopeManager: ScopeManager, -) : LanguageFrontend(language, config, scopeManager) { - 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" - } +@RegisterExtraPass(GoExtraPass::class) +@ReplacePass( + lang = GoLanguage::class, + old = EvaluationOrderGraphPass::class, + with = GoEvaluationOrderGraphPass::class +) +class GoLanguageFrontend(language: Language, ctx: TranslationContext) : + 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 stream = - GoLanguageFrontend::class.java.getResourceAsStream("/libcpgo-$arch$ext") + val fset = std.NewFileSet() + val f = Parser.parseFile(fset, file.absolutePath, file.readText()) - val tmp = File.createTempFile("libcpgo", ext) - tmp.deleteOnExit() - val fos = FileOutputStream(tmp) - stream.copyTo(FileOutputStream(tmp)) + this.commentMap = std.NewCommentMap(fset, f, f.comments) - fos.close() - stream.close() + currentFile = f + currentFileSet = fset + + val tu = newTranslationUnitDeclaration(file.absolutePath, rawNode = f) + scopeManager.resetToGlobal(tu) + currentTU = tu + + for (spec in f.imports) { + val import = specificationHandler.handle(spec) + scopeManager.addDeclaration(import) + } - log.info("Loading cpgo library from ${tmp.absoluteFile}") + val p = newNamespaceDeclaration(f.name.name) + scopeManager.enterScope(p) - System.load(tmp.absolutePath) - } catch (ex: Exception) { - log.error( - "Error while loading cpgo library. Go frontend will not work correctly", - ex - ) + 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) + + // 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.path, - 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 getCodeFromRawNode(astNode: T): String? { - // this is handled by native code - return null + 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 getLocationFromRawNode(astNode: T): PhysicalLocation? { - // this is handled by native code - return null + override fun codeOf(astNode: GoStandardLibrary.Ast.Node): String? { + return currentFileSet?.code(astNode) } - override fun setComment(s: S, ctx: T) {} + 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: 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..8dd43ab566 --- /dev/null +++ b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/SpecificationHandler.kt @@ -0,0 +1,189 @@ +/* + * 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!!) + } else { + decl.type = autoType() + } + + // 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..3d84a4a80f --- /dev/null +++ b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/StatementHandler.kt @@ -0,0 +1,324 @@ +/* + * 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 = newBlock(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 = newReference("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 Reference) { + 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 Reference) { + 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? 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 + // 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 new file mode 100644 index 0000000000..f9c20d952e --- /dev/null +++ b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/GoExtraPass.kt @@ -0,0 +1,287 @@ +/* + * 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.* +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.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 +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(ctx: TranslationContext) : ComponentPass(ctx), ScopeProvider { + + override val scope: Scope? + get() = scopeManager.currentScope + + override fun accept(component: Component) { + 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 component.translationUnits) { + walker.iterate(tu) + } + } + + /** + * handleForEachStatement adds a [HasType.TypeObserver] 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).registerTypeObserver( + object : HasType.TypeObserver { + override fun typeChanged(newType: Type, src: HasType) { + 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 = forEach.primitiveType("int") + + // 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 assignedTypeChanged(assignedTypes: Set, src: HasType) { + // 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 Reference) { + // 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.autoType()) + 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) { + // 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) { + 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(ctx) + ?.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 Reference && 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(), 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.typeExists(fqn.toString())) { + replaceCallWithCast(fqn, parent, call) + } + } + } + } + + private fun replaceCallWithCast( + typeName: CharSequence, + parent: Node, + call: CallExpression, + ) { + val cast = parent.newCastExpression(call.code) + cast.location = call.location + cast.castType = call.objectType(typeName) + 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..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 @@ -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.Reference +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..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 @@ -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 = @@ -73,6 +70,70 @@ 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) } + } + + @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 2ffdb45db9..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 @@ -31,16 +31,15 @@ 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 -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() { @@ -53,19 +52,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 @@ -83,21 +82,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 @@ -113,27 +112,23 @@ 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 + val body = main.body as? Block 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) - assertEquals(TypeParser.createFrom("p.MyStruct*", GoLanguage()), new.type) + val new = assertIs(decl.firstAssignment) + with(tu) { assertEquals(objectType("p.MyStruct").pointer(), new.type) } val construct = new.initializer as? ConstructExpression assertNotNull(construct) @@ -141,49 +136,43 @@ 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) + 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) 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. - assertEquals(TypeParser.createFrom("map[string,string]", GoLanguage()), make.type) + // temporary solution. This should be fixed in the future. + assertEquals( + tu.objectType("map", listOf(tu.primitiveType("string"), tu.primitiveType("string"))), + 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) + assertEquals(tu.objectType("chan", listOf(tu.primitiveType("int"))), make.type) } @Test @@ -196,39 +185,51 @@ 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) + assertEquals(tu.primitiveType("int"), a.type) - val s = p.byNameOrNull("s") + val s = p.variables["s"] assertNotNull(s) assertLocalName("s", s) - assertEquals(TypeParser.createFrom("string", GoLanguage()), s.type) + assertEquals(tu.primitiveType("string"), s.type) - val f = p.byNameOrNull("f") + val f = p.variables["f"] assertNotNull(f) assertLocalName("f", f) - assertEquals(TypeParser.createFrom("float64", GoLanguage()), f.type) + assertEquals(tu.primitiveType("float64"), f.type) - val f32 = p.byNameOrNull("f32") + val f32 = p.variables["f32"] assertNotNull(f32) assertLocalName("f32", f32) - assertEquals(TypeParser.createFrom("float32", GoLanguage()), f32.type) + assertEquals(tu.primitiveType("float32"), f32.type) - val n = p.byNameOrNull("n") + val n = p.variables["n"] assertNotNull(n) - assertEquals(TypeParser.createFrom("int*", GoLanguage()), n.type) + with(tu) { assertEquals(tu.primitiveType("int").pointer(), n.type) } val nil = n.initializer as? Literal<*> 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,10 +266,10 @@ 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.statements.first() as? CallExpression + var callExpression = body.calls.firstOrNull() assertNotNull(callExpression) assertLocalName("myTest", callExpression) @@ -277,11 +278,11 @@ class GoLanguageFrontendTest : BaseTest() { val s = myTest.parameters.first() assertNotNull(s) assertLocalName("s", s) - assertEquals(TypeParser.createFrom("string", GoLanguage()), s.type) + assertEquals(tu.primitiveType("string"), s.type) assertLocalName("myTest", myTest) - body = myTest.body as? CompoundStatement + body = myTest.body as? Block assertNotNull(body) callExpression = body.statements.first() as? CallExpression @@ -294,25 +295,23 @@ class GoLanguageFrontendTest : BaseTest() { assertNotNull(literal) assertEquals("%s", literal.value) - assertEquals(TypeParser.createFrom("string", GoLanguage()), literal.type) + 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) 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? Reference 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<*> @@ -325,14 +324,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 @@ -359,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(TypeParser.createFrom("int", GoLanguage()), myField.type) - - val myInterface = - p.getDeclarationsByName("p.MyInterface", RecordDeclaration::class.java) - .iterator() - .next() + assertEquals(tu.primitiveType("int"), myField.type) + val myInterface = p.records["p.MyInterface"] assertNotNull(myInterface) assertEquals("interface", myInterface.kind) @@ -388,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) @@ -422,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) @@ -438,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 @@ -457,19 +451,19 @@ 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 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) + assertEquals(myFunc.receiver, (lhs.base as? Reference)?.refersTo) assertLocalName("Field", lhs) - assertEquals(TypeParser.createFrom("int", GoLanguage()), lhs.type) + assertEquals(tu.primitiveType("int"), lhs.type) - val rhs = binOp.rhs as? DeclaredReferenceExpression + val rhs = assign.rhs.firstOrNull() as? Reference assertNotNull(rhs) assertFullName("otherPackage.OtherField", rhs) } @@ -486,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 = @@ -495,11 +489,11 @@ class GoLanguageFrontendTest : BaseTest() { assertNotNull(b) assertLocalName("b", b) - assertEquals(TypeParser.createFrom("bool", GoLanguage()), 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 - val initializer = b.initializer as? DeclaredReferenceExpression + val initializer = b.initializer as? Reference assertNotNull(initializer) assertLocalName("true", initializer) @@ -522,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 @@ -587,18 +581,18 @@ 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.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) + with(tu) { + // type will be inferred from the function declaration + assertEquals(objectType("p.MyStruct").pointer(), 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,9 +606,12 @@ 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) + + val go = main.calls["go"] + assertNotNull(go) } @Test @@ -631,15 +628,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.statement is Block) + 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,15 +682,28 @@ 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) - 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)) + + 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 @@ -691,10 +716,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) @@ -706,11 +731,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) @@ -737,9 +762,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? Reference)?.refersTo == i + } + assertNotNull(assign) + + val call = assertIs(assign.value) + assertLocalName("myTest", call) } } 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..1abacc2c4c --- /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.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 +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 Reference && 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/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/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/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..4b7207b8c0 100644 --- a/cpg-language-go/src/test/resources/golang/literal.go +++ b/cpg-language-go/src/test/resources/golang/literal.go @@ -4,4 +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 +} 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 diff --git a/cpg-language-go/src/test/resources/log4j2.xml b/cpg-language-go/src/test/resources/log4j2.xml index 359d8071bf..5b73082e2c 100644 --- a/cpg-language-go/src/test/resources/log4j2.xml +++ b/cpg-language-go/src/test/resources/log4j2.xml @@ -2,11 +2,11 @@ - + - + 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..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 @@ -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,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) : @@ -60,28 +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 - .stream() - .map { type: ReferenceType -> this.parseType(type.asString()) } - .collect(Collectors.toList()) + 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 @@ -91,21 +85,19 @@ open class DeclarationHandler(lang: JavaLanguageFrontend) : frontend.scopeManager.addDeclaration(param) } - val name = - frontend.scopeManager - .firstScopeOrNull { RecordScope::class.java.isInstance(it) } - ?.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 } // 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 } @@ -125,23 +117,19 @@ 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? = - 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()) } val param = - this.newParamVariableDeclaration( + this.newParameterDeclaration( parameter.nameAsString, resolvedType, parameter.isVarArgs @@ -171,15 +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", - if (recordDecl != null) this.parseType(recordDecl.name) - else UnknownType.getUnknownType(language), + recordDeclaration?.toType() ?: unknownType(), "this", false ) @@ -199,22 +186,17 @@ 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()) - TypeManager.getInstance() - .addTypeParameter( - recordDeclaration, - classInterDecl.typeParameters - .stream() - .map { t: TypeParameter -> ParameterizedType(t.nameAsString, language) } - .collect(Collectors.toList()) - ) + .map { type -> frontend.getTypeAsGoodAsPossible(type) } + .toMutableList() + + frontend.typeManager.addTypeParameter( + recordDeclaration, + classInterDecl.typeParameters.map { ParameterizedType(it.nameAsString, language) } + ) // TODO: I cannot replicate the old partionedBy logic val staticImports = @@ -251,30 +233,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()) { @@ -298,19 +287,18 @@ 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 = this.parseType(scope.name!!) + if (scope?.name != null) { + val fieldType = scope.name?.let { this.objectType(it) } ?: unknownType() // Enter the scope of the inner class because the new field belongs there. frontend.scopeManager.enterScope(recordDeclaration) val field = this.newFieldDeclaration( - "this$" + scope.name!!.localName, + "this$" + scope.name?.localName, fieldType, - listOf(), + listOf(), null, null, null @@ -329,13 +317,8 @@ 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 joinedModifiers = java.lang.String.join(" ", modifiers) + " " - val location = frontend.getLocationFromRawNode(fieldDecl) + val modifiers = fieldDecl.modifiers.map { modifier -> modifier.keyword.asString() } + val location = frontend.locationOf(fieldDecl) val initializer = variable.initializer .map { ctx: Expression -> frontend.expressionHandler.handle(ctx) } @@ -344,28 +327,27 @@ open class DeclarationHandler(lang: JavaLanguageFrontend) : try { // Resolve type first with ParameterizedType type = - TypeManager.getInstance() - .getTypeParameter( - frontend.scopeManager.currentRecord, - variable.resolve().type.describe() - ) - ?: this.parseType(joinedModifiers + variable.resolve().type.describe()) + frontend.typeManager.getTypeParameter( + frontend.scopeManager.currentRecord, + 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 } } @@ -387,11 +369,11 @@ 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? } - 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 @@ -405,7 +387,7 @@ open class DeclarationHandler(lang: JavaLanguageFrontend) : return this.newEnumConstantDeclaration( enumConstDecl.nameAsString, enumConstDecl.toString(), - frontend.getLocationFromRawNode(enumConstDecl) + frontend.locationOf(enumConstDecl) ) } @@ -433,7 +415,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 a4663a48c3..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 @@ -28,25 +28,46 @@ 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.expr.* +import com.github.javaparser.ast.body.VariableDeclarator +import com.github.javaparser.ast.expr.ArrayAccessExpr +import com.github.javaparser.ast.expr.ArrayCreationExpr +import com.github.javaparser.ast.expr.ArrayInitializerExpr +import com.github.javaparser.ast.expr.BinaryExpr +import com.github.javaparser.ast.expr.BooleanLiteralExpr +import com.github.javaparser.ast.expr.CharLiteralExpr +import com.github.javaparser.ast.expr.ClassExpr +import com.github.javaparser.ast.expr.DoubleLiteralExpr +import com.github.javaparser.ast.expr.EnclosedExpr import com.github.javaparser.ast.expr.Expression +import com.github.javaparser.ast.expr.FieldAccessExpr +import com.github.javaparser.ast.expr.InstanceOfExpr +import com.github.javaparser.ast.expr.IntegerLiteralExpr +import com.github.javaparser.ast.expr.LiteralExpr +import com.github.javaparser.ast.expr.LongLiteralExpr +import com.github.javaparser.ast.expr.MethodCallExpr +import com.github.javaparser.ast.expr.NameExpr +import com.github.javaparser.ast.expr.NullLiteralExpr +import com.github.javaparser.ast.expr.ObjectCreationExpr +import com.github.javaparser.ast.expr.StringLiteralExpr +import com.github.javaparser.ast.expr.SuperExpr +import com.github.javaparser.ast.expr.ThisExpr +import com.github.javaparser.ast.expr.UnaryExpr +import com.github.javaparser.ast.expr.VariableDeclarationExpr import com.github.javaparser.resolution.UnsolvedSymbolException import com.github.javaparser.resolution.declarations.ResolvedMethodDeclaration import de.fraunhofer.aisec.cpg.frontends.Handler import de.fraunhofer.aisec.cpg.frontends.HandlerInterface import de.fraunhofer.aisec.cpg.graph.* -import de.fraunhofer.aisec.cpg.graph.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.UnknownType +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 +import kotlin.jvm.optionals.getOrNull import org.slf4j.LoggerFactory class ExpressionHandler(lang: JavaLanguageFrontend) : @@ -54,13 +75,13 @@ 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) val param = - this.newParamVariableDeclaration( + this.newParameterDeclaration( parameter.nameAsString, resolvedType, parameter.isVarArgs @@ -97,24 +118,24 @@ 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) + // castExpression.expression.registerTypeListener(castExpression) } return castExpression } /** - * 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 @@ -126,10 +147,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 @@ -137,9 +156,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) } @@ -152,15 +180,17 @@ 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()) - 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?)!! + val arraySubsExpression = this.newSubscriptExpression(expr.toString()) + (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 } @@ -172,35 +202,36 @@ 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 { - UnknownType.getUnknownType(this.language) + unknownType() } } catch (e: NoClassDefFoundError) { val s = frontend.recoverTypeFromUnsolvedException(e) if (s != null) { - this.parseType(s) + this.objectType(s) } else { - UnknownType.getUnknownType(this.language) + unknownType() } } 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 { + private fun handleAssignmentExpression(expr: Expression): AssignExpression { val assignExpr = expr.asAssignExpr() // first, handle the target. this is the first argument of the operator call @@ -214,11 +245,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 @@ -244,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 @@ -261,7 +296,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 @@ -274,12 +309,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 @@ -293,17 +328,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) - UnknownType.getUnknownType(language) + 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 @@ -316,28 +351,20 @@ 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) - UnknownType.getUnknownType(language) + unknownType() } } } - base = - this.newDeclaredReferenceExpression( - scope.asNameExpr().nameAsString, - baseType, - scope.toString() - ) + base = this.newReference(scope.asNameExpr().nameAsString, baseType, 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? + 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 @@ -345,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 @@ -356,16 +383,15 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : scope.asFieldAccessExpr().nameAsString } val qualifiedNameFromImports = frontend.getQualifiedNameFromImports(name) - val baseType: Type - baseType = + val baseType = if (qualifiedNameFromImports != null) { - this.parseType(qualifiedNameFromImports) + this.objectType(qualifiedNameFromImports) } else { log.info("Unknown base type 2 for {}", fieldAccessExpr) - UnknownType.getUnknownType(language) + unknownType() } base = - this.newDeclaredReferenceExpression( + this.newReference( scope.asFieldAccessExpr().nameAsString, baseType, scope.toString() @@ -374,35 +400,36 @@ 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 { 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()) + 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) - UnknownType.getUnknownType(language) + unknownType() } val memberExpression = this.newMemberExpression( fieldAccessExpr.name.identifier, - base!!, + base, fieldType, "." // there is only "." in java ) @@ -412,20 +439,20 @@ 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) - UnknownType.getUnknownType(language) + unknownType() } 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) { - base.location = frontend.getLocationFromRawNode(fieldAccessExpr) + if (base.location == null) { + base.location = frontend.locationOf(fieldAccessExpr) } return this.newMemberExpression(fieldAccessExpr.name.identifier, base, fieldType, ".") } @@ -433,53 +460,53 @@ 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.primitiveType("int"), + value + ) + is StringLiteralExpr -> + newLiteral( + literalExpr.asStringLiteralExpr().asString(), + this.primitiveType("java.lang.String"), + value + ) + is BooleanLiteralExpr -> + newLiteral( + literalExpr.asBooleanLiteralExpr().value, + this.primitiveType("boolean"), + value + ) + is CharLiteralExpr -> + newLiteral( + literalExpr.asCharLiteralExpr().asChar(), + this.primitiveType("char"), + value + ) + is DoubleLiteralExpr -> + newLiteral( + literalExpr.asDoubleLiteralExpr().asDouble(), + this.primitiveType("double"), + value + ) + is LongLiteralExpr -> + newLiteral( + literalExpr.asLongLiteralExpr().asNumber(), + this.primitiveType("long"), + value + ) + is NullLiteralExpr -> newLiteral(null, this.objectType("null"), value) + else -> null } - return null } - private fun handleClassExpression(expr: Expression): DeclaredReferenceExpression { + private fun handleClassExpression(expr: Expression): Reference { val classExpr = expr.asClassExpr() - val type = this.parseType(classExpr.type.asString()) + val type = frontend.typeOf(classExpr.type) val thisExpression = - this.newDeclaredReferenceExpression( + this.newReference( classExpr.toString().substring(classExpr.toString().lastIndexOf('.') + 1), type, classExpr.toString() @@ -489,10 +516,10 @@ 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.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 @@ -504,21 +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.getUnknownType(language), - expr.toString() - ) + val superExpression = this.newReference(expr.toString(), unknownType(), expr.toString()) frontend.setCodeAndLocation(superExpression, expr) return superExpression } @@ -535,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()); @@ -544,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) @@ -592,19 +614,17 @@ 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()) + 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? - typeString = + val typeString: String? = if ( ex.name.startsWith( "We are unable to find the value declaration corresponding to" @@ -616,26 +636,25 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : } val t: Type if (typeString == null) { - t = UnknownType.getUnknownType(language) + t = unknownType() } else { - t = this.parseType(typeString) + 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 } 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()) + this.newReference(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()) + this.newReference(nameExpr.nameAsString, t, nameExpr.toString()) } } @@ -654,7 +673,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()) @@ -712,7 +731,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 { @@ -733,15 +752,17 @@ 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) { + if (base is Reference && base.refersTo is RecordDeclaration) { isStatic = true } } else { @@ -756,15 +777,14 @@ 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!!) - base = this.newDeclaredReferenceExpression(baseName, baseType) + baseType = this.objectType(baseName ?: Type.UNKNOWN_TYPE_STRING) + base = this.newReference(baseName, baseType) } else { // Since it is possible to omit the "this" keyword, some methods in java do not have // a base. @@ -774,15 +794,14 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : base = createImplicitThis() } } - val member = - this.newMemberExpression(name, base!!, UnknownType.getUnknownType(language), ".") + 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 = this.parseType(typeString) + callExpression.type = typeString?.let { this.objectType(it) } ?: unknownType() val arguments = methodCallExpr.arguments // handle the arguments @@ -790,8 +809,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 } @@ -806,8 +827,8 @@ 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) - base = this.newDeclaredReferenceExpression("this", thisType, "this") + ?: unknownType() + base = this.newReference("this", thisType, "this") base.isImplicit = true return base } @@ -848,7 +869,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 @@ -858,7 +879,7 @@ class ExpressionHandler(lang: JavaLanguageFrontend) : frontend.scopeManager.enterScope(anonymousRecord) - anonymousRecord.addSuperClass(TypeParser.createFrom(constructorName, language)) + 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 @@ -877,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) @@ -896,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) } @@ -911,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/JavaLanguage.kt b/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/JavaLanguage.kt index d1a8a6a528..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 @@ -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 @@ -58,6 +57,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). @@ -78,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: @@ -96,16 +104,10 @@ 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) } - 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 60fdafb7b2..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,17 +36,21 @@ 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 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 @@ -56,7 +60,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 @@ -67,16 +70,14 @@ 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( 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? @@ -110,23 +111,23 @@ 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 = - newNamespaceDeclaration(packDecl.name.asString(), getCodeFromRawNode(packDecl)) + newNamespaceDeclaration(packDecl.name.asString(), codeOf(packDecl)) setCodeAndLocation(namespaceDeclaration, packDecl) scopeManager.addDeclaration(namespaceDeclaration) 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 @@ -134,7 +135,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) } @@ -175,38 +176,31 @@ open class JavaLanguageFrontend( 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 } @@ -218,8 +212,8 @@ open class JavaLanguageFrontend( return try { val type = nodeWithType.typeAsString if (type == "var") { - UnknownType.getUnknownType(language) - } else parseType(resolved.type.describe()) + unknownType() + } else typeOf(resolved.type) } catch (ex: RuntimeException) { getTypeFromImportIfPossible(nodeWithType.type) } catch (ex: NoClassDefFoundError) { @@ -230,8 +224,8 @@ open class JavaLanguageFrontend( fun getTypeAsGoodAsPossible(type: Type): de.fraunhofer.aisec.cpg.graph.types.Type { return try { if (type.toString() == "var") { - UnknownType.getUnknownType(language) - } else parseType(type.resolve().describe()) + unknownType() + } else typeOf(type.resolve()) } catch (ex: RuntimeException) { getTypeFromImportIfPossible(type) } catch (ex: NoClassDefFoundError) { @@ -302,14 +296,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() @@ -348,10 +346,12 @@ 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()) + type = typeOf(resolved.returnType) } type } catch (ex: RuntimeException) { @@ -385,33 +385,34 @@ open class JavaLanguageFrontend( // 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 } 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) + 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 - val o = context!!.packageDeclaration - if (o.isPresent) { - name = o.get().nameAsString + language.namespaceDelimiter + name + // as our current translation unit, so we can "adjust" the name to an FQN + val o = context?.packageDeclaration + if (o?.isPresent == true) { + 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 } @@ -423,13 +424,8 @@ open class JavaLanguageFrontend( 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 } } /** @@ -450,7 +446,7 @@ open class JavaLanguageFrontend( 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 @@ -460,7 +456,7 @@ open class JavaLanguageFrontend( newAnnotationMember( pair.nameAsString, expressionHandler.handle(pair.value) as Expression, - getCodeFromRawNode(pair) + codeOf(pair) ) members.add(member) } @@ -472,7 +468,7 @@ open class JavaLanguageFrontend( newAnnotationMember( ANNOTATION_MEMBER_VALUE, expressionHandler.handle(value.asLiteralExpr()) as Expression, - getCodeFromRawNode(value) + codeOf(value) ) members.add(member) } @@ -483,8 +479,33 @@ open class JavaLanguageFrontend( 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 { - @JvmField val JAVA_EXTENSIONS = listOf(".java") const val THIS = "this" const val ANNOTATION_MEMBER_VALUE = "value" } @@ -495,9 +516,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..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 @@ -46,9 +60,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 +74,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 } @@ -132,7 +146,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 @@ -159,7 +173,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 } @@ -189,31 +203,34 @@ 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) { + 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) { @@ -230,7 +247,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) { @@ -240,31 +257,34 @@ 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) { + 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) { @@ -300,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 } @@ -330,15 +350,15 @@ 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) - compoundStatement.addStatement(statement!!) + statement?.let { compoundStatement.addStatement(it) } } frontend.setCodeAndLocation(compoundStatement, stmt) frontend.scopeManager.leaveScope(compoundStatement) @@ -349,7 +369,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) { @@ -448,7 +468,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 } @@ -477,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()) { @@ -487,7 +507,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 @@ -495,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) { @@ -508,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? -> @@ -561,7 +585,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) { @@ -569,7 +593,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) @@ -582,7 +606,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 @@ -596,91 +620,70 @@ class StatementHandler(lang: JavaLanguageFrontend?) : } init { - map[IfStmt::class.java] = HandlerInterface { stmt: Statement -> handleIfStatement(stmt) } - map[AssertStmt::class.java] = - HandlerInterface { - stmt: Statement -> + 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[WhileStmt::class.java] = - HandlerInterface { - stmt: Statement -> + map[com.github.javaparser.ast.stmt.WhileStmt::class.java] = + HandlerInterface { stmt: Statement -> handleWhileStatement(stmt) } - map[DoStmt::class.java] = - HandlerInterface { - stmt: Statement -> + map[com.github.javaparser.ast.stmt.DoStmt::class.java] = + HandlerInterface { stmt: Statement -> handleDoStatement(stmt) } - map[ForEachStmt::class.java] = - HandlerInterface { - stmt: Statement -> + map[com.github.javaparser.ast.stmt.ForEachStmt::class.java] = + HandlerInterface { stmt: Statement -> handleForEachStatement(stmt) } - map[ForStmt::class.java] = - HandlerInterface { - stmt: Statement -> + map[com.github.javaparser.ast.stmt.ForStmt::class.java] = + HandlerInterface { stmt: Statement -> handleForStatement(stmt) } - map[BreakStmt::class.java] = - HandlerInterface { - stmt: Statement -> + map[com.github.javaparser.ast.stmt.BreakStmt::class.java] = + HandlerInterface { stmt: Statement -> handleBreakStatement(stmt) } - map[ContinueStmt::class.java] = - HandlerInterface { - stmt: Statement -> + map[com.github.javaparser.ast.stmt.ContinueStmt::class.java] = + HandlerInterface { stmt: Statement -> handleContinueStatement(stmt) } - map[ReturnStmt::class.java] = - HandlerInterface { - stmt: Statement -> + map[com.github.javaparser.ast.stmt.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 -> + 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[com.github.javaparser.ast.stmt.SwitchStmt::class.java] = + HandlerInterface { stmt: Statement -> handleSwitchStatement(stmt) } - map[EmptyStmt::class.java] = - HandlerInterface { - stmt: Statement -> + map[com.github.javaparser.ast.stmt.EmptyStmt::class.java] = + HandlerInterface { stmt: Statement -> handleEmptyStatement(stmt) } - map[SynchronizedStmt::class.java] = - HandlerInterface { - stmt: Statement -> + map[com.github.javaparser.ast.stmt.SynchronizedStmt::class.java] = + HandlerInterface { stmt: Statement -> handleSynchronizedStatement(stmt) } - map[TryStmt::class.java] = - HandlerInterface { - stmt: Statement -> + map[com.github.javaparser.ast.stmt.TryStmt::class.java] = + HandlerInterface { stmt: Statement -> handleTryStatement(stmt) } - map[ThrowStmt::class.java] = - HandlerInterface { - stmt: Statement -> - handleThrowStmt(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 9ef45aa1a5..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 @@ -30,10 +30,11 @@ 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.statements.expressions.DeclaredReferenceExpression +import de.fraunhofer.aisec.cpg.graph.objectType 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.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 @@ -44,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 @@ -62,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 @@ -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? Reference)?.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 } @@ -111,9 +117,7 @@ class JavaCallResolverHelper { ): RecordDeclaration? { val baseName = callee.base.name.parent ?: return null - if ( - TypeParser.createFrom(baseName, curClass.language) 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 ed17eb1e5d..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 @@ -29,11 +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.TranslationResult +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.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 @@ -43,33 +43,32 @@ import org.slf4j.LoggerFactory @DependsOn(TypeHierarchyResolver::class) @ExecuteBefore(ImportResolver::class) @RequiredFrontend(JavaLanguageFrontend::class) -class JavaExternalTypeHierarchyResolver : Pass() { - override fun accept(translationResult: TranslationResult) { +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()) - 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 = - CommonPath.commonPath( - translationResult.config.softwareComponents[ - translationResult.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 {}", - 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)) } - 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) { @@ -77,7 +76,7 @@ class JavaExternalTypeHierarchyResolver : Pass() { 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.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/enhancements/EOGTest.kt b/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/EOGTest.kt index 6b0351bc01..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 @@ -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,11 +755,11 @@ 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[ - { l: DeclaredReferenceExpression -> + { l: Reference -> l.location?.region?.startLine == 8 && l.name.localName == "a" }] assertNotNull(a) @@ -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/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/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 9534e456a1..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 @@ -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 @@ -133,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 @@ -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.primitiveType("java.lang.String"), sDecl.type) // should contain a single statement val sce = forEachStatement.statement as? MemberCallExpression @@ -155,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) } @@ -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.objectType("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.objectType("NotResolvableTypeException"), catchClauses[1].parameter?.type) // third type should have been resolved through the import - assertEquals(createTypeFrom("some.ImportedException"), (catchClauses[2].parameter)?.type) + assertEquals(tu.objectType("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.primitiveType("java.lang.Integer"), method.returnTypes.firstOrNull()) val functionType = method.type as? FunctionType assertNotNull(functionType) @@ -335,13 +333,13 @@ internal class JavaLanguageFrontendTest : BaseTest() { } val graphNodes = SubgraphWalker.flattenAST(declaration) - assertTrue(graphNodes.size != 0) + assertTrue(graphNodes.isNotEmpty()) val switchStatements = graphNodes.filterIsInstance() 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) @@ -376,8 +374,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,19 +384,18 @@ 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 - val ref = cast.expression as? DeclaredReferenceExpression + val ref = cast.expression as? Reference assertNotNull(ref) assertEquals(e, ref.refersTo) } @@ -421,17 +418,19 @@ 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 assertNotNull(a) - // type should be Integer[] - assertEquals(createTypeFrom("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 + val ace = a.initializer as? NewArrayExpression assertNotNull(ace) // which has a initializer list (1 entry) @@ -447,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) } @@ -471,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 @@ -628,25 +627,21 @@ 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 receiver = - (lhs?.base as? DeclaredReferenceExpression)?.refersTo as? VariableDeclaration? + val lhs = op.lhs() + val receiver = (lhs?.base as? Reference)?.refersTo as? VariableDeclaration? assertNotNull(receiver) assertLocalName("this", receiver) - assertEquals(createTypeFrom("my.Animal"), receiver.type) + assertEquals(tu.objectType("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) { @@ -674,12 +669,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") @@ -778,16 +767,14 @@ 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 Reference).refersTo assertNotNull(ref) 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/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/graph/types/TypeTests.kt b/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/types/TypeTests.kt index 04dd3d2c3a..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 @@ -25,13 +25,10 @@ */ 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.graph.* import java.nio.file.Path @@ -40,85 +37,6 @@ import kotlin.test.* internal class TypeTests : BaseTest() { - @Test - fun createFromJava() { - 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 - ) - assertEquals(expected, result) - - // Test 8: generics - typeString = "List list" - result = TypeParser.createFrom(typeString, JavaLanguage()) - var generics: MutableList = ArrayList() - 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) - } - // Tests on the resulting graph @Test @Throws(Exception::class) @@ -129,22 +47,23 @@ internal class TypeTests : BaseTest() { // Check Parameterized val recordDeclarations = result.records val recordDeclarationBox = findByUniqueName(recordDeclarations, "Box") - val typeT = TypeManager.getInstance().getTypeParameter(recordDeclarationBox, "T") + val typeT = result.finalCtx.typeManager.getTypeParameter(recordDeclarationBox, "T") assertNotNull(typeT) - assertEquals(typeT, TypeManager.getInstance().getTypeParameter(recordDeclarationBox, "T")) + 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") - assertEquals(typeT, fieldDeclarationT.type) - 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") @@ -202,16 +121,15 @@ 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) + 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( @@ -220,8 +138,7 @@ internal class TypeTests : BaseTest() { level1: Type, level1b: Type, level2: Type, - unrelated: Type, - result: TranslationResult + unrelated: Type ) { /* Type hierarchy: @@ -233,56 +150,33 @@ internal class TypeTests : BaseTest() { | Level2 */ - val provider = result.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(t, setOf(t).commonType) } // 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(root, setOf(t, root).commonType) } // 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(level0, setOf(t, level0).commonType) } // Level1 and Level1B have Level0 as common ancestor - assertEquals( - Optional.of(level0), - TypeManager.getInstance().getCommonType(listOf(level1, level1b), provider) - ) + assertEquals(level0, setOf(level1, level1b).commonType) // Level2 and Level1B have Level0 as common ancestor - assertEquals( - Optional.of(level0), - TypeManager.getInstance().getCommonType(listOf(level2, level1b), provider) - ) + assertEquals(level0, setOf(level2, level1b).commonType) // Level1 and Level2 have Level1 as common ancestor - assertEquals( - Optional.of(level1), - TypeManager.getInstance().getCommonType(listOf(level1, level2), provider) - ) + 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.getInstance().getCommonType(listOf(unrelated, t), provider) - ) + assertEquals(null, setOf(unrelated, t).commonType) } } } 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..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.* @@ -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) @@ -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" @@ -101,16 +101,15 @@ 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) - 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" @@ -118,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/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolverTest.kt b/cpg-language-java/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolverTest.kt index ec5fbbb98a..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 @@ -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.primitiveType("int") + val stringType = tu.primitiveType(("java.lang.String")) testMethods(records, intType, stringType) testOverriding(records) ensureNoUnknownClassDummies(records) 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/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-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 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..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 @@ -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 @@ -107,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) @@ -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 = newParameterDeclaration(paramName, type, false, frontend.codeOf(param)) frontend.scopeManager.addDeclaration(decl) frontend.bindingsCache[paramSymbolName] = decl @@ -155,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) } } @@ -222,22 +215,12 @@ 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" - val field = - newFieldDeclaration( - fieldName, - fieldType, - listOf(), - "", - null, - null, - false, - frontend.language - ) + val field = newFieldDeclaration(fieldName, fieldType, listOf(), "", null, null, false) frontend.scopeManager.addDeclaration(field) } @@ -260,8 +243,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" @@ -270,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 a4f8d08e40..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 @@ -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 @@ -62,18 +61,14 @@ 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.getUnknownType(language), - frontend.getCodeFromRawNode(value) - ) + newLiteral(null, unknownType(), 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 @@ -85,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) - ) + newReference(name, frontend.typeOf(value), frontend.codeOf(value)) } LLVMMetadataAsValueValueKind, LLVMInlineAsmValueKind -> { @@ -97,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 -> { @@ -109,7 +100,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) @@ -120,30 +111,26 @@ 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") + newReference("undef", cpgType, "undef") } else if (LLVMIsPoison(value) == 1) { - return newDeclaredReferenceExpression("poison", cpgType, "poison") + newReference("poison", cpgType, "poison") } else { log.error("Unknown expression {}", kind) - return newProblemExpression( + newProblemExpression( "Unknown expression $kind", ProblemNode.ProblemType.TRANSLATION, - frontend.getCodeFromRawNode(value) + frontend.codeOf(value) ) } } } } - /** 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.getCodeFromRawNode(valueRef) - ) + return newReference(valueRef.name, frontend.typeOf(valueRef), frontend.codeOf(valueRef)) } /** @@ -161,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 @@ -242,7 +229,7 @@ class ExpressionHandler(lang: LLVMIRLanguageFrontend) : ?: newProblemExpression( "Wrong type of constant binary operation +", ProblemNode.ProblemType.TRANSLATION, - frontend.getCodeFromRawNode(value) + frontend.codeOf(value) ) LLVMSub, LLVMFSub -> @@ -250,7 +237,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) @@ -258,20 +245,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) ) } } @@ -289,7 +276,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 @@ -318,15 +305,12 @@ 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 arrayType = LLVMTypeOf(valueRef) + val list = + newInitializerListExpression(frontend.typeOf(valueRef), frontend.codeOf(valueRef)) val length = if (LLVMIsAConstantDataArray(valueRef) != null) { LLVMGetArrayLength(arrayType) @@ -353,9 +337,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 @@ -369,7 +355,7 @@ class ExpressionHandler(lang: LLVMIRLanguageFrontend) : expr.addArgument(arg) } - return expr + expr } } @@ -378,9 +364,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 @@ -394,14 +382,14 @@ class ExpressionHandler(lang: LLVMIRLanguageFrontend) : expr.addArgument(arg) } - return expr + expr } } /** 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)) } /** @@ -410,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 = @@ -445,7 +433,7 @@ class ExpressionHandler(lang: LLVMIRLanguageFrontend) : newProblemExpression( "Default node for getelementptr", ProblemNode.ProblemType.TRANSLATION, - frontend.getCodeFromRawNode(instr) + frontend.codeOf(instr) ) // loop through all operands / indices @@ -460,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()) @@ -469,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 @@ -520,21 +508,14 @@ 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 - baseType = field?.type ?: UnknownType.getUnknownType(language) + baseType = field?.type ?: unknownType() // construct our member expression - expr = - newMemberExpression( - fieldName, - base, - field?.type ?: UnknownType.getUnknownType(), - ".", - "" - ) + expr = newMemberExpression(fieldName, base, field?.type ?: unknownType(), ".", "") log.info("{}", expr) // the current expression is the new base @@ -570,7 +551,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/LLVMIRLanguage.kt b/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/LLVMIRLanguage.kt index 417fbb7498..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 @@ -40,6 +38,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. @@ -59,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 1f5ba20567..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 @@ -25,17 +25,16 @@ */ 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.* 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.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.statements.expressions.Reference import de.fraunhofer.aisec.cpg.graph.types.* import de.fraunhofer.aisec.cpg.helpers.Benchmark import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker @@ -46,15 +45,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, - 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) @@ -63,21 +65,16 @@ 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 * [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() - 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 @@ -89,11 +86,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) @@ -107,22 +104,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) @@ -156,12 +152,16 @@ class LLVMIRLanguageFrontend( counter++ } - LLVMContextDispose(ctx) + LLVMContextDispose(ctxRef) bench.addMeasurement() return tu } + override fun typeOf(type: LLVMTypeRef): Type { + return typeOf(type, mutableMapOf()) + } + /** Returns a pair of the name and symbol name of [valueRef]. */ fun getNameOf(valueRef: LLVMValueRef): Pair { var name = valueRef.name @@ -179,22 +179,20 @@ class LLVMIRLanguageFrontend( 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 { 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 UnknownType.getUnknownType(language) + if (typeRef in alreadyVisited) { + return alreadyVisited[typeRef] ?: unknownType() } alreadyVisited[typeRef] = null val res: Type = @@ -202,19 +200,19 @@ class LLVMIRLanguageFrontend( 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 @@ -222,23 +220,25 @@ class LLVMIRLanguageFrontend( 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 + } /** Determines if a struct with [name] exists in the scope. */ fun isKnownStructTypeName(name: String): Boolean { @@ -258,11 +258,11 @@ class LLVMIRLanguageFrontend( } fun guessSlotNumber(valueRef: LLVMValueRef): String { - val code = getCodeFromRawNode(valueRef) - if (code?.contains("=") == true) { - return code.split("=").firstOrNull()?.trim()?.trim('%') ?: "" + val code = codeOf(valueRef) + 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 b4b3c355bb..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 @@ -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 @@ -75,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) { @@ -99,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 } @@ -134,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) @@ -144,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) @@ -180,7 +179,7 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : "throw", postfix = false, prefix = true, - code = frontend.getCodeFromRawNode(instr) + code = frontend.codeOf(instr) ) } LLVMLandingPad -> { @@ -217,7 +216,7 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : return newProblemExpression( "Not handling instruction opcode $opcode yet", ProblemNode.ProblemType.TRANSLATION, - frontend.getCodeFromRawNode(instr) + frontend.codeOf(instr) ) } @@ -245,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)) + val emptyStatement = newEmptyStatement(frontend.codeOf(instr)) emptyStatement.name = name - return emptyStatement + emptyStatement } } @@ -268,17 +267,17 @@ 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) - val compoundStatement = newCompoundStatement(nodeCode) + val compoundStatement = newBlock(nodeCode) val dummyCall = newCallExpression( llvmInternalRef("llvm.catchswitch"), "llvm.catchswitch", - frontend.getCodeFromRawNode(instr), + frontend.codeOf(instr), false ) dummyCall.addArgument(parent, "parent") @@ -310,7 +309,7 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : newCallExpression( llvmInternalRef("llvm.matchesCatchpad"), "llvm.matchesCatchpad", - frontend.getCodeFromRawNode(instr), + frontend.codeOf(instr), false ) @@ -345,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) @@ -366,7 +365,7 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : newCallExpression( llvmInternalRef("llvm.cleanuppad"), "llvm.cleanuppad", - frontend.getCodeFromRawNode(instr), + frontend.codeOf(instr), false ) dummyCall.addArgument(catchswitch, "parentCatchswitch") @@ -393,7 +392,7 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : newCallExpression( llvmInternalRef("llvm.catchpad"), "llvm.catchpad", - frontend.getCodeFromRawNode(instr), + frontend.codeOf(instr), false ) dummyCall.addArgument(catchswitch, "parentCatchswitch") @@ -416,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) } @@ -478,17 +477,17 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : return newProblemExpression( "Not opcode found for binary operator", ProblemNode.ProblemType.TRANSLATION, - frontend.getCodeFromRawNode(instr) + frontend.codeOf(instr) ) } /** * 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.getCodeFromRawNode(instr)) + val array = newNewArrayExpression(frontend.codeOf(instr)) array.type = frontend.typeOf(instr) @@ -507,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.getCodeFromRawNode(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) + ) } /** @@ -574,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 -> ">" @@ -609,7 +608,7 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : "!=" } LLVMRealPredicateTrue -> { - return newLiteral(true, parseType("i1"), "true") + return newLiteral(true, primitiveType("i1"), "true") } else -> "unknown" } @@ -638,17 +637,16 @@ 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) if (copy is DeclarationStatement) { base = - newDeclaredReferenceExpression( + newReference( copy.singleDeclaration?.name?.localName, - (copy.singleDeclaration as? VariableDeclaration)?.type - ?: UnknownType.getUnknownType(this.language), - frontend.getCodeFromRawNode(instr) + (copy.singleDeclaration as? VariableDeclaration)?.type ?: unknownType(), + frontend.codeOf(instr) ) } } @@ -664,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 @@ -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 ?: unknownType() // construct our member expression expr = newMemberExpression(field?.name?.localName, base, baseType, ".", "") @@ -708,11 +706,9 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : } } - val compoundStatement = newCompoundStatement(frontend.getCodeFromRawNode(instr)) - - val assignment = newBinaryOperator("=", frontend.getCodeFromRawNode(instr)) - assignment.lhs = base - assignment.rhs = valueToSet + val compoundStatement = newBlock(frontend.codeOf(instr)) + val assignment = + newAssignExpression("=", listOf(base), listOf(valueToSet), frontend.codeOf(instr)) compoundStatement.addStatement(copy) compoundStatement.addStatement(assignment) @@ -730,7 +726,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) @@ -777,16 +773,16 @@ 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)) + 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" ) } @@ -796,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.getCodeFromRawNode(instr) - val compoundStatement = newCompoundStatement(instrStr) + val instrStr = frontend.codeOf(instr) + val compoundStatement = newBlock(instrStr) compoundStatement.name = Name("atomiccmpxchg") val ptr = frontend.getOperandValueAtIndex(instr, 0) val cmp = frontend.getOperandValueAtIndex(instr, 1) @@ -850,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 @@ -865,16 +859,16 @@ 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 - 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) val ty = value.type - val exchOp = newBinaryOperator("=", instrStr) + val exchOp = newAssignExpression("=", code = instrStr) exchOp.name = Name("atomicrmw") val ptrDeref = newUnaryOperator("*", false, true, instrStr) @@ -882,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) @@ -914,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 -> { @@ -949,7 +943,7 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : value, ty, ) - exchOp.rhs = conditional + exchOp.rhs = listOf(conditional) } LLVMAtomicRMWBinOpUMax, LLVMAtomicRMWBinOpUMin -> { @@ -960,13 +954,13 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : ">" } val condition = newBinaryOperator(operatorCode, instrStr) - val castExprLhs = newCastExpression(frontend.getCodeFromRawNode(instr)) - castExprLhs.castType = parseType("u${ty.name}") + val castExprLhs = newCastExpression(frontend.codeOf(instr)) + castExprLhs.castType = objectType("u${ty.name}") castExprLhs.expression = ptrDeref condition.lhs = castExprLhs - val castExprRhs = newCastExpression(frontend.getCodeFromRawNode(instr)) - castExprRhs.castType = parseType("u${ty.name}") + val castExprRhs = newCastExpression(frontend.codeOf(instr)) + castExprRhs.castType = objectType("u${ty.name}") castExprRhs.expression = value condition.rhs = castExprRhs @@ -979,7 +973,7 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : value, ty, ) - exchOp.rhs = conditional + exchOp.rhs = listOf(conditional) } else -> { throw TranslationException("LLVMAtomicRMWBinOp $operation not supported") @@ -988,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) @@ -1008,7 +1002,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" @@ -1019,14 +1013,14 @@ 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) { // 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. @@ -1044,7 +1038,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 @@ -1073,7 +1067,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") @@ -1082,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) { @@ -1115,13 +1109,13 @@ 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 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) @@ -1145,11 +1139,7 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : } val callee = - newDeclaredReferenceExpression( - calledFuncName, - frontend.typeOf(calledFunc), - frontend.getCodeFromRawNode(calledFunc) - ) + newReference(calledFuncName, frontend.typeOf(calledFunc), frontend.codeOf(calledFunc)) val callExpr = newCallExpression(callee, calledFuncName, instrStr, false) @@ -1162,9 +1152,9 @@ 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) + val tryBlock = newBlock(instrStr) tryBlock.addStatement(declarationOrNot(callExpr, instr)) tryBlock.addStatement(tryContinue) tryStatement.tryBlock = tryBlock @@ -1175,15 +1165,15 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : catchClause.parameter = newVariableDeclaration( "e_${gotoCatch.labelName}", - UnknownType.getUnknownType(language), + unknownType(), instrStr, true, - frontend.language + 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 @@ -1198,18 +1188,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.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 = "" 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 } @@ -1227,10 +1218,10 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : val except = newVariableDeclaration( exceptionName, - parseType(catchType), // TODO: This doesn't work for multiple types to catch - frontend.getCodeFromRawNode(instr), + objectType(catchType), // TODO: This doesn't work for multiple types to catch + frontend.codeOf(instr), false, - frontend.language + instr ) frontend.bindingsCache["%${exceptionName}"] = except catchInstr.parameter = except @@ -1244,27 +1235,31 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : * modified value is constructed. */ private fun handleInsertelement(instr: LLVMValueRef): Statement { - val instrStr = frontend.getCodeFromRawNode(instr) - val compoundStatement = newCompoundStatement(instrStr) + val instrStr = frontend.codeOf(instr) + 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.getUnknownType(this.language), + decl?.type ?: unknownType(), instrStr ) 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 } @@ -1274,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.getCodeFromRawNode(instr)) + val arrayExpr = newSubscriptExpression(frontend.codeOf(instr)) arrayExpr.arrayExpression = frontend.getOperandValueAtIndex(instr, 0) arrayExpr.subscriptExpression = frontend.getOperandValueAtIndex(instr, 1) @@ -1290,9 +1285,9 @@ 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 list = newInitializerListExpression(frontend.typeOf(instr), instrStr) val elementType = frontend.typeOf(instr).dereference() val initializers = mutableListOf() @@ -1331,9 +1326,10 @@ 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, parseType("i32"), instrStr) + arrayExpr.subscriptExpression = + newLiteral(idxInt, primitiveType("i32"), instrStr) initializers += arrayExpr } } else if (idxInt < array1Length + array2Length) { @@ -1342,10 +1338,10 @@ 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, parseType("i32"), instrStr) + newLiteral(idxInt - array1Length, primitiveType("i32"), instrStr) initializers += arrayExpr } } else { @@ -1392,9 +1388,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 basicBlock = key.subStatement as? CompoundStatement - val decl = declarationOrNot(labelMap[key]!!, instr) + val (key, value) = labelMap.entries.elementAt(0) + val basicBlock = key.subStatement as? Block + val decl = declarationOrNot(value, instr) flatAST.addAll(SubgraphWalker.flattenAST(decl)) val mutableStatements = basicBlock?.statements?.toMutableList() mutableStatements?.add(basicBlock.statements.size - 1, decl) @@ -1418,11 +1414,11 @@ 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.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) @@ -1438,18 +1434,15 @@ 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.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(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) { @@ -1475,9 +1468,9 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : newVariableDeclaration( lhs, frontend.typeOf(valueRef), - frontend.getCodeFromRawNode(valueRef), + frontend.codeOf(valueRef), false, - frontend.language + valueRef ) decl.initializer = rhs @@ -1496,15 +1489,15 @@ 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) { - log.debug("Parsing {}", frontend.getCodeFromRawNode(instr)) + log.debug("Parsing {}", frontend.codeOf(instr)) val stmt = frontend.statementHandler.handle(instr) if (stmt != null) { @@ -1579,18 +1572,18 @@ 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)) - castExprLhs.castType = parseType(op1Type) + val castExprLhs = newCastExpression(frontend.codeOf(instr)) + castExprLhs.castType = objectType(op1Type) castExprLhs.expression = op1 binaryOperator.lhs = castExprLhs val op2Type = "u${op2.type.name}" - val castExprRhs = newCastExpression(frontend.getCodeFromRawNode(instr)) - castExprRhs.castType = parseType(op2Type) + val castExprRhs = newCastExpression(frontend.codeOf(instr)) + castExprRhs.castType = objectType(op2Type) castExprRhs.expression = op2 binaryOperator.rhs = castExprRhs } else { @@ -1602,7 +1595,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( @@ -1618,7 +1611,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 @@ -1634,7 +1629,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 @@ -1678,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 fcbe56deca..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 @@ -25,12 +25,11 @@ */ package de.fraunhofer.aisec.cpg.passes -import de.fraunhofer.aisec.cpg.TranslationResult +import de.fraunhofer.aisec.cpg.TranslationContext import de.fraunhofer.aisec.cpg.frontends.llvm.LLVMIRLanguageFrontend -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.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 @@ -41,9 +40,9 @@ import java.util.* @ExecuteFirst @RequiredFrontend(LLVMIRLanguageFrontend::class) -class CompressLLVMPass : Pass() { - override fun accept(t: TranslationResult) { - val flatAST = SubgraphWalker.flattenAST(t) +class CompressLLVMPass(ctx: TranslationContext) : ComponentPass(ctx) { + 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 @@ -59,7 +58,7 @@ class CompressLLVMPass : Pass() { // 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) { @@ -84,8 +83,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 @@ -99,7 +97,7 @@ class CompressLLVMPass : Pass() { } 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 = @@ -111,7 +109,7 @@ class CompressLLVMPass : Pass() { 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 && @@ -140,22 +138,21 @@ class CompressLLVMPass : Pass() { } 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 ( @@ -168,7 +165,7 @@ class CompressLLVMPass : Pass() { ) { 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 } } @@ -199,7 +196,7 @@ class CompressLLVMPass : Pass() { catch.parameter = error } val exceptionReference = - catch.newDeclaredReferenceExpression( + catch.newReference( catch.parameter?.name, catch.parameter?.type ?: UnknownType.getUnknownType(catch.language), "" @@ -215,7 +212,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-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..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 @@ -25,20 +25,14 @@ */ 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.graph.bodyOrNull -import de.fraunhofer.aisec.cpg.graph.byNameOrNull +import de.fraunhofer.aisec.cpg.* +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.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 +45,11 @@ class LLVMIRLanguageFrontendTest { val frontend = LLVMIRLanguageFrontend( LLVMIRLanguage(), - TranslationConfiguration.builder().build(), - ScopeManager(), + TranslationContext( + TranslationConfiguration.builder().build(), + ScopeManager(), + TypeManager() + ) ) frontend.parse(topLevel.resolve("main.ll").toFile()) } @@ -76,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) @@ -187,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( @@ -195,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( @@ -203,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( @@ -219,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) @@ -243,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) @@ -268,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) @@ -310,26 +307,25 @@ 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(TypeParser.createFrom("i32", LLVMIRLanguage()), rhs.type) - assertLocalName("x", comparison.lhs as DeclaredReferenceExpression) + assertEquals(tu.primitiveType("i32"), rhs.type) + assertLocalName("x", comparison.lhs as Reference) assertLocalName("x", lhs) - assertEquals(TypeParser.createFrom("i32", LLVMIRLanguage()), lhs.type) + assertEquals(tu.primitiveType("i32"), lhs.type) // Check that the jump targets are set correctly 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) @@ -346,20 +342,20 @@ 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.objectType("ui32"), ifBranchCompRhs.castType) + assertEquals(tu.objectType("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.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) @@ -380,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 @@ -391,12 +387,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) @@ -418,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) @@ -448,12 +446,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 Reference) + assertLocalName("old", (thenExpr.rhs.first() as Reference).refersTo) } @Test @@ -555,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) @@ -567,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) @@ -594,22 +594,24 @@ 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) // 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) + assertSame(ptr, (dereferencePtr.input as? Reference)?.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) @@ -648,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 = @@ -658,12 +660,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 @@ -683,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) @@ -714,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 @@ -749,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 @@ -758,37 +762,38 @@ 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? 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 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 = (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 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 ) } @@ -807,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) @@ -833,33 +838,33 @@ 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? BinaryOperator) + 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 as? ArraySubscriptionExpression)?.subscriptExpression as? Literal<*>)?.value + ((yMod.lhs.first() as? SubscriptExpression)?.subscriptExpression as? Literal<*>)?.value ) assertSame( yModInit, - ((yMod.lhs as? ArraySubscriptionExpression)?.arrayExpression - as? DeclaredReferenceExpression) - ?.refersTo + ((yMod.lhs.first() as? SubscriptExpression)?.arrayExpression as? Reference)?.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 = @@ -869,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 ) @@ -920,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) @@ -951,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) @@ -984,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", @@ -1012,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", @@ -1029,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/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 a934d59ebd..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 @@ -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 @@ -43,6 +41,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 = @@ -72,35 +77,26 @@ 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 ( 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")!! + 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 ) { - 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. - return getSimpleTypeOf("int")!! + getSimpleTypeOf("int") ?: unknownType } else { - return getSimpleTypeOf("float")!! + getSimpleTypeOf("float") ?: unknownType } } 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..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 @@ -25,23 +25,22 @@ */ 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 +import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration +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 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) @@ -49,17 +48,22 @@ class PythonLanguageFrontend( 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 unknownType() + } + + 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-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 c2060cb52c..f4a31e8ab4 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 @@ -63,11 +64,12 @@ 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 - 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()) @@ -100,11 +102,14 @@ 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( - self.frontend, self.get_src_code(expr)) + self.frontend, + UnknownType.getUnknownType(self.frontend.getLanguage()), + self.get_src_code(expr)) lst = [] @@ -241,7 +246,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 @@ -274,27 +279,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() + tpe = TypeBuilderKt.unknownType(self.frontend) lit = ExpressionBuilderKt.newLiteral( self.frontend, resultvalue, tpe, self.get_src_code(expr)) @@ -306,20 +315,21 @@ 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( - self.frontend, expr.attr, value, UnknownType.getUnknownType(), + self.frontend, expr.attr, value, + TypeBuilderKt.unknownType(self.frontend), ".", self.get_src_code(expr)) return mem 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) @@ -329,8 +339,9 @@ def handle_expression_impl(self, expr): r = ExpressionBuilderKt.newExpression(self.frontend, "") return r elif isinstance(expr, ast.Name): - r = ExpressionBuilderKt.newDeclaredReferenceExpression( - self.frontend, expr.id, UnknownType.getUnknownType(), + r = ExpressionBuilderKt.newReference( + self.frontend, expr.id, + TypeBuilderKt.unknownType(self.frontend), self.get_src_code(expr)) # Take a little shortcut and set refersTo, in case this is a method @@ -348,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 = [] @@ -361,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/_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 be4f9be6bb..dd13767b34 100644 --- a/cpg-language-python/src/main/python/CPGPython/_statements.py +++ b/cpg-language-python/src/main/python/CPGPython/_statements.py @@ -26,10 +26,12 @@ 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 +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 @@ -63,7 +65,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) @@ -115,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( @@ -129,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 @@ -170,7 +172,7 @@ def handle_statement_impl(self, stmt): else: name = s.name src = name - tpe = UnknownType.getUnknownType() + tpe = TypeBuilderKt.unknownType(self.frontend) v = DeclarationBuilderKt.newVariableDeclaration(self.frontend, name, tpe, src, False) @@ -200,7 +202,7 @@ def handle_statement_impl(self, stmt): else: name = s.name src = name - tpe = UnknownType.getUnknownType() + tpe = TypeBuilderKt.unknownType(self.frontend) v = DeclarationBuilderKt.newVariableDeclaration( self.frontend, name, tpe, src, False) # inaccurate but ast.alias does not hold location information @@ -231,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: @@ -301,8 +303,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), @@ -334,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: @@ -406,11 +409,12 @@ 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() + 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) @@ -451,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: @@ -460,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... """ @@ -474,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): @@ -507,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] @@ -533,12 +541,12 @@ 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.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) @@ -546,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: @@ -564,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") @@ -578,7 +590,8 @@ class Foo: None, rhs, False) # TODO None -> add infos else: v = DeclarationBuilderKt.newFieldDeclaration( - self.frontend, name, UnknownType.getUnknownType(), + self.frontend, name, + TypeBuilderKt.unknownType(self.frontend), None, self.get_src_code(stmt), None, None, False) # TODO None -> add infos self.scopemanager.addDeclaration(v) @@ -597,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(), - UnknownType.getUnknownType(), + TypeBuilderKt.autoType(self.frontend), self.get_src_code(stmt), False) if rhs is not None: @@ -641,7 +654,7 @@ def bar(self): else: v = DeclarationBuilderKt.newFieldDeclaration( self.frontend, lhs.getName(), - UnknownType.getUnknownType(), + TypeBuilderKt.unknownType(self.frontend), None, self.get_src_code(stmt), None, None, False) self.scopemanager.addDeclaration(v) @@ -657,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(), - UnknownType.getUnknownType(), + 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 d04323082c..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 @@ -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.primitiveType("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.primitiveType("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.primitiveType("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.primitiveType("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.objectType("None"), n.type) assertEquals(null, (n.initializer as? Literal<*>)?.value) } @@ -130,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) @@ -143,11 +142,11 @@ class PythonFrontendTest : BaseTest() { val s = bar.parameters.first() assertNotNull(s) assertLocalName("s", s) - assertEquals(TypeParser.createFrom("str", PythonLanguage()), s.type) + assertEquals(tu.primitiveType("str"), s.type) assertLocalName("bar", bar) - val compStmt = bar.body as? CompoundStatement + val compStmt = bar.body as? Block assertNotNull(compStmt) assertNotNull(compStmt.statements) @@ -160,9 +159,9 @@ class PythonFrontendTest : BaseTest() { assertNotNull(literal) assertEquals("bar(s) here: ", literal.value) - assertEquals(TypeParser.createFrom("str", PythonLanguage()), literal.type) + 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) @@ -212,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 = @@ -220,11 +219,11 @@ class PythonFrontendTest : BaseTest() { as? VariableDeclaration assertNotNull(sel) assertLocalName("sel", sel) - assertEquals(TypeParser.createFrom("bool", PythonLanguage()), sel.type) + assertEquals(tu.primitiveType("bool"), sel.type) val initializer = sel.initializer as? Literal<*> assertNotNull(initializer) - assertEquals(TypeParser.createFrom("bool", PythonLanguage()), initializer.type) + assertEquals(tu.primitiveType("bool"), initializer.type) assertEquals("True", initializer.code) val `if` = body.statements[1] as? IfStatement @@ -262,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) @@ -279,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()) @@ -303,32 +302,32 @@ 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 assertNotNull(foo) assertLocalName("foo", foo) - assertEquals(TypeParser.createFrom("int", PythonLanguage()), foo.type) + assertEquals(tu.primitiveType("int"), foo.type) val initializer = foo.initializer as? ConditionalExpression assertNotNull(initializer) - assertEquals(TypeParser.createFrom("int", PythonLanguage()), initializer.type) + assertEquals(tu.primitiveType("int"), initializer.type) 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(TypeParser.createFrom("bool", PythonLanguage()), ifCond.type) + assertEquals(tu.primitiveType("bool"), ifCond.type) assertEquals(false, ifCond.value) - assertEquals(TypeParser.createFrom("int", PythonLanguage()), thenExpr.type) + assertEquals(tu.primitiveType("int"), thenExpr.type) assertEquals(21, (thenExpr.value as? Long)?.toInt()) - assertEquals(TypeParser.createFrom("int", PythonLanguage()), elseExpr.type) + assertEquals(tu.primitiveType("int"), elseExpr.type) assertEquals(42, (elseExpr.value as? Long)?.toInt()) } @@ -378,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) @@ -412,7 +410,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,19 +432,18 @@ class PythonFrontendTest : BaseTest() { assertNotNull(i) assertLocalName("i", i) - assertEquals(TypeParser.createFrom("int", PythonLanguage()), i.type) + assertEquals(tu.primitiveType("int"), i.type) // 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 @@ -492,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) @@ -506,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]) } @@ -548,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 @@ -556,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) @@ -591,31 +584,31 @@ 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? 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) - 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())" @@ -658,19 +651,16 @@ 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 - ) - assertEquals( - classFieldWithInit, - (assignClsFieldOutsideFunc.rhs as? DeclaredReferenceExpression)?.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 @@ -681,41 +671,35 @@ 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 - ) - assertEquals("shadowed", (barStmt3.rhs as? Literal<*>)?.value) + assertEquals(classFieldNoInitializer, (barStmt3.lhs())?.refersTo) + 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 - ) - assertEquals("shadowed", (barStmt5.rhs as? Literal<*>)?.value) + assertEquals(classFieldDeclaredInFunction, (barStmt5.lhs())?.refersTo) + assertEquals("shadowed", (barStmt5.rhs>())?.value) /* TODO: foo = Foo() @@ -776,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 @@ -804,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 } @@ -864,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) @@ -887,14 +871,15 @@ 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 } + @Test fun testIssue473() { val topLevel = Path.of("src", "test", "resources", "python") @@ -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 = @@ -943,10 +928,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) } @@ -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/build.gradle.kts b/cpg-language-typescript/build.gradle.kts index f717365daf..aaa38f32c4 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("16.20.1") + 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/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..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 @@ -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) { @@ -57,17 +56,15 @@ 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) + val type = node.typeChildNode?.let { this.frontend.typeOf(it) } ?: unknownType() val field = newFieldDeclaration( name, type, listOf(), - this.frontend.getCodeFromRawNode(node), - this.frontend.getLocationFromRawNode(node), + this.frontend.codeOf(node), + this.frontend.locationOf(node), null, false, ) @@ -88,7 +85,7 @@ class DeclarationHandler(lang: TypeScriptLanguageFrontend) : } else { "class" }, - this.frontend.getCodeFromRawNode(node) + this.frontend.codeOf(node) ) this.frontend.scopeManager.enterScope(record) @@ -112,22 +109,13 @@ 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) + val type = node.typeChildNode?.let { this.frontend.typeOf(it) } ?: unknownType() - val param = - newParamVariableDeclaration(name, type, false, this.frontend.getCodeFromRawNode(node)) - - return param + return newParameterDeclaration(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) @@ -156,29 +144,21 @@ 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 { - func.type = - this.frontend.typeHandler.handle(it) ?: UnknownType.getUnknownType(this.language) - } + node.typeChildNode?.let { func.type = this.frontend.typeOf(it) } this.frontend.scopeManager.enterScope(func) @@ -217,20 +197,15 @@ class DeclarationHandler(lang: TypeScriptLanguageFrontend) : // TODO: support ObjectBindingPattern (whatever it is). seems to be multiple assignment - val `var` = - newVariableDeclaration( - name, - UnknownType.getUnknownType(language), - this.frontend.getCodeFromRawNode(node), - false - ) - `var`.location = this.frontend.getLocationFromRawNode(node) + val declaration = + 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 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 2a40f45ead..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 @@ -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) { @@ -66,14 +65,12 @@ 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.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("") } @@ -89,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>") } @@ -103,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() @@ -116,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 == UnknownType.getUnknownType(language)) { + if (func?.type == unknownType()) { val returnValue = func.bodyOrNull()?.returnValue /*if (returnValue == null) { @@ -124,7 +121,7 @@ class ExpressionHandler(lang: TypeScriptLanguageFrontend) : func.type = TypeParser.createFrom("void", false) } else {*/ - val returnType = returnValue?.type ?: UnknownType.getUnknownType(language) + val returnType = returnValue?.type ?: unknownType() func.type = returnType // } @@ -132,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 @@ -142,13 +139,11 @@ 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.codeOf(node)) } private fun handleObjectLiteralExpression(node: TypeScriptNode): InitializerListExpression { - val ile = newInitializerListExpression(this.frontend.getCodeFromRawNode(node)) + val ile = newInitializerListExpression(unknownType(), this.frontend.codeOf(node)) ile.initializers = node.children?.mapNotNull { this.handle(it) } ?: emptyList() @@ -161,27 +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, primitiveType("string"), frontend.codeOf(node)) } private fun handleIdentifier(node: TypeScriptNode): Expression { - val name = this.frontend.getCodeFromRawNode(node)?.trim() ?: "" - - val ref = - newDeclaredReferenceExpression( - name, - UnknownType.getUnknownType(language), - this.frontend.getCodeFromRawNode(node) - ) + val name = this.frontend.codeOf(node)?.trim() ?: "" - return ref + return newReference(name, unknownType(), this.frontend.codeOf(node)) } private fun handlePropertyAccessExpression(node: TypeScriptNode): Expression { @@ -189,18 +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) } ?: "" - val memberExpression = - newMemberExpression( - name, - base, - UnknownType.getUnknownType(language), - ".", - this.frontend.getCodeFromRawNode(node) - ) - - return memberExpression + return newMemberExpression(name, base, unknownType(), ".", this.frontend.codeOf(node)) } private fun handleCallExpression(node: TypeScriptNode): Expression { @@ -209,25 +188,25 @@ 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 memberExpressionExpression = + this.handle(propertyAccess) as? MemberExpression + ?: return ProblemExpression("node is not a member expression") - call = newMemberCallExpression( - memberExpression, - code = this.frontend.getCodeFromRawNode(node) + memberExpressionExpression, + code = this.frontend.codeOf(node) ) - } else { - // TODO: fqn - how? - val fqn = this.frontend.getIdentifierName(node) - // regular function call + } else { + // TODO: fqn - how? + val fqn = this.frontend.getIdentifierName(node) + // regular function call - val ref = newDeclaredReferenceExpression(fqn) + val ref = newReference(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/JavaScriptLanguage.kt b/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/JavaScriptLanguage.kt index 9095985124..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.* @@ -43,6 +41,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). @@ -55,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/StatementHandler.kt b/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/StatementHandler.kt index 0d49979e57..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 @@ -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) @@ -81,8 +81,8 @@ class StatementHandler(lang: TypeScriptLanguageFrontend) : return returnStmt } - private fun handleBlock(node: TypeScriptNode): CompoundStatement { - val block = newCompoundStatement(this.frontend.getCodeFromRawNode(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) } } @@ -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/TypeHandler.kt b/cpg-language-typescript/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/TypeHandler.kt index 778979d02a..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,10 +26,12 @@ package de.fraunhofer.aisec.cpg.frontends.typescript import de.fraunhofer.aisec.cpg.frontends.Handler -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( @@ -50,34 +52,32 @@ class TypeHandler(frontend: TypeScriptLanguageFrontend) : "ArrayType" -> return handleArrayType(node) } - return UnknownType.getUnknownType(language) + return unknownType() } 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) } ?: 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 UnknownType.getUnknownType(language) + 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 25458bb421..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 @@ -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 @@ -36,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,24 +58,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 { @@ -113,6 +108,10 @@ class TypeScriptLanguageFrontend( return translationUnit } + override fun typeOf(type: TypeScriptNode): Type { + return typeHandler.handleNode(type) + } + /** * 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. @@ -125,19 +124,19 @@ 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 { - val groups = it.groups + val matches: Sequence? = + currentFileContent?.let { + Regex("(?:/\\*((?:[^*]|(?:\\*+[^*/]))*)\\*+/)|(?://(.*))").findAll(it) + } + 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() @@ -145,45 +144,35 @@ class TypeScriptLanguageFrontend( FrontendUtils.matchCommentToNode( comment, - commentRegion ?: translationUnit.location!!.region, + commentRegion ?: translationUnit.location?.region ?: Region(), translationUnit ) } } } - override fun getCodeFromRawNode(astNode: T): String? { - return if (astNode is TypeScriptNode) { - return astNode.code - } else { - null - } + 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 + override fun locationOf(astNode: TypeScriptNode): PhysicalLocation { + 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 { - val code = it - 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()) - } 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? { - 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 @@ -194,24 +183,26 @@ 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 } - 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 @@ -223,25 +214,23 @@ 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) ?: "") + val annotation = newAnnotation(call.name.localName, this.codeOf(node) ?: "") annotation.members = call.arguments.map { newAnnotationMember("", it, it.code ?: "") }.toMutableList() 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.codeOf(node) ?: "") } } } 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..9be1043148 --- /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.0.0", + "typescript": "5.2.2" + }, + "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.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", + "@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.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", + "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.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" + }, + "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.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" + }, + "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 331c9a48b4..e62bf96a60 100644 --- a/cpg-language-typescript/src/main/nodejs/package.json +++ b/cpg-language-typescript/src/main/nodejs/package.json @@ -1,17 +1,19 @@ { "scripts": { "build": "tsc --build tsconfig.json", - "prebundle": "tsc --build tsconfig.json", - "bundle": "webpack --mode=production", + "bundle": "rollup -c", "start": "node src/parser.js" }, "dependencies": { - "@types/node": "18.14.0", - "typescript": "4.8.2" + "@types/node": "^18.0.0", + "typescript": "5.2.2" }, "license": "Apache-2.0", "devDependencies": { - "webpack": "5.75.0", - "webpack-cli": "5.0.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 1123c36d79..0000000000 --- a/cpg-language-typescript/src/main/nodejs/yarn.lock +++ /dev/null @@ -1,806 +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.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.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.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@*", "@types/estree@^0.0.51": - version "0.0.51" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.51.tgz#cfd70924a25a3fd32b218e5e420e6897e1ac4f40" - integrity sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ== - -"@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.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== - -"@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" - "@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-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== - 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/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== - 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== - 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" - "@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/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/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== - -"@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.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@^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@^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" - 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.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== - 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@^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== - -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.0.2: - version "27.0.6" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.0.6.tgz#a5fdb1e14ad34eb228cfe162d9f729cdbfa28aed" - integrity sha512-qupxcj/dRuA3xHPMUd40gr2EaAurFbkwzOh7wfPaeE9id7hyjURRQoqNfHifHK3XjJU6YJJUQKILGUnwGPEOCA== - 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-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" - 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.0.0, schema-utils@^3.1.0: - 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" - -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== - 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, source-map@^0.6.1: - 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.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== - 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" - -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== - dependencies: - "@jridgewell/source-map" "^0.3.2" - acorn "^8.5.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== - -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.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== - 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" - colorette "^2.0.14" - commander "^9.4.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.75.0: - version "5.75.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.75.0.tgz#1e440468647b2505860e94c9ff3e44d5b582c152" - integrity sha512-piaIaoVJlqMsPtX/+3KTTO6jfvrSYgauFVdt8cr9LTHKmcq/AMd4mhzsiP7ZF/PGRNPGA8336jldh9l2Kt2ogQ== - dependencies: - "@types/eslint-scope" "^3.7.3" - "@types/estree" "^0.0.51" - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/wasm-edit" "1.11.1" - "@webassemblyjs/wasm-parser" "1.11.1" - 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" - es-module-lexer "^0.9.0" - 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.1.0" - tapable "^2.1.1" - terser-webpack-plugin "^5.1.3" - 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== - -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== 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..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 @@ -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.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.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(TypeParser.createFrom("Number", TypeScriptLanguage()), someFunction.type) + assertEquals(tu.primitiveType("number"), someFunction.type) val someOtherFunction = functions.last() assertLocalName("someOtherFunction", someOtherFunction) - assertEquals(TypeParser.createFrom("Number", TypeScriptLanguage()), 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(TypeParser.createFrom("String", TypeScriptLanguage()), parameter.type) + assertEquals(tu.primitiveType("string"), parameter.type) } @Test @@ -137,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() @@ -208,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) @@ -222,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 @@ -278,7 +274,7 @@ class TypeScriptLanguageFrontendTest { val lastName = user.fields.lastOrNull() assertNotNull(lastName) assertLocalName("lastName", lastName) - assertEquals(TypeParser.createFrom("string", TypeScriptLanguage()), 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(TypeParser.createFrom("User[]", TypeScriptLanguage()), users.type) + assertIs(users.type) + assertLocalName("User[]", users.type) val usersComponent = tu.getDeclarationsByName("Users", RecordDeclaration::class.java).iterator().next() @@ -304,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/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 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 fb4c192e7a..7adc5ed7c2 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,13 @@ 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 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.system.exitProcess import org.neo4j.driver.exceptions.AuthenticationException import org.neo4j.ogm.config.Configuration @@ -101,6 +104,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 +174,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]"] @@ -204,6 +225,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, + FilenameMapper::class + ) + 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 } + /** * Pushes the whole translationResult to the neo4j db. * @@ -314,16 +353,18 @@ 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) .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") .optionalLanguage("de.fraunhofer.aisec.cpg.frontends.typescript.TypeScriptLanguage") .loadIncludes(loadIncludes) + .addIncludesToGraph(loadIncludes) .debugParser(DEBUG_PARSER) .useUnityBuild(useUnityBuild) @@ -338,12 +379,27 @@ 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 as KClass> + ) + } else { + if (pass !in passClassMap) { + throw ConfigurationException("Asked to produce unknown pass") + } + passClassMap[pass]?.let { translationConfiguration.registerPass(it) } + } + } } + translationConfiguration.registerPass(PrepareSerialization::class) - 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) @@ -393,6 +449,13 @@ class Application : Callable { printSchema(mutuallyExclusiveParameters.files) return EXIT_SUCCESS } + 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() diff --git a/cpg-neo4j/src/main/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/Schema.kt b/cpg-neo4j/src/main/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/Schema.kt index f121ebe80a..6047667e48 100644 --- a/cpg-neo4j/src/main/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/Schema.kt +++ b/cpg-neo4j/src/main/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/Schema.kt @@ -332,6 +332,7 @@ class Schema { " classDef special fill:#afa,stroke:#5a5,stroke-dasharray:5 5;" ) } + private fun closeMermaid(out: PrintWriter) { out.println("```") } 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..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,44 +25,37 @@ */ 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.* +import de.fraunhofer.aisec.cpg.graph.builder.* import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration -import java.io.File +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 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() - val application = Application() + val cmd = CommandLine(Application::class.java) + cmd.parseArgs(path.toString()) + val application = cmd.getCommand() + + 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() @@ -71,7 +64,7 @@ class ApplicationTest { val functions = session.loadAll(FunctionDeclaration::class.java) assertNotNull(functions) - assertEquals(38, functions.size) + assertEquals(31, functions.size) transaction.commit() } 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/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/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/cpg-core/specifications/dfg.md b/docs/docs/CPG/specs/dfg.md old mode 100644 new mode 100755 similarity index 80% rename from cpg-core/specifications/dfg.md rename to docs/docs/CPG/specs/dfg.md index 0f7036b3c3..af1138bf60 --- a/cpg-core/specifications/dfg.md +++ b/docs/docs/CPG/specs/dfg.md @@ -58,66 +58,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. +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: ```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 Block + 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. @@ -130,7 +144,7 @@ Scheme: lhs -- DFG --> node; ``` -## ArrayCreationExpression +## NewArrayExpression Interesting fields: @@ -141,7 +155,7 @@ The `initializer` flows to the array creation expression. Scheme: ```mermaid flowchart LR - node([ArrayCreationExpression]) -.- initializer(initializer) + node([NewArrayExpression]) -.- initializer(initializer) initializer -- DFG --> node ``` @@ -153,14 +167,13 @@ Interesting fields: The `initializer` flows to the whole expression. Scheme: - ```mermaid +```mermaid flowchart LR node([NewExpression]) -.- initializer(initializer) initializer -- DFG --> node - ``` - +``` -## ArraySubscriptionExpression +## SubscriptExpression Interesting fields: @@ -172,7 +185,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; ``` @@ -182,21 +195,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: @@ -210,19 +223,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; ``` @@ -231,20 +244,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; @@ -289,9 +302,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 @@ -405,7 +418,6 @@ 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. @@ -430,7 +442,7 @@ Scheme: node -.- iterable[iterable] variable -.- declarations["declarations[i]"] iterable -- for all i: DFG --> declarations - ``` +``` ### Case 2. The `variable` is another type of `Statement`. @@ -445,7 +457,7 @@ Scheme: localVars -. "last" .-> variable iterable -- DFG --> variable variable -- DFG --> node - ``` +``` ### DoStatement @@ -458,8 +470,7 @@ Scheme: flowchart LR node([DoStatement]) -.- condition(condition) condition -- DFG --> node - ``` - +``` ### WhileStatement @@ -474,8 +485,7 @@ Scheme: node -.- conditionDeclaration(conditionDeclaration) condition -- DFG --> node conditionDeclaration -- DFG --> node - ``` - +``` ### ForStatement @@ -490,8 +500,8 @@ Scheme: node -.- conditionDeclaration(conditionDeclaration) condition -- DFG --> node conditionDeclaration -- DFG --> node - ``` - +``` + ### IfStatement @@ -506,8 +516,8 @@ Scheme: node -.- conditionDeclaration(conditionDeclaration) condition -- DFG --> node conditionDeclaration -- DFG --> node - ``` - +``` + ### SwitchStatement Interesting fields: @@ -521,7 +531,8 @@ Scheme: node -.- selectorDeclaration(selectorDeclaration) selector -- DFG --> node selectorDeclaration -- DFG --> node - ``` +``` + ## FunctionDeclaration Interesting fields: @@ -547,7 +558,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 @@ -564,7 +575,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 @@ -575,6 +586,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 `References` 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/cpg-core/specifications/eog.md b/docs/docs/CPG/specs/eog.md similarity index 90% rename from cpg-core/specifications/eog.md rename to docs/docs/CPG/specs/eog.md index b8eaa53dcd..8ef5512d20 100644 --- a/cpg-core/specifications/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 @@ -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. @@ -146,7 +168,7 @@ flowchart LR child --EOG--> parent ``` -## ArraySubscriptionExpression +## SubscriptExpression Array access in the form of `arrayExpression[subscriptExpression]`. Interesting fields: @@ -160,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. @@ -179,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 @@ -273,8 +295,32 @@ 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 +## Block Represents an explicit block of statements. @@ -288,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 @@ -361,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 @@ -538,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 @@ -546,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 @@ -558,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 @@ -567,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 new file mode 100644 index 0000000000..7e594aad52 --- /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) +[BlockStatement](#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) +[NewArrayExpression](#earraycreationexpression) +[SubscriptionExpression](#earraysubscriptionexpression) +[TypeExpression](#etypeexpression) +[BinaryOperator](#ebinaryoperator) +[ConditionalExpression](#econditionalexpression) +[Reference](#edeclaredreferenceexpression) +[InitializerListExpression](#einitializerlistexpression) +[DeleteExpression](#edeleteexpression) +[BlockStatementExpression](#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 +[ConstructorCallExpression](#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 +``` +## ConstructorCallExpression +**Labels**:[Node](#enode) +[Statement](#estatement) +[Expression](#eexpression) +[CallExpression](#ecallexpression) +[ConstructorCallExpression](#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 +``` +## NewArrayExpression +**Labels**:[Node](#enode) +[Statement](#estatement) +[Expression](#eexpression) +[NewArrayExpression](#earraycreationexpression) + +### Relationships +[INITIALIZER](#NewArrayExpressionINITIALIZER) + +[DIMENSIONS](#NewArrayExpressionDIMENSIONS) + +[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; +NewArrayExpression--"INITIALIZER¹"-->NewArrayExpressionINITIALIZER[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; +NewArrayExpression--"DIMENSIONS*"-->NewArrayExpressionDIMENSIONS[Expression]:::outer +``` +## SubscriptionExpression +**Labels**:[Node](#enode) +[Statement](#estatement) +[Expression](#eexpression) +[SubscriptionExpression](#earraysubscriptionexpression) + +### Relationships +[ARRAY_EXPRESSION](#SubscriptionExpressionARRAY_EXPRESSION) + +[SUBSCRIPT_EXPRESSION](#SubscriptionExpressionSUBSCRIPT_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; +SubscriptionExpression--"ARRAY_EXPRESSION¹"-->SubscriptionExpressionARRAY_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; +SubscriptionExpression--"SUBSCRIPT_EXPRESSION¹"-->SubscriptionExpressionSUBSCRIPT_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 +``` +## Reference +**Labels**:[Node](#enode) +[Statement](#estatement) +[Expression](#eexpression) +[Reference](#edeclaredreferenceexpression) + +### Children +[MemberExpression](#ememberexpression) + +### Relationships +[REFERS_TO](#ReferenceREFERS_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; +Reference--"REFERS_TO¹"-->ReferenceREFERS_TO[Declaration]:::outer +``` +## MemberExpression +**Labels**:[Node](#enode) +[Statement](#estatement) +[Expression](#eexpression) +[Reference](#edeclaredreferenceexpression) +[MemberExpression](#ememberexpression) + +### Relationships +[BASE](#MemberExpressionBASE) + +[REFERS_TO](#ReferenceREFERS_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 +``` +## BlockStatementExpression +**Labels**:[Node](#enode) +[Statement](#estatement) +[Expression](#eexpression) +[BlockStatementExpression](#ecompoundstatementexpression) + +### Relationships +[STATEMENT](#BlockStatementExpressionSTATEMENT) + +[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; +BlockStatementExpression--"STATEMENT¹"-->BlockStatementExpressionSTATEMENT[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[BlockStatement]:::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 +``` +## BlockStatement +**Labels**:[Node](#enode) +[Statement](#estatement) +[BlockStatement](#ecompoundstatement) + +### Relationships +[STATEMENTS](#BlockStatementSTATEMENTS) + +[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; +BlockStatement--"STATEMENTS*"-->BlockStatementSTATEMENTS[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[BlockStatement]:::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[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[BlockStatement]:::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) +[ParameterDeclaration](#eparamvariabledeclaration) +[TypeParameterDeclaration](#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[Reference]:::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[ParameterDeclaration]:::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) + +## ParameterDeclaration +**Labels**:[Node](#enode) +[Declaration](#edeclaration) +[ValueDeclaration](#evaluedeclaration) +[ParameterDeclaration](#eparamvariabledeclaration) + +### Relationships +[DEFAULT](#ParameterDeclarationDEFAULT) + +[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; +ParameterDeclaration--"DEFAULT¹"-->ParameterDeclarationDEFAULT[Expression]:::outer +``` +## TypeParameterDeclaration +**Labels**:[Node](#enode) +[Declaration](#edeclaration) +[ValueDeclaration](#evaluedeclaration) +[TypeParameterDeclaration](#etypeparamdeclaration) + +### Relationships +[DEFAULT](#TypeParameterDeclarationDEFAULT) + +[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; +TypeParameterDeclaration--"DEFAULT¹"-->TypeParameterDeclarationDEFAULT[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..3b91b95eb8 --- /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) [BlockStatement](#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) [NewArrayExpression](#earraycreationexpression) [SubscriptionExpression](#earraysubscriptionexpression) [TypeExpression](#etypeexpression) [BinaryOperator](#ebinaryoperator) [ConditionalExpression](#econditionalexpression) [Reference](#edeclaredreferenceexpression) [InitializerListExpression](#einitializerlistexpression) [DeleteExpression](#edeleteexpression) [BlockStatementExpression](#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 +[ConstructorCallExpression](#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 +``` +# ConstructorCallExpression +**Labels**:[Node](#enode) [Statement](#estatement) [Expression](#eexpression) [CallExpression](#ecallexpression) [ConstructorCallExpression](#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 +``` +# NewArrayExpression +**Labels**:[Node](#enode) [Statement](#estatement) [Expression](#eexpression) [NewArrayExpression](#earraycreationexpression) +## Relationships +[INITIALIZER](#NewArrayExpressionINITIALIZER) +[DIMENSIONS](#NewArrayExpressionDIMENSIONS) +[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; +NewArrayExpression--"INITIALIZER¹"-->NewArrayExpressionINITIALIZER[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; +NewArrayExpression--"DIMENSIONS*"-->NewArrayExpressionDIMENSIONS[Expression]:::outer +``` +# SubscriptionExpression +**Labels**:[Node](#enode) [Statement](#estatement) [Expression](#eexpression) [SubscriptionExpression](#earraysubscriptionexpression) +## Relationships +[ARRAY_EXPRESSION](#SubscriptionExpressionARRAY_EXPRESSION) +[SUBSCRIPT_EXPRESSION](#SubscriptionExpressionSUBSCRIPT_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; +SubscriptionExpression--"ARRAY_EXPRESSION¹"-->SubscriptionExpressionARRAY_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; +SubscriptionExpression--"SUBSCRIPT_EXPRESSION¹"-->SubscriptionExpressionSUBSCRIPT_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 +``` +# Reference +**Labels**:[Node](#enode) [Statement](#estatement) [Expression](#eexpression) [Reference](#edeclaredreferenceexpression) +## Children +[MemberExpression](#ememberexpression) +## Relationships +[REFERS_TO](#ReferenceREFERS_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; +Reference--"REFERS_TO¹"-->ReferenceREFERS_TO[Declaration]:::outer +``` +# MemberExpression +**Labels**:[Node](#enode) [Statement](#estatement) [Expression](#eexpression) [Reference](#edeclaredreferenceexpression) [MemberExpression](#ememberexpression) +## Relationships +[BASE](#MemberExpressionBASE) +[REFERS_TO](#ReferenceREFERS_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 +``` +# BlockStatementExpression +**Labels**:[Node](#enode) [Statement](#estatement) [Expression](#eexpression) [BlockStatementExpression](#ecompoundstatementexpression) +## Relationships +[STATEMENT](#BlockStatementExpressionSTATEMENT) +[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; +BlockStatementExpression--"STATEMENT¹"-->BlockStatementExpressionSTATEMENT[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[BlockStatement]:::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 +``` +# BlockStatement +**Labels**:[Node](#enode) [Statement](#estatement) [BlockStatement](#ecompoundstatement) +## Relationships +[STATEMENTS](#BlockStatementSTATEMENTS) +[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; +BlockStatement--"STATEMENTS*"-->BlockStatementSTATEMENTS[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[BlockStatement]:::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[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[BlockStatement]:::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) [ParameterDeclaration](#eparamvariabledeclaration) [TypeParameterDeclaration](#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[Reference]:::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[ParameterDeclaration]:::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) +# ParameterDeclaration +**Labels**:[Node](#enode) [Declaration](#edeclaration) [ValueDeclaration](#evaluedeclaration) [ParameterDeclaration](#eparamvariabledeclaration) +## Relationships +[DEFAULT](#ParameterDeclarationDEFAULT) +[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; +ParameterDeclaration--"DEFAULT¹"-->ParameterDeclarationDEFAULT[Expression]:::outer +``` +# TypeParameterDeclaration +**Labels**:[Node](#enode) [Declaration](#edeclaration) [ValueDeclaration](#evaluedeclaration) [TypeParameterDeclaration](#etypeparamdeclaration) +## Relationships +[DEFAULT](#TypeParameterDeclarationDEFAULT) +[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; +TypeParameterDeclaration--"DEFAULT¹"-->TypeParameterDeclarationDEFAULT[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..d2b1b9d87e --- /dev/null +++ b/docs/docs/Contributing/index.md @@ -0,0 +1,160 @@ +--- +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. + +: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. +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..3b663a172e --- /dev/null +++ b/docs/docs/GettingStarted/cli.md @@ -0,0 +1,59 @@ +--- +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) +--- + +# 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 new file mode 100644 index 0000000000..8185da075f --- /dev/null +++ b/docs/docs/GettingStarted/index.md @@ -0,0 +1,21 @@ +--- +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) + +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/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..da8bfe840d --- /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() + .guessCastExpressions(true) + .inferRecords(true) + .inferDfgForUnresolvedCalls(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..361e78b760 --- /dev/null +++ b/docs/docs/GettingStarted/query.md @@ -0,0 +1,174 @@ +--- +title: "The Query API" +linkTitle: "The 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(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) +---------------------------------------- +------------------------ +-------- +``` + +## 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 +- **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. +- **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/GettingStarted/shortcuts.md b/docs/docs/GettingStarted/shortcuts.md new file mode 100644 index 0000000000..3bfc3448d3 --- /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 + `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()`. +* `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/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 0000000000..b826d5af84 Binary files /dev/null and b/docs/docs/assets/fonts/Inter/font-files/Inter-italic.var.woff2 differ 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 0000000000..6a256a068f Binary files /dev/null and b/docs/docs/assets/fonts/Inter/font-files/Inter-roman.var.woff2 differ diff --git a/docs/docs/assets/fonts/Inter/font-files/Inter.var.woff2 b/docs/docs/assets/fonts/Inter/font-files/Inter.var.woff2 new file mode 100644 index 0000000000..365eedc50c Binary files /dev/null and b/docs/docs/assets/fonts/Inter/font-files/Inter.var.woff2 differ diff --git a/docs/docs/assets/fonts/Inter/font-files/inter.css b/docs/docs/assets/fonts/Inter/font-files/inter.css new file mode 100644 index 0000000000..f450010124 --- /dev/null +++ b/docs/docs/assets/fonts/Inter/font-files/inter.css @@ -0,0 +1,200 @@ +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 100; + font-display: swap; + src: url("Inter-Thin.woff2?v=3.19") format("woff2"), + url("Inter-Thin.woff?v=3.19") format("woff"); +} +@font-face { + font-family: 'Inter'; + font-style: italic; + font-weight: 100; + font-display: swap; + src: url("Inter-ThinItalic.woff2?v=3.19") format("woff2"), + url("Inter-ThinItalic.woff?v=3.19") format("woff"); +} + +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 200; + font-display: swap; + src: url("Inter-ExtraLight.woff2?v=3.19") format("woff2"), + url("Inter-ExtraLight.woff?v=3.19") format("woff"); +} +@font-face { + font-family: 'Inter'; + font-style: italic; + font-weight: 200; + font-display: swap; + src: url("Inter-ExtraLightItalic.woff2?v=3.19") format("woff2"), + url("Inter-ExtraLightItalic.woff?v=3.19") format("woff"); +} + +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 300; + font-display: swap; + src: url("Inter-Light.woff2?v=3.19") format("woff2"), + url("Inter-Light.woff?v=3.19") format("woff"); +} +@font-face { + font-family: 'Inter'; + font-style: italic; + font-weight: 300; + font-display: swap; + src: url("Inter-LightItalic.woff2?v=3.19") format("woff2"), + url("Inter-LightItalic.woff?v=3.19") format("woff"); +} + +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url("Inter-Regular.woff2?v=3.19") format("woff2"), + url("Inter-Regular.woff?v=3.19") format("woff"); +} +@font-face { + font-family: 'Inter'; + font-style: italic; + font-weight: 400; + font-display: swap; + src: url("Inter-Italic.woff2?v=3.19") format("woff2"), + url("Inter-Italic.woff?v=3.19") format("woff"); +} + +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 500; + font-display: swap; + src: url("Inter-Medium.woff2?v=3.19") format("woff2"), + url("Inter-Medium.woff?v=3.19") format("woff"); +} +@font-face { + font-family: 'Inter'; + font-style: italic; + font-weight: 500; + font-display: swap; + src: url("Inter-MediumItalic.woff2?v=3.19") format("woff2"), + url("Inter-MediumItalic.woff?v=3.19") format("woff"); +} + +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 600; + font-display: swap; + src: url("Inter-SemiBold.woff2?v=3.19") format("woff2"), + url("Inter-SemiBold.woff?v=3.19") format("woff"); +} +@font-face { + font-family: 'Inter'; + font-style: italic; + font-weight: 600; + font-display: swap; + src: url("Inter-SemiBoldItalic.woff2?v=3.19") format("woff2"), + url("Inter-SemiBoldItalic.woff?v=3.19") format("woff"); +} + +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 700; + font-display: swap; + src: url("Inter-Bold.woff2?v=3.19") format("woff2"), + url("Inter-Bold.woff?v=3.19") format("woff"); +} +@font-face { + font-family: 'Inter'; + font-style: italic; + font-weight: 700; + font-display: swap; + src: url("Inter-BoldItalic.woff2?v=3.19") format("woff2"), + url("Inter-BoldItalic.woff?v=3.19") format("woff"); +} + +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 800; + font-display: swap; + src: url("Inter-ExtraBold.woff2?v=3.19") format("woff2"), + url("Inter-ExtraBold.woff?v=3.19") format("woff"); +} +@font-face { + font-family: 'Inter'; + font-style: italic; + font-weight: 800; + font-display: swap; + src: url("Inter-ExtraBoldItalic.woff2?v=3.19") format("woff2"), + url("Inter-ExtraBoldItalic.woff?v=3.19") format("woff"); +} + +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 900; + font-display: swap; + src: url("Inter-Black.woff2?v=3.19") format("woff2"), + url("Inter-Black.woff?v=3.19") format("woff"); +} +@font-face { + font-family: 'Inter'; + font-style: italic; + font-weight: 900; + font-display: swap; + src: url("Inter-BlackItalic.woff2?v=3.19") format("woff2"), + url("Inter-BlackItalic.woff?v=3.19") format("woff"); +} + +/* ------------------------------------------------------- +Variable font. +Usage: + + html { font-family: 'Inter', sans-serif; } + @supports (font-variation-settings: normal) { + html { font-family: 'Inter var', sans-serif; } + } +*/ +@font-face { + font-family: 'Inter var'; + font-weight: 100 900; + font-display: swap; + font-style: normal; + font-named-instance: 'Regular'; + src: url("Inter-roman.var.woff2?v=3.19") format("woff2"); +} +@font-face { + font-family: 'Inter var'; + font-weight: 100 900; + font-display: swap; + font-style: italic; + font-named-instance: 'Italic'; + src: url("Inter-italic.var.woff2?v=3.19") format("woff2"); +} + + +/* -------------------------------------------------------------------------- +[EXPERIMENTAL] Multi-axis, single variable font. + +Slant axis is not yet widely supported (as of February 2019) and thus this +multi-axis single variable font is opt-in rather than the default. + +When using this, you will probably need to set font-variation-settings +explicitly, e.g. + + * { font-variation-settings: "slnt" 0deg } + .italic { font-variation-settings: "slnt" 10deg } + +*/ +@font-face { + font-family: 'Inter var experimental'; + font-weight: 100 900; + font-display: swap; + font-style: oblique 0deg 10deg; + src: url("Inter.var.woff2?v=3.19") format("woff2"); +} diff --git a/docs/docs/assets/fonts/Inter/inter.css b/docs/docs/assets/fonts/Inter/inter.css new file mode 100644 index 0000000000..90c80bb04c --- /dev/null +++ b/docs/docs/assets/fonts/Inter/inter.css @@ -0,0 +1,206 @@ + +/* +Font families defined by this CSS: + +- "Inter" static "traditional" fonts for older web browsers +- "Inter var" single-axis variable fonts for all modern browsers +- "Inter var experimental" multi-axis variable fonts for some modern web browsers + +Use like this in your CSS: + + :root { font-family: 'Inter', sans-serif; } + @supports (font-variation-settings: normal) { + :root { font-family: 'Inter var', sans-serif; } + } + +------------------------- static ------------------------- */ +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 100; + font-display: swap; + src: url("font-files/Inter-Thin.woff2?v=3.19") format("woff2"); +} +@font-face { + font-family: 'Inter'; + font-style: italic; + font-weight: 100; + font-display: swap; + src: url("font-files/Inter-ThinItalic.woff2?v=3.19") format("woff2"); +} + +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 200; + font-display: swap; + src: url("font-files/Inter-ExtraLight.woff2?v=3.19") format("woff2"); +} +@font-face { + font-family: 'Inter'; + font-style: italic; + font-weight: 200; + font-display: swap; + src: url("font-files/Inter-ExtraLightItalic.woff2?v=3.19") format("woff2"); +} + +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 300; + font-display: swap; + src: url("font-files/Inter-Light.woff2?v=3.19") format("woff2"); +} +@font-face { + font-family: 'Inter'; + font-style: italic; + font-weight: 300; + font-display: swap; + src: url("font-files/Inter-LightItalic.woff2?v=3.19") format("woff2"); +} + +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url("font-files/Inter-Regular.woff2?v=3.19") format("woff2"); +} +@font-face { + font-family: 'Inter'; + font-style: italic; + font-weight: 400; + font-display: swap; + src: url("font-files/Inter-Italic.woff2?v=3.19") format("woff2"); +} + +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 500; + font-display: swap; + src: url("font-files/Inter-Medium.woff2?v=3.19") format("woff2"); +} +@font-face { + font-family: 'Inter'; + font-style: italic; + font-weight: 500; + font-display: swap; + src: url("font-files/Inter-MediumItalic.woff2?v=3.19") format("woff2"); +} + +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 600; + font-display: swap; + src: url("font-files/Inter-SemiBold.woff2?v=3.19") format("woff2"); +} +@font-face { + font-family: 'Inter'; + font-style: italic; + font-weight: 600; + font-display: swap; + src: url("font-files/Inter-SemiBoldItalic.woff2?v=3.19") format("woff2"); +} + +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 700; + font-display: swap; + src: url("font-files/Inter-Bold.woff2?v=3.19") format("woff2"); +} +@font-face { + font-family: 'Inter'; + font-style: italic; + font-weight: 700; + font-display: swap; + src: url("font-files/Inter-BoldItalic.woff2?v=3.19") format("woff2"); +} + +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 800; + font-display: swap; + src: url("font-files/Inter-ExtraBold.woff2?v=3.19") format("woff2"); +} +@font-face { + font-family: 'Inter'; + font-style: italic; + font-weight: 800; + font-display: swap; + src: url("font-files/Inter-ExtraBoldItalic.woff2?v=3.19") format("woff2"); +} + +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 900; + font-display: swap; + src: url("font-files/Inter-Black.woff2?v=3.19") format("woff2"); +} +@font-face { + font-family: 'Inter'; + font-style: italic; + font-weight: 900; + font-display: swap; + src: url("font-files/Inter-BlackItalic.woff2?v=3.19") format("woff2"); +} + + +/* ----------------------- variable ----------------------- */ + +@font-face { + font-family: 'Inter var'; + font-style: normal; + font-weight: 100 900; + font-display: swap; + src: url('font-files/Inter-roman.var.woff2?v=3.19') format('woff2'); + font-named-instance: 'Regular'; +} + +@font-face { + font-family: 'Inter var'; + font-style: italic; + font-weight: 100 900; + font-display: swap; + src: url('font-files/Inter-italic.var.woff2?v=3.19') format('woff2'); + font-named-instance: 'Italic'; +} + +/* ----------- experimental multi-axis variable ----------- + +Slant axis is not yet widely supported (as of February 2019) and thus this +multi-axis single-file variable font is opt-in rather than the default. +When using this, you will likely need to set font-variation-settings explicitly, e.g: + * { font-variation-settings: "slnt" 0deg } + .italic { font-variation-settings: "slnt" 10deg } +*/ + +@font-face { + font-family: 'Inter var experimental'; + font-style: oblique 0deg 10deg; + font-weight: 100 900; + font-display: swap; + src: url('font-files/Inter.var.woff2?v=3.19') format('woff2'); +} + +/* Legacy name (became legacy on Feb 2, 2019) */ +@font-face { + font-family: 'Inter var alt'; + font-weight: 100 900; + font-style: normal; + font-named-instance: 'Regular'; + font-display: swap; + src: url("font-files/Inter-roman.var.woff2?v=3.19") format("woff2"); +} +@font-face { + font-family: 'Inter var alt'; + font-weight: 100 900; + font-style: italic; + font-named-instance: 'Italic'; + font-display: swap; + src: url("font-files/Inter-italic.var.woff2?v=3.19") format("woff2"); +} 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 0000000000..d372112040 Binary files /dev/null and b/docs/docs/assets/img/Institut-AISEC-Gebaeude-Nacht.jpg differ 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 @@ +7V1bd5u4Fv41ebQXkrg+Jk7aZiY9zUzO6XTmZZYMss0UWz4CJ3Z//YirAYmY1AYRJ2u1K0YIEPvbH/tqfIEmy+1HhteLz9QjwQXUvO0Fur6AECBD43/ikV02YjsoHZkz38vG9gMP/g+SDWYHzje+R8LKxIjSIPLX1UGXrlbEjSpjmDH6VJ02o0H1qms8J8LAg4sDcfQP34sW6aid31c8/on480V+ZaBle5Y4n5wNhAvs0afSELq5QBNGaZR+Wm4nJIill8slPe5Dw95iYYysojYH/DX5cjv7Nr/c/fgjRL98u8Tm7G6UneURB5vshi+gGfDzXc0oPy1fdbTLRGH+f0PzHaMwAeqSTwD2mqN9td/PP82zv8mJpvnAHV7NN7G4ofaB8ZOQlZfP4aue1o/jY+ka8mFYWQ7k8lzHH8OIrPn+NWH+kkSEZUP3++2rp4UfkYc1duP5T1xT+dgiWgZ8C8R35G9JrnrpdhBMaEBZciHkGcT2dD4eRox+J6U9Npwi0ywWV8YiFyxhEdmWhjJsPhLKF8d2fEq218nUJCdKrkdPe60rSLEoaZydjeFM0efFmfe6wD9k6vAC1YA9qsY9DkPO8lehDZjYM1emDaZrk+nsNNqAtMPqYJp9qgMS1OEX/IgFHBjdrDziZbI7JGqOaC5p+6eIR/m5/SgWEr/xLngIJYIHjkTwutGR4PVGHnr+Y86H+NT8/1Xyr2BMacL5A2WoBsoQgLrfRQu6OnvRI+UcMQXRf6TnL3blGm8LYv/vbs29Z+avo7MXv65c6x1B/Hd3Xz+Pbn8/f9krV/3cFRf8Ie1my93MFY7PELOB//nkE4aZu9h1i8thx/T0uNT9VPXuEhBD2gldrhkJeXyhxQQ5fxQGQI/m6LHklGb8+J2ENOB324fnymiEI587Zeh65GhDwc82qpYdmmPLECCEjjU2+gRRjPmaQSw95N4B1XVtkICKseTtck1ZVMHs/NFxhkk3MYCU0O0m3p3KFGpfmBcjpiXZ9zfLNsMYJtvEqFSC5zWOYq/xQ0Cf3jyQpjZMYlptgJxwCTMajDIkH8gq9CP/MbaNRb6bSdLaFYjf1eM59TCHyXMxDSIB8itmPp4GsTr8L0yrYG/eTbLAMPkuJlZkfMfJmeogHub4+cNqDZKnUEzZCIXIk5U4L103zTjEa04sQDbhCrvf08r3/ohmxanWPGXl0Zo29VYHndkucaWaNbWNOAFxmrxf7ZEvq4MCS5LZ6K4sLuaXblejz2RJWbf5vRYC7yDvWpM/j2xF+Rt9ZpagmFninhd/BpM3IH3HUS19MSX0H0L1f85f9shQrvli9ua3DUnWfHl/e/4I6EA5As0Zmg48iBUOdtWuqOF6AwR4BrFkCuKYFsIn6pEDAA3OHRCTPHlSrpyp65CbLUR/em4KSPBYTubj98xPMVHzNfn4xsBA9hDAENMisaMW4eS5eM+4HOZvBhB5BNwZIDb59ht5Wny5+dv61Xc+sQeIvLxbRyroxIKzaEHnlJudO0rXmXj/IVG0y+SLNxGtCp9s/ehbdnj8+c/S+PW2tON6l22kMOSt9LCQN/GEPnwBgJBumEueucMsNIgwm5PomXmmHD1GApxkbMsXlcGQHXpP/USX8yourBmZdL3ZrBp4xWV/Hk9HwDMW4kO2uaIrkiSITgQxaA0xh43tEq0YG/nmn+V9+8OSrUGphnWkahzF0Lxi1BOkZdZmWA2bt4ZKcMS8gwhWEPjrkBw2Wjhcp19WStxq0QbVw68ZMeXhl2c5U+05fASjJUGo0Y5Bq5qfHwEo+tsyG2acwN2WgiDrSOkNhBlsAMGcmobZFQgI1HwJEQJd5kZ0BYHs+wh9QXA4DdEND/QqD3TFEOQFaQWm/5Cd2Bv/iunfewIqjL+h1C+EtWCg6B3OT5GuqzM/UZa06omwLaKvTggLav3iI0lDLJIwFnXGWCDKvEvPrmDpK3XXzZaM1VV6hLL+rp6I1aKu35ElrBFLYgp7dQjzmkDHxOpU2a2Wyo5UKjsYarphENCkPWCqsJH1J/b1IDr8bbBuoiJYC02V++SyLsAzj4uQPazQFIpe1tmluPW2xkKTQ3dkMOMYRdNezrye094yXg3CEA3SkW6vL0q9i7yv6M1lvtvCA5U6GJLWo/6Mm6LktwNg/UkHVEc70gakM89/O3rd4gDVbobCwF+Vq2fbdTZISkH9ovCeBH+ZqbHVeo62KXiO9XRrx4lwSUdSb7xVlQm3oSB1S3EuHPabSXr1ufAciMM+4rH1q+NK87KvV515Oty2BHaZil1E1E+lqVuNR201XmlHEBpqJmIY4CClxTkdtngcdUaM0hMITq5v4GQ4qBzr3R1HGYUNXKpKFY4pJBIkRqLX2Am9wSYuoIGhJRJQi3SOu2GPxXchyMq7jF9qzzfdAIeh71ahqD69pNnOFoajHJw4muY4g8+CtnS2joxgLcHbK/qMDtQ+xGBYCMtAXcsagmGOP96Vpq3jCWHzoiUuan6pxsUh8RBQOYR/SNdx0vgctahjn5gObfwoGR1ed4anKVDsh0YOcsY2hAhpuqUhZNY1DY5NyzJNx+CW00SO/pMMM8EY6MAxka05yMyXWbQLOGMH2cjhOy0NmBqqXeZU7ENAoNIB8gkFVtQH9Vqkxo6g3kuJBA7QaGhF+bbtik3x6rv9amaQU7+S9TyBxHqR1QeBZO+M6oVAx6Q5wUtI1y2F2vbov1PoxRQSe3bs5ykENE0Fh/LbV8ahSo2vZaFgSP5fWw41ZebeOdS4aAkl+giJdPFtY51SokhWnk3Zu3X1rKkB7p0UzYZFcLVGvdiJFknk4ZGi4mupLSgfW8FRRgnzdJRo+SXMl9sJMXlmHPK2xM6YXmxLiyJApzRqH703WJZXYVfUJtteJYkkrWKHsmbCEb0wqNv6zTMM+ik+vDzMec+avVIGOUWts1MCGC3Ck/A7idxFplBCrTi5EcJuHkl8P1kLRf5L2fERHg4XBSMa+yyy7o0AT0lwT+MfXkjeie6SVfp2y7hy7Ls4uKtNmNIoosvShMvAn8c7orjd4wpnW8V5arVsTQO2fZ0uOXnR5nI7j3+iYYx/bBgZu9Qjf/NDiECl2pvr4sZQ6Wvojqx4V5+hksbPuNpii9QB9e90tSh58839j5mnCrb/TXh08y8= \ 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 @@ +7V1bc6M2FP41eYwHSSDgMXGylzadTZtetn3pyCDbtNhyASf2/voKI2yQZJvEARFvZnYnRggsznc+zhV8gYaz1ceELKY/sZDGF9AKVxfo5gJC6Lo+/5OPrIsRABAqRiZJFIqx3cBD9I2KQUuMLqOQprWJGWNxFi3qgwGbz2mQ1cZIkrCn+rQxi+vfuiATqgw8BCRWR/+IwmxajHqOtRv/RKPJtPxmYIk9M1JOFgPplITsqTKEbi/QMGEsKz7NVkMa59Ir5VIc92HP3u3CEjrPmhzw1/DL5/HXydX62x8p+uHrFcHju0txlkcSL8UFX0Ac8/Ndjxk/LV91thaiwP8tWbnjMt0AdcUnAG/B0b7e7eefJuLv5kSjcuCOzCfLXNzQ+pDwk9B5WM7hqx7Jx/GxYg3lMKwtB3J5LvKPaUYXfP+CJtGMZjQRQ/e77eunaZTRhwUJ8vlPXFP52DSbxXwL5FcUrWipesV2HA9ZzJLNF6HQoV5o8/E0S9i/tLLHgyOE8XZxVSxKwdIko6vKkMDmI2V8ccmaTxF7faEmJVFKPXraad2WFNOKxnlijAhFn2zPvNMF/kGowzNUA3aoGvckTTnL34Q2EOqNA5024MCjo/HraIPjoIHrHNUIjLvUCKRoxA/kkShQJGw5D2koxHdM2hzUUtjei7jH+LmjLBcSv/A2qAg1gge+RvC205Lg7b1UDKPHkhL5qfn/682/LWkqE84fKMc0UI4C1P06m7L52YseGecIVkT/kZ2/2I1rvKeI/df1gjvQSbTIzl78tnGt9xXx3939/tPl51/OX/bGVb/0xhV/yLpdcU9zTvIz5Gzgfz5FNCFJMF23i8tx3/T1cdG4quY9JqAGtkM2WyQ05VGGlXPkuwCiByTZH0ZWXFPBkl9oymJ+wV34rwnLSBZx1wzdXPpWTyDEyJMhRBBvh6q5AN8dOF3iqAZ/+3Gs3O3eMcW+Qsu+YKrGlZ9nC5ZkNdjOHiDX7i3p1HhSQ7rbfHchVmh9ScIcNGuTj/9uOedZveWcGqdqIL0hWe5HfojZ0zuWTm/p6TbBcsiFnLD4UoD5QOdplEWPuZ3c5sETTbq7hvK7hhyKDEFv2a6mRzRY/k6SiIziXCN+S4sC2XfvNfm4t6xXcy461pPNmWQcjzP97JEFFuwBXT369Wf6NP1y+7f7Y+R/Sh64NMpkplb8+dVz13jKJmxO4jvGFkLo/9AsWwupk2XG6pDQVZR9FYfnn/+sjN+sKjtu1mKjwKJsNoBbodNQ6VRQUEjZMgnogSsU0XhGkgnNDszDeggTGpON7ap+qQ4Gceg9izalYgG9bUtFbrlUWaxfHCWBuV3Gy/H1FXxzoT6IzTmb0w1pXgly0BhyDmOy3mgJV3ex+Wd13+6wzVavVMU9UVVOYmzpTncEaZXFAqt+89gxCY4u9SeDFcfRIqXHTRtJF0V716Y9QzVMkmUbjykOtJYtdP2RdQgfxZJpENpr3JBfN2xYzb86mvSr8wotG1oIdFm7ziAYwz0Q4BF2cFsQ2EjqCgAqBrYuBd4WBrruja4w8AKqx2DkOXlxoC0aYKkgBA1jUIbqBkz/MTuxM/4107/zBEwYf8eon7htayj9RFfSi2JdrfmJurRrR4ylIHSoq2Osj11EWrtrIiCJHKmMRRrGotYYC1SZt+nZbVn6Rt113JCxtkmPUJf87ohYDXIdLZlCiVi2YZewrPq1TKxWld1tqOzIpLKDvqYbegFNkRo3hY2uctPVjeh481w7cZFd98l1N6JufXJdceTMAyPH6llwClU36+xy3nZTa2HpsTsxmgGKCyCHMy2nvXW86oUh6qUj3VxdjHoXEHUKan8y303hgUYdDGgy62co+Q0QqLsYmu7jTkMdaDCRYyr9DeTkma4Zv1sXw2DUb8rPA9CTMuDGHb33DPjz7Ixn2GuEruQ14m6T4NBgeGYqCw6gVDwEmsJVp2lw2G0S6c2nwUsgjruHp5auTqvLq88OdkYuU5lwsH0zxYEaU7fdEd3UmNpVeNRU4Y32AqG+5iD6AQ4yWpazYYO7UWvEqNyA4PDmFg77g8qprt1plDHYvGWqSAHkziGdjeg0bkLfYfsWsGHPcgioQSYnWCaP2ycl6Dy8yl//xzeDmKRpFNSRqN+7tFnOBmajGpj4luX7vc9+NnS1Tg1egeTqoYad/powWIrIkBxq7QmDOfpkXZm2yCekB5Yse6eOdWRh0sMMXm0+/1Cs4FVjctSgbv3KNGjiPelo8LaTOvuiw47og2w4QBtdt10LIVxTNNet7bPwS5mF0QDbwMecnD7CsG53bWtgYYAt4FvQ95DvtsS60r5KjwQdWLVkmmAXvGuQCzuBd89lETjCoS4L8OY4AuwBtlwP28jFEPt1rbDtAfdiAHIwRC56MUeQ6+++BEGJJFx5d9+CXeS2RBKkPjFZ0uDAyo8d0xJVdI/HdkKVU1KY4Dn0eotkUV9CozjrjUlhW+7A83dqj8yQwlMvyT1CChuZIUUpamOkqJXjGmb1++S3NX0eZV8e7T3s2UsJ6cGPMtRvlw/q6/la5cM2r3g25enGda59XWrvjNhrWOSnz7qIMOwGyd7+MaLmNpmt+55aaTHFB7m4+XI+WG1ZCPmtHfYxL0vuXOkiNWY3yNO3SqDmwfYeg/ImzInhxNgbpI/Sx3U0w+Wa8MjaLbAcoM+LyPD8uKbd+KTp07f7mjDe6bM/9yXRx++ADU6D+CT9l2bBVGiXUtjdXBdNbh9pfnmi3aH8BbD8iJCk0y099vZEiE6LmIxofM/yF0du3uYW0HnxW0Z5mTcKSHwnTRixLGOzyoSrOJrkO7K8NeOaiK3teaTCs2UBz7splrz5WaXZapK/YnJAvi0TOghYSP/mh1CFV9Jb6PIWTu0b5E4qT19KJYBSqasvOQdw4Kk82rZzPqNAzTd3P9JWKNjut+7Q7f8= \ 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 0000000000..b53637c326 Binary files /dev/null and b/docs/docs/assets/img/cpg-flow-no-backend.png differ 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 0000000000..ca5a199136 Binary files /dev/null and b/docs/docs/assets/img/cpg-flow.png differ 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 0000000000..8de553c5ef Binary files /dev/null and b/docs/docs/assets/img/logo-aisec.jpg differ 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 0000000000..c125b1cf55 Binary files /dev/null and b/docs/docs/assets/img/overall-view-black-background.png differ 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 0000000000..3b9e7e1ef3 Binary files /dev/null and b/docs/docs/assets/img/overall-view-white-background.png differ diff --git a/docs/docs/assets/img/overall-view.png b/docs/docs/assets/img/overall-view.png new file mode 100755 index 0000000000..6f1dd9b7f1 Binary files /dev/null and b/docs/docs/assets/img/overall-view.png differ diff --git a/docs/docs/assets/img/overall-view.svg b/docs/docs/assets/img/overall-view.svg new file mode 100755 index 0000000000..d13dfb9411 --- /dev/null +++ b/docs/docs/assets/img/overall-view.svg @@ -0,0 +1,1613 @@ + + + + + + + + + + + + + + + + + + + + + + + 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..70688c45ef --- /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. + + +

+ +

+ +
+

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..b9f34e4eb6 --- /dev/null +++ b/docs/docs/stylesheets/extra.css @@ -0,0 +1,446 @@ +[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: "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; + --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: "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.7rem; +} + +.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; + font-size: 0.7rem; +} + +.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.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 { + 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); +} + +.box { + border: 1px solid #c7cacc; +} + +.box p { + border: none; +} + +a.green-button { + -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; +} \ No newline at end of file diff --git a/docs/mkdocs-material-plugins.txt b/docs/mkdocs-material-plugins.txt new file mode 100755 index 0000000000..9d918e48ba --- /dev/null +++ b/docs/mkdocs-material-plugins.txt @@ -0,0 +1,3 @@ +mkdocs-git-revision-date-localized-plugin +mkdocs-glightbox +mkdocs-minify-plugin \ No newline at end of file diff --git a/docs/mkdocs.yaml b/docs/mkdocs.yaml new file mode 100755 index 0000000000..7ab9c29691 --- /dev/null +++ b/docs/mkdocs.yaml @@ -0,0 +1,173 @@ +# 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 + - assets/fonts/Inter/inter.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 + - "Shortcuts to Explore the Graph": GettingStarted/shortcuts.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 + # This assumes that the most recent dokka build was generated with the "main" tag! + - "API Reference": dokka/main 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 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 32f132ed94..46ee7ccde6 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,48 +1,51 @@ [versions] -kotlin = "1.8.0" -neo4j = "4.0.2" +kotlin = "1.9.0" +neo4j = "4.0.6" log4j = "2.20.0" -sonarqube = "4.0.0.2929" -spotless = "6.15.0" +sonarqube = "4.3.1.3277" +spotless = "6.21.0" +nexus-publish = "1.3.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 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" } -apache-commons-lang3 = { module = "org.apache.commons:commons-lang3", version = "3.12.0"} +jline = { module = "org.jline:jline", version = "3.23.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"} -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"} -eclipse-runtime = { module = "org.eclipse.platform:org.eclipse.core.runtime", version = "3.26.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"} -icu4j = { module = "com.ibm.icu:icu4j", version = "72.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.11.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"} 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"} -mockito = { module = "org.mockito:mockito-core", version = "5.1.1"} +junit-params = { module = "org.junit.jupiter:junit-jupiter-params", version = "5.10.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" } -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.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" } [bundles] log4j = ["log4j-impl", "log4j-core"] @@ -53,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 = "3.5.0"} +node = { id = "com.github.node-gradle.node", version = "7.0.0"} diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 070cb702f0..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-7.6-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/jitpack.yml b/jitpack.yml index 4fce533b52..8f25a3cc8c 100644 --- a/jitpack.yml +++ b/jitpack.yml @@ -2,8 +2,8 @@ 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 - - ls -l ~/go + - wget -q https://go.dev/dl/go1.20.4.linux-amd64.tar.gz && tar -C ~ -xzf go1.20.4.linux-amd64.tar.gz 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..40507dbf43 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") @@ -20,28 +9,33 @@ 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 enableCXXFrontend: Boolean by extra { + val enableCXXFrontend: String? by settings + enableCXXFrontend.toBoolean() +} +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() } 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") 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: