From cc8ae0da0a02d84e6566cf904d42ab2ac795f080 Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Sat, 30 Dec 2023 15:17:59 +0100 Subject: [PATCH 1/9] JVM language frontend based on SootUp --- .gitignore | 1 - build.gradle.kts | 6 + ...frontend-dependency-conventions.gradle.kts | 5 + configure_frontends.sh | 2 + .../fraunhofer/aisec/cpg/graph/Extensions.kt | 4 + cpg-language-jvm/build.gradle.kts | 50 +++ .../cpg/frontends/jvm/DeclarationHandler.kt | 150 +++++++ .../cpg/frontends/jvm/ExpressionHandler.kt | 376 ++++++++++++++++ .../aisec/cpg/frontends/jvm/JVMLanguage.kt | 59 +++ .../cpg/frontends/jvm/JVMLanguageFrontend.kt | 189 ++++++++ .../cpg/frontends/jvm/StatementHandler.kt | 164 +++++++ .../frontends/jvm/JVMLanguageFrontendTest.kt | 422 ++++++++++++++++++ .../class/arrays/mypackage/Arrays.class | Bin 0 -> 457 bytes .../class/arrays/mypackage/Arrays.java | 21 + .../class/arrays/mypackage/Element.class | Bin 0 -> 198 bytes .../class/arrays/mypackage/Element.java | 5 + .../class/fields/mypackage/Fields.class | Bin 0 -> 383 bytes .../class/fields/mypackage/Fields.java | 18 + .../mypackage/AnotherExtendedClass.class | Bin 0 -> 227 bytes .../mypackage/AnotherExtendedClass.java | 4 + .../inheritance/mypackage/Application.class | Bin 0 -> 864 bytes .../inheritance/mypackage/Application.java | 27 ++ .../inheritance/mypackage/BaseClass.class | Bin 0 -> 382 bytes .../inheritance/mypackage/BaseClass.java | 14 + .../inheritance/mypackage/ExtendedClass.class | Bin 0 -> 1100 bytes .../inheritance/mypackage/ExtendedClass.java | 18 + .../inheritance/mypackage/MyInterface.class | Bin 0 -> 141 bytes .../inheritance/mypackage/MyInterface.java | 7 + .../class/literals/mypackage/Literals.class | Bin 0 -> 1524 bytes .../class/literals/mypackage/Literals.java | 37 ++ .../class/methods/mypackage/Adder.class | Bin 0 -> 408 bytes .../class/methods/mypackage/Adder.java | 11 + .../class/methods/mypackage/Main.class | Bin 0 -> 606 bytes .../class/methods/mypackage/Main.java | 12 + .../test/resources/jar/literals/literals.jar | Bin 0 -> 915 bytes .../jimple/helloworld/HelloWorld.jimple | 22 + .../src/test/resources/log4j2.xml | 14 + gradle.properties.example | 3 +- gradle/libs.versions.toml | 8 + settings.gradle.kts | 7 +- 40 files changed, 1653 insertions(+), 3 deletions(-) create mode 100644 cpg-language-jvm/build.gradle.kts create mode 100644 cpg-language-jvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/jvm/DeclarationHandler.kt create mode 100644 cpg-language-jvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/jvm/ExpressionHandler.kt create mode 100644 cpg-language-jvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/jvm/JVMLanguage.kt create mode 100644 cpg-language-jvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/jvm/JVMLanguageFrontend.kt create mode 100644 cpg-language-jvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/jvm/StatementHandler.kt create mode 100644 cpg-language-jvm/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/jvm/JVMLanguageFrontendTest.kt create mode 100644 cpg-language-jvm/src/test/resources/class/arrays/mypackage/Arrays.class create mode 100644 cpg-language-jvm/src/test/resources/class/arrays/mypackage/Arrays.java create mode 100644 cpg-language-jvm/src/test/resources/class/arrays/mypackage/Element.class create mode 100644 cpg-language-jvm/src/test/resources/class/arrays/mypackage/Element.java create mode 100644 cpg-language-jvm/src/test/resources/class/fields/mypackage/Fields.class create mode 100644 cpg-language-jvm/src/test/resources/class/fields/mypackage/Fields.java create mode 100644 cpg-language-jvm/src/test/resources/class/inheritance/mypackage/AnotherExtendedClass.class create mode 100644 cpg-language-jvm/src/test/resources/class/inheritance/mypackage/AnotherExtendedClass.java create mode 100644 cpg-language-jvm/src/test/resources/class/inheritance/mypackage/Application.class create mode 100644 cpg-language-jvm/src/test/resources/class/inheritance/mypackage/Application.java create mode 100644 cpg-language-jvm/src/test/resources/class/inheritance/mypackage/BaseClass.class create mode 100644 cpg-language-jvm/src/test/resources/class/inheritance/mypackage/BaseClass.java create mode 100644 cpg-language-jvm/src/test/resources/class/inheritance/mypackage/ExtendedClass.class create mode 100644 cpg-language-jvm/src/test/resources/class/inheritance/mypackage/ExtendedClass.java create mode 100644 cpg-language-jvm/src/test/resources/class/inheritance/mypackage/MyInterface.class create mode 100644 cpg-language-jvm/src/test/resources/class/inheritance/mypackage/MyInterface.java create mode 100644 cpg-language-jvm/src/test/resources/class/literals/mypackage/Literals.class create mode 100644 cpg-language-jvm/src/test/resources/class/literals/mypackage/Literals.java create mode 100644 cpg-language-jvm/src/test/resources/class/methods/mypackage/Adder.class create mode 100644 cpg-language-jvm/src/test/resources/class/methods/mypackage/Adder.java create mode 100644 cpg-language-jvm/src/test/resources/class/methods/mypackage/Main.class create mode 100644 cpg-language-jvm/src/test/resources/class/methods/mypackage/Main.java create mode 100644 cpg-language-jvm/src/test/resources/jar/literals/literals.jar create mode 100644 cpg-language-jvm/src/test/resources/jimple/helloworld/HelloWorld.jimple create mode 100644 cpg-language-jvm/src/test/resources/log4j2.xml diff --git a/.gitignore b/.gitignore index 8dcaac4922..99d9338d5c 100644 --- a/.gitignore +++ b/.gitignore @@ -15,7 +15,6 @@ out .data/ logs /lsp/*.log -*.class *.dylib *.so diff --git a/build.gradle.kts b/build.gradle.kts index 205e0059fa..d55860c422 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -150,3 +150,9 @@ val enableRubyFrontend: Boolean by extra { enableRubyFrontend.toBoolean() } project.logger.lifecycle("Ruby frontend is ${if (enableRubyFrontend) "enabled" else "disabled"}") + +val enableJVMFrontend: Boolean by extra { + val enableJVMFrontend: String? by project + enableJVMFrontend.toBoolean() +} +project.logger.lifecycle("JVM frontend is ${if (enableJVMFrontend) "enabled" else "disabled"}") 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 7325c19481..10fef182f3 100644 --- a/buildSrc/src/main/kotlin/cpg.frontend-dependency-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/cpg.frontend-dependency-conventions.gradle.kts @@ -11,12 +11,17 @@ val enablePythonFrontend: Boolean by rootProject.extra val enableLLVMFrontend: Boolean by rootProject.extra val enableTypeScriptFrontend: Boolean by rootProject.extra val enableRubyFrontend: Boolean by rootProject.extra +val enableJVMFrontend: Boolean by rootProject.extra dependencies { if (enableJavaFrontend) { api(project(":cpg-language-java")) kover(project(":cpg-language-java")) } + if (enableJVMFrontend) { + api(project(":cpg-language-jvm")) + kover(project(":cpg-language-jvm")) + } if (enableCXXFrontend) { api(project(":cpg-language-cxx")) kover(project(":cpg-language-cxx")) diff --git a/configure_frontends.sh b/configure_frontends.sh index 7304b0b04c..49e3233752 100755 --- a/configure_frontends.sh +++ b/configure_frontends.sh @@ -58,3 +58,5 @@ answerTypescript=$(ask "Do you want to enable the TypeScript frontend? (currentl setProperty "enableTypeScriptFrontend" $answerTypescript answerRuby=$(ask "Do you want to enable the Ruby frontend? (currently $(getProperty "enableRubyFrontend"))") setProperty "enableRubyFrontend" $answerRuby +answerJVM=$(ask "Do you want to enable the JVM frontend? (currently $(getProperty "enableJVMFrontend"))") +setProperty "enableJVMFrontend" $answerJVM 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 88d65ab47a..77c6cb28f8 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 @@ -614,6 +614,10 @@ val Node?.returns: List val Node?.assigns: List get() = this.allChildren() +/** Returns all [ProblemNode] children in this graph, starting with this [Node]. */ +val Node?.problems: List + get() = this.allChildren() + /** Returns all [Assignment] child edges in this graph, starting with this [Node]. */ val Node?.assignments: List get() { diff --git a/cpg-language-jvm/build.gradle.kts b/cpg-language-jvm/build.gradle.kts new file mode 100644 index 0000000000..e809f20033 --- /dev/null +++ b/cpg-language-jvm/build.gradle.kts @@ -0,0 +1,50 @@ +/* + * 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. + * + * $$$$$$\ $$$$$$$\ $$$$$$\ + * $$ __$$\ $$ __$$\ $$ __$$\ + * $$ / \__|$$ | $$ |$$ / \__| + * $$ | $$$$$$$ |$$ |$$$$\ + * $$ | $$ ____/ $$ |\_$$ | + * $$ | $$\ $$ | $$ | $$ | + * \$$$$$ |$$ | \$$$$$ | + * \______/ \__| \______/ + * + */ +plugins { + id("cpg.frontend-conventions") +} + +publishing { + publications { + named("cpg-language-jvm") { + pom { + artifactId = "cpg-language-jvm" + name.set("Code Property Graph - JVM bytecode Frontend") + description.set("A JVM bytecode frontend for the CPG") + } + } + } +} + +tasks.withType().configureEach { + kotlinOptions { + freeCompilerArgs = listOf("-Xcontext-receivers") + } +} + +dependencies { + api(libs.bundles.sootup) +} diff --git a/cpg-language-jvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/jvm/DeclarationHandler.kt b/cpg-language-jvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/jvm/DeclarationHandler.kt new file mode 100644 index 0000000000..97b57b5acd --- /dev/null +++ b/cpg-language-jvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/jvm/DeclarationHandler.kt @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2024, Fraunhofer AISEC. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * $$$$$$\ $$$$$$$\ $$$$$$\ + * $$ __$$\ $$ __$$\ $$ __$$\ + * $$ / \__|$$ | $$ |$$ / \__| + * $$ | $$$$$$$ |$$ |$$$$\ + * $$ | $$ ____/ $$ |\_$$ | + * $$ | $$\ $$ | $$ | $$ | + * \$$$$$ |$$ | \$$$$$ | + * \______/ \__| \______/ + * + */ +package de.fraunhofer.aisec.cpg.frontends.jvm + +import de.fraunhofer.aisec.cpg.frontends.Handler +import de.fraunhofer.aisec.cpg.graph.* +import de.fraunhofer.aisec.cpg.graph.declarations.* +import sootup.core.jimple.basic.Local +import sootup.core.model.SootClass +import sootup.core.model.SootField +import sootup.core.model.SootMethod +import sootup.java.core.JavaSootClass +import sootup.java.core.JavaSootField +import sootup.java.core.JavaSootMethod +import sootup.java.core.jimple.basic.JavaLocal + +class DeclarationHandler(frontend: JVMLanguageFrontend) : + Handler(::ProblemDeclaration, frontend) { + init { + map.put(SootClass::class.java) { handleClass(it as SootClass) } + map.put(JavaSootClass::class.java) { handleClass(it as SootClass) } + map.put(SootMethod::class.java) { handleMethod(it as SootMethod) } + map.put(JavaSootMethod::class.java) { handleMethod(it as SootMethod) } + map.put(SootField::class.java) { handleField(it as SootField) } + map.put(JavaSootField::class.java) { handleField(it as SootField) } + map.put(Local::class.java) { handleLocal(it as Local) } + map.put(JavaLocal::class.java) { handleLocal(it as Local) } + } + + private fun handleClass(sootClass: SootClass): RecordDeclaration { + val record = + newRecordDeclaration( + sootClass.getName(), + if (sootClass.isInterface()) { + "interface" + } else { + "class" + }, + rawNode = sootClass + ) + + // Collect super class + val o = sootClass.superclass + if (o.isPresent) { + record.addSuperClass(frontend.typeOf(o.get())) + } + + // Collect implemented interfaces + for (i in sootClass.interfaces) { + record.implementedInterfaces += frontend.typeOf(i) + } + + // Enter the class scope + frontend.scopeManager.enterScope(record) + + // Loop through all fields + for (sootField in sootClass.fields) { + val field = handle(sootField) + frontend.scopeManager.addDeclaration(field) + } + + // Loop through all methods + for (sootMethod in sootClass.methods) { + val method = handle(sootMethod) + frontend.scopeManager.addDeclaration(method) + } + + // Leave the class scope + frontend.scopeManager.leaveScope(record) + + return record + } + + private fun handleMethod(sootMethod: SootMethod): MethodDeclaration { + val record = frontend.scopeManager.currentRecord + + val method = + if (sootMethod.name == "") { + newConstructorDeclaration(sootMethod.name, record, rawNode = sootMethod) + } else { + newMethodDeclaration( + sootMethod.name, + sootMethod.isStatic, + frontend.scopeManager.currentRecord, + rawNode = sootMethod, + ) + } + + // Enter method scope + frontend.scopeManager.enterScope(method) + + // Add "@this" as the receiver + method.receiver = + newVariableDeclaration("@this", method.recordDeclaration?.toType() ?: unknownType()) + .implicit("@this") + frontend.scopeManager.addDeclaration(method.receiver) + + // Add method parameters + for ((index, type) in sootMethod.parameterTypes.withIndex()) { + val param = newParameterDeclaration("@parameter${index}", frontend.typeOf(type)) + frontend.scopeManager.addDeclaration(param) + } + + if (sootMethod.isConcrete) { + // Handle method body + method.body = frontend.statementHandler.handle(sootMethod.body) + } + + // Leave method scope + frontend.scopeManager.leaveScope(method) + + return method + } + + fun handleField(field: SootField): FieldDeclaration { + return newFieldDeclaration( + field.name, + frontend.typeOf(field.type), + field.modifiers.map { it.name.lowercase() }, + rawNode = field + ) + } + + private fun handleLocal(local: Local): VariableDeclaration { + return newVariableDeclaration(local.name, frontend.typeOf(local.type), rawNode = local) + } +} diff --git a/cpg-language-jvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/jvm/ExpressionHandler.kt b/cpg-language-jvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/jvm/ExpressionHandler.kt new file mode 100644 index 0000000000..33b8911575 --- /dev/null +++ b/cpg-language-jvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/jvm/ExpressionHandler.kt @@ -0,0 +1,376 @@ +/* + * Copyright (c) 2024, Fraunhofer AISEC. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * $$$$$$\ $$$$$$$\ $$$$$$\ + * $$ __$$\ $$ __$$\ $$ __$$\ + * $$ / \__|$$ | $$ |$$ / \__| + * $$ | $$$$$$$ |$$ |$$$$\ + * $$ | $$ ____/ $$ |\_$$ | + * $$ | $$\ $$ | $$ | $$ | + * \$$$$$ |$$ | \$$$$$ | + * \______/ \__| \______/ + * + */ +package de.fraunhofer.aisec.cpg.frontends.jvm + +import de.fraunhofer.aisec.cpg.frontends.Handler +import de.fraunhofer.aisec.cpg.graph.* +import de.fraunhofer.aisec.cpg.graph.statements.expressions.* +import de.fraunhofer.aisec.cpg.graph.types.FunctionType +import de.fraunhofer.aisec.cpg.passes.SymbolResolver +import sootup.core.jimple.basic.Local +import sootup.core.jimple.basic.Value +import sootup.core.jimple.common.constant.* +import sootup.core.jimple.common.expr.* +import sootup.core.jimple.common.ref.* +import sootup.core.signatures.MethodSignature +import sootup.core.signatures.SootClassMemberSignature +import sootup.java.core.jimple.basic.JavaLocal + +class ExpressionHandler(frontend: JVMLanguageFrontend) : + Handler( + ::ProblemExpression, + frontend, + ) { + + init { + map.put(Local::class.java) { handleLocal(it as Local) } + map.put(JavaLocal::class.java) { handleLocal(it as Local) } + map.put(JThisRef::class.java) { handleThisRef(it as JThisRef) } + map.put(JParameterRef::class.java) { handleParameterRef(it as JParameterRef) } + map.put(JInstanceFieldRef::class.java) { handleInstanceFieldRef(it as JInstanceFieldRef) } + map.put(JStaticFieldRef::class.java) { handleStaticFieldRef(it as JStaticFieldRef) } + map.put(JArrayRef::class.java) { handleArrayRef(it as JArrayRef) } + map.put(JInterfaceInvokeExpr::class.java) { + handleInterfaceInvokeExpr(it as JInterfaceInvokeExpr) + } + map.put(JVirtualInvokeExpr::class.java) { + handleVirtualInvokeExpr(it as JVirtualInvokeExpr) + } + map.put(JDynamicInvokeExpr::class.java) { + handleDynamicInvokeExpr(it as JDynamicInvokeExpr) + } + map.put(JSpecialInvokeExpr::class.java) { handleSpecialInvoke(it as JSpecialInvokeExpr) } + map.put(JStaticInvokeExpr::class.java) { handleStaticInvoke(it as JStaticInvokeExpr) } + map.put(JNewExpr::class.java) { handleNewExpr(it as JNewExpr) } + map.put(JNewArrayExpr::class.java) { handleNewArrayExpr(it as JNewArrayExpr) } + map.put(JNewMultiArrayExpr::class.java) { + handleNewMultiArrayExpr(it as JNewMultiArrayExpr) + } + map.put(JCastExpr::class.java) { handleCastExpr(it as JCastExpr) } + + // Binary operators + // - Equality checks + map.put(JEqExpr::class.java) { handleAbstractBinopExpr(it as AbstractBinopExpr) } + map.put(JNeExpr::class.java) { handleAbstractBinopExpr(it as AbstractBinopExpr) } + map.put(JGeExpr::class.java) { handleAbstractBinopExpr(it as AbstractBinopExpr) } + map.put(JGtExpr::class.java) { handleAbstractBinopExpr(it as AbstractBinopExpr) } + map.put(JLeExpr::class.java) { handleAbstractBinopExpr(it as AbstractBinopExpr) } + map.put(JLtExpr::class.java) { handleAbstractBinopExpr(it as AbstractBinopExpr) } + + // - Numeric comparisons + map.put(JCmpExpr::class.java) { handleAbstractBinopExpr(it as AbstractBinopExpr) } + map.put(JCmplExpr::class.java) { handleAbstractBinopExpr(it as AbstractBinopExpr) } + map.put(JCmpgExpr::class.java) { handleAbstractBinopExpr(it as AbstractBinopExpr) } + + // - Simple arithmetics + map.put(JAddExpr::class.java) { handleAbstractBinopExpr(it as AbstractBinopExpr) } + map.put(JDivExpr::class.java) { handleAbstractBinopExpr(it as AbstractBinopExpr) } + map.put(JMulExpr::class.java) { handleAbstractBinopExpr(it as AbstractBinopExpr) } + map.put(JRemExpr::class.java) { handleAbstractBinopExpr(it as AbstractBinopExpr) } + map.put(JSubExpr::class.java) { handleAbstractBinopExpr(it as AbstractBinopExpr) } + + // - Binary arithmetics + map.put(JAndExpr::class.java) { handleAbstractBinopExpr(it as AbstractBinopExpr) } + map.put(JOrExpr::class.java) { handleAbstractBinopExpr(it as AbstractBinopExpr) } + map.put(JShlExpr::class.java) { handleAbstractBinopExpr(it as AbstractBinopExpr) } + map.put(JShrExpr::class.java) { handleAbstractBinopExpr(it as AbstractBinopExpr) } + map.put(JUshrExpr::class.java) { handleAbstractBinopExpr(it as AbstractBinopExpr) } + map.put(JXorExpr::class.java) { handleAbstractBinopExpr(it as AbstractBinopExpr) } + + // Unary operator + map.put(JNegExpr::class.java) { handleNegExpr(it as JNegExpr) } + + // Special operators, which we need to model as binary/unary operators + map.put(JInstanceOfExpr::class.java) { handleInstanceOfExpr(it as JInstanceOfExpr) } + map.put(JLengthExpr::class.java) { handleLengthExpr(it as JLengthExpr) } + + // Constants + map.put(BooleanConstant::class.java) { handleBooleanConstant(it as BooleanConstant) } + map.put(FloatConstant::class.java) { handleFloatConstant(it as FloatConstant) } + map.put(DoubleConstant::class.java) { handleDoubleConstant(it as DoubleConstant) } + map.put(IntConstant::class.java) { handleIntConstant(it as IntConstant) } + map.put(LongConstant::class.java) { handleLongConstant(it as LongConstant) } + map.put(StringConstant::class.java) { handleStringConstant(it as StringConstant) } + map.put(NullConstant::class.java) { handleNullConstant(it as NullConstant) } + map.put(ClassConstant::class.java) { handleClassConstant(it as ClassConstant) } + } + + private fun handleLocal(local: Local): Expression { + // Apparently, a local can either be a reference to variable or a literal + return if (local.name.startsWith("\"")) { + val lit = newLiteral(local.name.substring(1, local.name.length - 2), rawNode = local) + lit.type = objectType("java.lang.String") + + lit + } else { + val ref = newReference(local.name, frontend.typeOf(local.type), rawNode = local) + + ref + } + } + + private fun handleThisRef(thisRef: JThisRef): Reference { + val ref = newReference("@this", frontend.typeOf(thisRef.type), rawNode = thisRef) + + return ref + } + + private fun handleParameterRef(parameterRef: JParameterRef): Reference { + val ref = + newReference( + "@parameter${parameterRef.index}", + frontend.typeOf(parameterRef.type), + rawNode = parameterRef + ) + + return ref + } + + private fun handleInstanceFieldRef(instanceFieldRef: JInstanceFieldRef): Reference { + val base = handle(instanceFieldRef.base) ?: newProblemExpression("missing base") + + val ref = + newMemberExpression( + instanceFieldRef.fieldSignature.name, + base, + frontend.typeOf(instanceFieldRef.fieldSignature.type), + rawNode = instanceFieldRef + ) + + return ref + } + + private fun handleStaticFieldRef(staticFieldRef: JStaticFieldRef) = + staticFieldRef.fieldSignature.toStaticRef() + + private fun handleArrayRef(arrayRef: JArrayRef): SubscriptExpression { + val sub = newSubscriptExpression(rawNode = arrayRef) + sub.arrayExpression = handle(arrayRef.base) ?: newProblemExpression("missing base") + sub.subscriptExpression = handle(arrayRef.index) ?: newProblemExpression("missing index") + + return sub + } + + private fun handleAbstractInstanceInvokeExpr( + invokeExpr: AbstractInstanceInvokeExpr + ): MemberCallExpression { + val base = handle(invokeExpr.base) ?: newProblemExpression("could not parse base") + // Not really necessary, but since we already have the type information, we can use it + base.type = frontend.typeOf(invokeExpr.methodSignature.declClassType) + + val callee = newMemberExpression(invokeExpr.methodSignature.name, base) + + val call = newMemberCallExpression(callee, rawNode = invokeExpr) + call.arguments = invokeExpr.args.mapNotNull { handle(it) } + + return call + } + + private fun handleVirtualInvokeExpr(invokeExpr: JVirtualInvokeExpr): MemberCallExpression { + return handleAbstractInstanceInvokeExpr(invokeExpr) + } + + private fun handleInterfaceInvokeExpr(invokeExpr: JInterfaceInvokeExpr): MemberCallExpression { + return handleAbstractInstanceInvokeExpr(invokeExpr) + } + + /** + * The difference between [JSpecialInvokeExpr] and a regular [JVirtualInvokeExpr] is that the + * invoked function is not part of the declared class, but rather it is a function of its base + * class(es). + * + * We currently can only model this as a regular call and hope that the [SymbolResolver] will + * pick the correct function. Maybe we can supply some kind of hint to the resolver to make this + * better. + */ + private fun handleSpecialInvoke(invokeExpr: JSpecialInvokeExpr): Expression { + // This is probably a constructor call + return if (invokeExpr.methodSignature.name == "") { + val type = frontend.typeOf(invokeExpr.methodSignature.declClassType) + val construct = newConstructExpression(rawNode = invokeExpr) + construct.callee = newReference(Name("", type.name)) + construct.type = type + + construct.arguments = invokeExpr.args.mapNotNull { handle(it) } + + construct + } else { + // Just a normal call + return handleAbstractInstanceInvokeExpr(invokeExpr) + } + } + + private fun handleDynamicInvokeExpr(dynamicInvokeExpr: AbstractInvokeExpr): CallExpression { + // Model this as a static call to the method. Not sure if this is really that good or if we + // want to somehow "call" the underlying bootstrap method. + // TODO(oxisto): This is actually somewhat related to a LambdaExpression, but not really + // sure ow to model this + val callee = dynamicInvokeExpr.methodSignature.toStaticRef() + val call = newCallExpression(callee, rawNode = dynamicInvokeExpr) + call.arguments = dynamicInvokeExpr.args.mapNotNull { handle(it) } + call.type = frontend.typeOf(dynamicInvokeExpr.methodSignature.type) + + return call + } + + private fun handleStaticInvoke(staticInvokeExpr: JStaticInvokeExpr): CallExpression { + val ref = staticInvokeExpr.methodSignature.toStaticRef() + + val call = newCallExpression(ref, rawNode = staticInvokeExpr) + call.arguments = staticInvokeExpr.args.mapNotNull { handle(it) } + call.type = frontend.typeOf(staticInvokeExpr.type) + + return call + } + + /** + * In the jimple IR, the "new" and the constructor calls are split into two expressions. This + * will only handle the "new" expression, a later call to "invokespecial" will handle the + * constructor call. + */ + private fun handleNewExpr(newExpr: JNewExpr) = + newNewExpression(frontend.typeOf(newExpr.type), rawNode = newExpr) + + private fun handleNewArrayExpr(newArrayExpr: JNewArrayExpr): NewArrayExpression { + val new = newNewArrayExpression(rawNode = newArrayExpr) + new.type = frontend.typeOf(newArrayExpr.type) + new.dimensions = listOfNotNull(handle(newArrayExpr.size)) + + return new + } + + private fun handleNewMultiArrayExpr(newMultiArrayExpr: JNewMultiArrayExpr): NewArrayExpression { + val new = newNewArrayExpression(rawNode = newMultiArrayExpr) + new.type = frontend.typeOf(newMultiArrayExpr.type) + new.dimensions = newMultiArrayExpr.sizes.mapNotNull { handle(it) } + + return new + } + + private fun handleCastExpr(castExpr: JCastExpr): CastExpression { + val cast = newCastExpression(rawNode = castExpr) + cast.expression = handle(castExpr.op) ?: newProblemExpression("missing expression") + cast.castType = frontend.typeOf(castExpr.type) + + return cast + } + + private fun handleAbstractBinopExpr(expr: AbstractBinopExpr): BinaryOperator { + val op = newBinaryOperator(expr.symbol.trim(), rawNode = expr) + op.lhs = handle(expr.op1) ?: newProblemExpression("missing lhs") + op.rhs = handle(expr.op2) ?: newProblemExpression("missing rhs") + op.type = frontend.typeOf(expr.type) + + return op + } + + private fun handleNegExpr(expr: AbstractUnopExpr): UnaryOperator { + val op = newUnaryOperator("-", postfix = false, prefix = true, rawNode = expr) + op.input = handle(expr.op) ?: newProblemExpression("missing input") + op.type = frontend.typeOf(expr.type) + + return op + } + + private fun handleInstanceOfExpr(instanceOfExpr: JInstanceOfExpr): BinaryOperator { + val op = newBinaryOperator("instanceof", rawNode = instanceOfExpr) + op.lhs = handle(instanceOfExpr.op) ?: newProblemExpression("missing lhs") + + val type = frontend.typeOf(instanceOfExpr.checkType) + op.rhs = newTypeExpression("", type, rawNode = type) + op.rhs.name = type.name + op.type = frontend.typeOf(instanceOfExpr.type) + + return op + } + + private fun handleLengthExpr(lengthExpr: JLengthExpr): UnaryOperator { + val op = newUnaryOperator("lengthof", prefix = true, postfix = false, rawNode = lengthExpr) + op.input = handle(lengthExpr.op) ?: newProblemExpression("missing input") + op.type = frontend.typeOf(lengthExpr.type) + + return op + } + + private fun handleBooleanConstant(constant: BooleanConstant) = + newLiteral( + constant.equalEqual(BooleanConstant.getTrue()), + primitiveType("boolean"), + rawNode = constant + ) + + private fun handleFloatConstant(constant: FloatConstant) = + newLiteral(constant.value, primitiveType("float"), rawNode = constant) + + private fun handleDoubleConstant(constant: DoubleConstant) = + newLiteral(constant.value, primitiveType("double"), rawNode = constant) + + private fun handleIntConstant(constant: IntConstant) = + newLiteral(constant.value, primitiveType("int"), rawNode = constant) + + private fun handleLongConstant(constant: LongConstant) = + newLiteral(constant.value, primitiveType("long"), rawNode = constant) + + private fun handleStringConstant(constant: StringConstant) = + newLiteral(constant.value, primitiveType("java.lang.String"), rawNode = constant) + + private fun handleNullConstant(constant: NullConstant) = + newLiteral(null, unknownType(), rawNode = constant) + + /** + * We need to keep the class name as a string, rather than a [Class], because otherwise we would + * try to find the specified class on the classpath, which can lead to unwanted results. + */ + private fun handleClassConstant(constant: ClassConstant) = + newLiteral(constant.value, primitiveType("java.lang.Class"), rawNode = constant) + + private fun MethodSignature.toStaticRef(): Reference { + // First, construct the name using . + val ref = (this as SootClassMemberSignature<*>).toStaticRef() + + // We can also provide a function type, since these are all statically known. This might + // help in inferring some (unknown) functions later + ref.type = + FunctionType( + this.name, + this.parameterTypes.map { frontend.typeOf(it) }, + listOf(frontend.typeOf(this.type)), + frontend.language + ) + + return ref + } + + private fun SootClassMemberSignature<*>.toStaticRef(): Reference { + // First, construct the name using . + val ref = newReference("${this.declClassType.fullyQualifiedName}.${this.name}") + + // Make it static + ref.isStaticAccess = true + + return ref + } +} diff --git a/cpg-language-jvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/jvm/JVMLanguage.kt b/cpg-language-jvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/jvm/JVMLanguage.kt new file mode 100644 index 0000000000..f5bae138b7 --- /dev/null +++ b/cpg-language-jvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/jvm/JVMLanguage.kt @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2024, Fraunhofer AISEC. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * $$$$$$\ $$$$$$$\ $$$$$$\ + * $$ __$$\ $$ __$$\ $$ __$$\ + * $$ / \__|$$ | $$ |$$ / \__| + * $$ | $$$$$$$ |$$ |$$$$\ + * $$ | $$ ____/ $$ |\_$$ | + * $$ | $$\ $$ | $$ | $$ | + * \$$$$$ |$$ | \$$$$$ | + * \______/ \__| \______/ + * + */ +package de.fraunhofer.aisec.cpg.frontends.jvm + +import de.fraunhofer.aisec.cpg.frontends.Language +import de.fraunhofer.aisec.cpg.graph.types.* +import kotlin.reflect.KClass + +class JVMLanguage : Language() { + override val fileExtensions: List + get() = listOf("class", "java", "jimple", "jar") + + override val namespaceDelimiter: String + get() = "." + + override val frontend: KClass + get() = JVMLanguageFrontend::class + + override val builtInTypes: Map + get() = + mapOf( + "float" to FloatingPointType("float", 32, this), + "double" to FloatingPointType("double", 64, this), + "char" to IntegerType("char", 8, this, NumericType.Modifier.UNSIGNED), + "boolean" to BooleanType("boolean", 1, this), + "byte" to IntegerType("byte", 8, this), + "short" to IntegerType("short", 16, this), + "int" to IntegerType("int", 32, this), + "long" to IntegerType("long", 64, this), + "java.lang.String" to StringType("java.lang.String", this), + "java.lang.Class" to ObjectType("java.lang.Class", listOf(), true, this) + ) + + override val compoundAssignmentOperators: Set + get() = setOf() +} diff --git a/cpg-language-jvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/jvm/JVMLanguageFrontend.kt b/cpg-language-jvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/jvm/JVMLanguageFrontend.kt new file mode 100644 index 0000000000..26f111e65e --- /dev/null +++ b/cpg-language-jvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/jvm/JVMLanguageFrontend.kt @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2024, Fraunhofer AISEC. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * $$$$$$\ $$$$$$$\ $$$$$$\ + * $$ __$$\ $$ __$$\ $$ __$$\ + * $$ / \__|$$ | $$ |$$ / \__| + * $$ | $$$$$$$ |$$ |$$$$\ + * $$ | $$ ____/ $$ |\_$$ | + * $$ | $$\ $$ | $$ | $$ | + * \$$$$$ |$$ | \$$$$$ | + * \______/ \__| \______/ + * + */ +package de.fraunhofer.aisec.cpg.frontends.jvm + +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.NamespaceDeclaration +import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration +import de.fraunhofer.aisec.cpg.graph.types.Type +import de.fraunhofer.aisec.cpg.sarif.PhysicalLocation +import java.io.File +import sootup.core.model.Body +import sootup.core.model.SootMethod +import sootup.core.model.SourceType +import sootup.core.types.ArrayType +import sootup.core.types.UnknownType +import sootup.core.util.printer.NormalStmtPrinter +import sootup.java.bytecode.inputlocation.JavaClassPathAnalysisInputLocation +import sootup.java.bytecode.interceptors.* +import sootup.java.core.views.JavaView +import sootup.java.sourcecode.inputlocation.JavaSourcePathAnalysisInputLocation +import sootup.jimple.parser.JimpleAnalysisInputLocation +import sootup.jimple.parser.JimpleView + +typealias SootType = sootup.core.types.Type + +class JVMLanguageFrontend( + language: Language>, + ctx: TranslationContext +) : LanguageFrontend(language, ctx) { + + val declarationHandler = DeclarationHandler(this) + val statementHandler = StatementHandler(this) + val expressionHandler = ExpressionHandler(this) + + lateinit var view: JavaView + + var body: Body? = null + + var printer: NormalStmtPrinter? = null + + /** + * Because of a limitation in SootUp, we can only specify the whole classpath for soot to parse. + * But in the CPG we need to specify one file. In this case, we take the + * [TranslationConfiguration.topLevel] and hand it over to soot, which parses all appropriate + * files within this folder/classpath. This means that the returned [TranslationUnitDeclaration] + * will contain not just the content of one file but the whole directory. + */ + override fun parse(file: File): TranslationUnitDeclaration { + val view = + when (file.extension) { + "class" -> { + JavaView( + JavaClassPathAnalysisInputLocation( + ctx.config.topLevel!!.path, + SourceType.Library, + listOf( + NopEliminator(), + CastAndReturnInliner(), + UnreachableCodeEliminator(), + Aggregator(), + CopyPropagator(), + // ConditionalBranchFolder(), + EmptySwitchEliminator(), + TypeAssigner(), + LocalNameStandardizer() + ) + ) + ) + } + "jar" -> { + JavaView( + JavaClassPathAnalysisInputLocation( + file.path, + SourceType.Library, + listOf( + NopEliminator(), + CastAndReturnInliner(), + UnreachableCodeEliminator(), + Aggregator(), + CopyPropagator(), + // ConditionalBranchFolder(), + EmptySwitchEliminator(), + TypeAssigner(), + LocalNameStandardizer() + ) + ) + ) + } + "java" -> { + JavaView(JavaSourcePathAnalysisInputLocation(ctx.config.topLevel!!.path)) + } + "jimple" -> { + JimpleView(JimpleAnalysisInputLocation(ctx.config.topLevel!!.toPath())) + } + else -> { + throw TranslationException("unsupported file") + } + } + // This contains the whole directory + val tu = newTranslationUnitDeclaration(file.parent) + scopeManager.resetToGlobal(tu) + + val packages = mutableMapOf() + + for (sootClass in view.classes) { + // Create an appropriate namespace, if it does not already exist + val pkg = + packages.computeIfAbsent(sootClass.type.packageName.name) { + val pkg = newNamespaceDeclaration(it) + scopeManager.addDeclaration(pkg) + pkg + } + + // Enter namespace scope + scopeManager.enterScope(pkg) + + val decl = declarationHandler.handle(sootClass) + scopeManager.addDeclaration(decl) + + // Leave namespace scope + scopeManager.leaveScope(pkg) + + // We need to clear the processed because they need to be per-file and we only have one + // frontend for all files + clearProcessed() + } + + return tu + } + + override fun setComment(node: Node, astNode: Any) {} + + override fun locationOf(astNode: Any): PhysicalLocation? { + // We do not really have a location anyway. maybe in jimple? + return null + } + + override fun codeOf(astNode: Any): String? { + if (astNode is SootMethod && astNode.isConcrete) { + return astNode.body.toString() + } + // We do not really have a source anyway. maybe in jimple? + return "" + } + + override fun typeOf(type: SootType): Type { + return when (type) { + is UnknownType -> { + unknownType() + } + is ArrayType -> { + typeOf(type.baseType).array() + } + else -> { + // TODO(oxisto): primitive types + val out = objectType(type.toString()) + + out + } + } + } +} diff --git a/cpg-language-jvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/jvm/StatementHandler.kt b/cpg-language-jvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/jvm/StatementHandler.kt new file mode 100644 index 0000000000..711b1c2308 --- /dev/null +++ b/cpg-language-jvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/jvm/StatementHandler.kt @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2024, Fraunhofer AISEC. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * $$$$$$\ $$$$$$$\ $$$$$$\ + * $$ __$$\ $$ __$$\ $$ __$$\ + * $$ / \__|$$ | $$ |$$ / \__| + * $$ | $$$$$$$ |$$ |$$$$\ + * $$ | $$ ____/ $$ |\_$$ | + * $$ | $$\ $$ | $$ | $$ | + * \$$$$$ |$$ | \$$$$$ | + * \______/ \__| \______/ + * + */ +package de.fraunhofer.aisec.cpg.frontends.jvm + +import de.fraunhofer.aisec.cpg.frontends.Handler +import de.fraunhofer.aisec.cpg.graph.* +import de.fraunhofer.aisec.cpg.graph.statements.* +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.ProblemExpression +import sootup.core.jimple.common.stmt.* +import sootup.core.model.Body +import sootup.core.util.printer.NormalStmtPrinter + +class StatementHandler(frontend: JVMLanguageFrontend) : + Handler(::ProblemExpression, frontend) { + init { + map.put(Body::class.java) { handleBody(it as Body) } + map.put(JAssignStmt::class.java) { handleAbstractDefinitionStmt(it as JAssignStmt) } + map.put(JIdentityStmt::class.java) { handleAbstractDefinitionStmt(it as JIdentityStmt) } + map.put(JIfStmt::class.java) { handleIfStmt(it as JIfStmt) } + map.put(JGotoStmt::class.java) { handleGotoStmt(it as JGotoStmt) } + map.put(JInvokeStmt::class.java) { handleInvokeStmt(it as JInvokeStmt) } + map.put(JReturnStmt::class.java) { handleReturnStmt(it as JReturnStmt) } + map.put(JReturnVoidStmt::class.java) { handleReturnVoidStmt(it as JReturnVoidStmt) } + } + + private fun handleBody(body: Body): Block { + // The first block contains all our other blocks and this will be the one we return + val outerBlock = newBlock(rawNode = body) + + val printer = NormalStmtPrinter() + printer.initializeSootMethod(body.stmtGraph) + + frontend.printer = printer + frontend.body = body + + // Parse locals, these are always at the beginning of the function + for (local in body.locals) { + val decl = frontend.declarationHandler.handle(local) + + if (decl != null) { + // We need to wrap them into a declaration statement and put them into the outer + // block + val stmt = newDeclarationStatement(rawNode = local) + stmt.addToPropertyEdgeDeclaration(decl) + frontend.scopeManager.addDeclaration(decl) + outerBlock += stmt + } + } + + // Parse statements and segment them into (sub)-blocks. + var block = outerBlock + for (sootStmt in body.stmts) { + val label = printer.labels[sootStmt] + if (label != null) { + // If we have a label, we need to create a new label statement, that starts a new + // block + val stmt = newLabelStatement() + block = newBlock() + stmt.label = label + stmt.subStatement = block + + // We need to inform our processing system, since we do it outside of a handler, so + // the created goto statements will be informed about our new label + frontend.process(Any(), stmt) + + // Always add it to the outer block + outerBlock += stmt + } + + // Parse the statement + val stmt = handle(sootStmt) + if (stmt != null) { + block += stmt + } + } + + // Always return the outer block, since it comprises all the other sub-blocks. + return outerBlock + } + + private fun handleAbstractDefinitionStmt(defStmt: AbstractDefinitionStmt): AssignExpression { + val assign = newAssignExpression("=", rawNode = defStmt) + assign.lhs = listOfNotNull(frontend.expressionHandler.handle(defStmt.leftOp)) + assign.rhs = listOfNotNull(frontend.expressionHandler.handle(defStmt.rightOp)) + + return assign + } + + private fun handleIfStmt(ifStmt: JIfStmt): IfStatement { + val stmt = newIfStatement(rawNode = ifStmt) + stmt.condition = + frontend.expressionHandler.handle(ifStmt.condition) + ?: newProblemExpression("missing condition") + stmt.thenStatement = handleBranchingStmt(ifStmt) + + return stmt + } + + private fun handleGotoStmt(gotoStmt: JGotoStmt): GotoStatement { + return handleBranchingStmt(gotoStmt) + } + + private fun handleBranchingStmt(branchingStmt: BranchingStmt): GotoStatement { + val stmt = newGotoStatement(rawNode = branchingStmt) + + frontend.body?.let { + val target = branchingStmt.getTargetStmts(it).firstOrNull() + val label = frontend.printer?.labels?.get(target) + if (label != null) { + stmt.labelName = label + } + + // Register a predicate listener that informs us as soon as new label statement that + // matches our label name is created. + frontend.registerPredicateListener({ _, to -> + (to is LabelStatement && to.label == stmt.labelName) + }) { _, to -> + stmt.targetLabel = to as LabelStatement + } + } + + return stmt + } + + private fun handleInvokeStmt(invokeStmt: JInvokeStmt) = + frontend.expressionHandler.handle(invokeStmt.invokeExpr) + + private fun handleReturnStmt(returnStmt: JReturnStmt): ReturnStatement { + val stmt = newReturnStatement(rawNode = returnStmt) + stmt.returnValue = + frontend.expressionHandler.handle(returnStmt.op) + ?: newProblemExpression("missing return value") + + return stmt + } + + private fun handleReturnVoidStmt(returnStmt: JReturnVoidStmt) = + newReturnStatement(rawNode = returnStmt) +} diff --git a/cpg-language-jvm/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/jvm/JVMLanguageFrontendTest.kt b/cpg-language-jvm/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/jvm/JVMLanguageFrontendTest.kt new file mode 100644 index 0000000000..2577a63de9 --- /dev/null +++ b/cpg-language-jvm/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/jvm/JVMLanguageFrontendTest.kt @@ -0,0 +1,422 @@ +/* + * 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.jvm + +import de.fraunhofer.aisec.cpg.* +import de.fraunhofer.aisec.cpg.graph.* +import de.fraunhofer.aisec.cpg.graph.statements.expressions.* +import de.fraunhofer.aisec.cpg.graph.types.PointerType +import de.fraunhofer.aisec.cpg.passes.EdgeCachePass +import de.fraunhofer.aisec.cpg.passes.astParent +import de.fraunhofer.aisec.cpg.test.analyze +import de.fraunhofer.aisec.cpg.test.analyzeAndGetFirstTU +import de.fraunhofer.aisec.cpg.test.assertFullName +import de.fraunhofer.aisec.cpg.test.assertInvokes +import de.fraunhofer.aisec.cpg.test.assertLiteralValue +import de.fraunhofer.aisec.cpg.test.assertLocalName +import de.fraunhofer.aisec.cpg.test.assertRefersTo +import java.nio.file.Path +import kotlin.test.* +import org.junit.jupiter.api.Disabled + +class JVMLanguageFrontendTest { + @Test + fun testHelloJimple() { + val topLevel = Path.of("src", "test", "resources", "jimple", "helloworld") + val tu = + analyzeAndGetFirstTU( + listOf(topLevel.resolve("HelloWorld.jimple").toFile()), + topLevel, + true + ) { + it.registerLanguage() + } + assertNotNull(tu) + + val helloWorld = tu.records["HelloWorld"] + assertNotNull(helloWorld) + + val constructor = helloWorld.constructors.firstOrNull() + assertNotNull(constructor) + + // All references should be resolved (except Object., which should be a construct + // expression anyway) + val refs = constructor.refs.filter { it.name.toString() != "java.lang.Object." } + refs.forEach { + val refersTo = it.refersTo + assertNotNull(refersTo, "${it.name} could not be resolved") + assertFalse( + refersTo.isInferred, + "${it.name} should not be resolved to an inferred node" + ) + } + + val main = helloWorld.methods["main"] + assertNotNull(main) + assertTrue(main.isStatic) + + val param0 = main.refs["@parameter0"] + assertNotNull(param0) + + val refersTo = param0.refersTo + assertNotNull(refersTo) + assertFalse(refersTo.isInferred) + } + + @Test + fun testMethodsClass() { + // This will be our classpath + val topLevel = Path.of("src", "test", "resources", "class", "methods") + val tu = + analyzeAndGetFirstTU( + // We just need to specify one file to trigger the class byte loader + listOf(topLevel.resolve("mypackage/Adder.class").toFile()), + topLevel, + true + ) { + it.registerPass() + it.registerLanguage() + } + assertNotNull(tu) + assertEquals(0, tu.problems.size) + + val pkg = tu.namespaces["mypackage"] + assertNotNull(pkg) + + val adder = pkg.records["Adder"] + assertNotNull(adder) + + val add = adder.methods["add"] + assertNotNull(add) + + val main = pkg.methods["Main.main"] + assertNotNull(main) + + println(main.code) + + // r5 contains our adder + val r5 = main.variables["r5"] + assertNotNull(r5) + assertFullName("mypackage.Adder", r5.type) + + // r3 should be the result of the add call + val r3 = main.variables["r3"] + assertNotNull(r3) + + val r3ref = r3.usages.firstOrNull { it.access == AccessValues.WRITE } + assertNotNull(r3ref) + + // Call to add should be resolved + val call = r3ref.prevDFG.firstOrNull() + assertIs(call) + assertLocalName("add", call) + assertInvokes(call, add) + assertEquals(listOf("Integer", "Integer"), call.arguments.map { it.type.name.localName }) + + // All references (which are not part of a call) and not to the stdlib should be resolved + val refs = tu.refs + refs + .filter { it.astParent !is CallExpression } + .filter { !it.name.startsWith("java.") } + .forEach { + val refersTo = it.refersTo + assertNotNull(refersTo, "${it.name} could not be resolved") + assertFalse( + refersTo.isInferred, + "${it.name} should not be resolved to an inferred node" + ) + } + } + + @Test + fun testLiteralsClass() { + // This will be our classpath + val topLevel = Path.of("src", "test", "resources", "class", "literals") + val result = + analyze( + // We just need to specify one file to trigger the byte code loader + listOf(topLevel.resolve("mypackage/Literals.class").toFile()), + topLevel, + true + ) { + it.registerPass() + it.registerLanguage() + } + assertNotNull(result) + + result.methods.forEach { + println(it.name) + println(it.code) + } + + assertEquals(0, result.problems.size) + } + + @Test + fun testLiteralsJar() { + // This will be our classpath + val topLevel = Path.of("src", "test", "resources", "jar", "literals") + val tu = + analyzeAndGetFirstTU( + // In case of a jar, the jar is directly used as a class path + listOf(topLevel.resolve("literals.jar").toFile()), + topLevel, + true + ) { + it.registerPass() + it.registerLanguage() + } + assertNotNull(tu) + assertEquals(0, tu.problems.size) + tu.methods.forEach { println(it.code) } + } + + @Test + fun testInheritanceClass() { + // This will be our classpath + val topLevel = Path.of("src", "test", "resources", "class", "inheritance") + val tu = + analyzeAndGetFirstTU( + // In case of a jar, the jar is directly used as a class path + listOf(topLevel.resolve("mypackage/Application.class").toFile()), + topLevel, + true + ) { + it.registerPass() + it.registerLanguage() + } + assertNotNull(tu) + tu.methods.forEach { println(it.code) } + assertEquals(0, tu.problems.size) + + val myInterface = tu.records["mypackage.MyInterface"] + assertNotNull(myInterface) + assertEquals("interface", myInterface.kind) + + val baseClass = tu.records["mypackage.BaseClass"] + assertNotNull(baseClass) + + val extendedClass = tu.records["mypackage.ExtendedClass"] + assertNotNull(extendedClass) + assertContains(extendedClass.implementedInterfaces, myInterface.toType()) + assertContains(extendedClass.superTypeDeclarations, baseClass) + assertContains(extendedClass.superTypeDeclarations, myInterface) + + val anotherExtendedClass = tu.records["mypackage.AnotherExtendedClass"] + assertNotNull(anotherExtendedClass) + assertContains(anotherExtendedClass.superTypeDeclarations, baseClass) + + assertEquals( + baseClass.toType(), + listOf(extendedClass.toType(), anotherExtendedClass.toType()).commonType + ) + + val appInit = tu.methods["mypackage.Application."] + assertNotNull(appInit) + + val appDoSomething = tu.methods["mypackage.Application.doSomething"] + assertNotNull(appDoSomething) + assertLocalName("MyInterface", appDoSomething.parameters.firstOrNull()?.type) + + // Call doSomething in Application. with an object of ExtendedClass, which should + // fulfill the MyInterface of the needed parameter + val doSomethingCall1 = appInit.calls["doSomething"] + assertNotNull(doSomethingCall1) + assertLocalName("ExtendedClass", doSomethingCall1.arguments.firstOrNull()?.type) + assertInvokes(doSomethingCall1, appDoSomething) + + val extended = appInit.variables["r4"] + assertNotNull(extended) + + val getMyProperty = + appInit.calls[ + { + it.name.localName == "getMyProperty" && + it is MemberCallExpression && + it.base in extended.usages + }] + assertNotNull(getMyProperty) + assertInvokes(getMyProperty, baseClass.methods["getMyProperty"]) + + val setMyProperty = + appInit.calls[ + { + it.name.localName == "setMyProperty" && + it is MemberCallExpression && + it.base in extended.usages + }] + assertNotNull(setMyProperty) + assertInvokes(setMyProperty, extendedClass.methods["setMyProperty"]) + } + + @Test + fun testFieldsClass() { + // This will be our classpath + val topLevel = Path.of("src", "test", "resources", "class", "fields") + val tu = + analyzeAndGetFirstTU( + // We just need to specify one file to trigger the byte code loader + listOf(topLevel.resolve("mypackage/Fields.class").toFile()), + topLevel, + true + ) { + it.registerPass() + it.registerLanguage() + } + assertNotNull(tu) + assertEquals(0, tu.problems.size) + tu.methods.forEach { println(it.code) } + + val refs = tu.refs.filterIsInstance() + refs.forEach { + val refersTo = it.refersTo + assertNotNull(refersTo, "${it.name} could not be resolved") + assertFalse( + refersTo.isInferred, + "${it.name} should not be resolved to an inferred node" + ) + } + + val setACall = tu.calls["setA"] + assertNotNull(setACall) + + val lit10 = setACall.arguments.firstOrNull() + assertIs>(lit10) + assertLiteralValue(10, lit10) + } + + @Disabled + @Test + fun testLiteralsSource() { + // This will be our classpath + val topLevel = Path.of("src", "test", "resources", "class", "literals") + val tu = + analyzeAndGetFirstTU( + // We just need to specify one file to trigger the source code loader + listOf(topLevel.resolve("mypackage/Literals.java").toFile()), + topLevel, + true + ) { + it.registerPass() + it.registerLanguage() + } + assertNotNull(tu) + + val haveFun = tu.methods["haveFunWithLiterals"] + assertNotNull(haveFun) + + println(haveFun.code) + } + + @Test + fun testArraysClass() { + // This will be our classpath + val topLevel = Path.of("src", "test", "resources", "class", "arrays") + val tu = + analyzeAndGetFirstTU( + // We just need to specify one file to trigger the class byte loader + listOf(topLevel.resolve("mypackage/Arrays.class").toFile()), + topLevel, + true + ) { + it.registerPass() + it.registerLanguage() + } + assertNotNull(tu) + tu.methods.forEach { println(it.code) } + assertEquals(0, tu.problems.size) + + val create = tu.methods["create"] + assertNotNull(create) + + val r3 = create.variables["r3"] + assertNotNull(r3) + + var arrayType = r3.type + assertIs(arrayType) + assertTrue(arrayType.isArray) + assertFullName("mypackage.Element", arrayType.elementType) + + val r3write = r3.usages.firstOrNull { it.access == AccessValues.WRITE } + assertNotNull(r3write) + + var expr = r3write.prevDFG.singleOrNull() + assertIs(expr) + assertLiteralValue(2, expr.dimensions.singleOrNull()) + + var r1 = create.variables["r1"] + assertNotNull(r1) + assertEquals(arrayType.elementType, r1.type) + + val r2 = create.variables["r2"] + assertNotNull(r2) + assertEquals(arrayType.elementType, r2.type) + + val r2write = r2.usages.firstOrNull { it.access == AccessValues.WRITE } + assertNotNull(r2write) + + val prevDFG = r2write.prevDFG.singleOrNull() + assertIs(prevDFG) + assertRefersTo(prevDFG.arrayExpression, r3) + + val createMulti = tu.methods["createMulti"] + assertNotNull(createMulti) + + r1 = createMulti.variables["r1"] + assertNotNull(r1) + + arrayType = r1.type + assertIs(arrayType) + assertTrue(arrayType.isArray) + assertFullName("mypackage.Element", arrayType.elementType) + + val r1write = r1.usages.firstOrNull { it.access == AccessValues.WRITE } + assertNotNull(r1write) + + expr = r1write.prevDFG.singleOrNull() + assertIs(expr) + listOf(2, 10).forEachIndexed { index, i -> assertLiteralValue(i, expr.dimensions[index]) } + } + + @Disabled + @Test + fun testExceptional() { + // This will be our classpath + val topLevel = Path.of("src", "test", "resources", "class", "exception") + val tu = + analyzeAndGetFirstTU( + // We just need to specify one file to trigger the class byte loader + listOf(topLevel.resolve("mypackage/Exceptional.class").toFile()), + topLevel, + true + ) { + it.registerPass() + it.registerLanguage() + } + assertNotNull(tu) + tu.methods.forEach { println(it.code) } + } +} diff --git a/cpg-language-jvm/src/test/resources/class/arrays/mypackage/Arrays.class b/cpg-language-jvm/src/test/resources/class/arrays/mypackage/Arrays.class new file mode 100644 index 0000000000000000000000000000000000000000..194c12155ea34323cbbb18dbdcbb73cf46d1a608 GIT binary patch literal 457 zcmZuuO-sW-5Pg%6rf#cEtF0n<@FJ}N3tqK?ik_k#DuVR1iAzaMQ;CVtf2F5_VsBpj zQQ|BS6zm>mW@p}eGqd~o^*RU8LrcJb>AA%LTC;K{=f2pgJstK%^# zm-#qQ>9q`E&XmGb$&7`>d7MRrb%rg*iT)%{LvfyHEC(|(8Jd*&OQ+ZmSnp5>YQeQ)0L zp1E4^Tiimc*!)&Cl+lF129H2!VD(3DhX>Ll3ELGP5QhJUuYU?~nb~0U8pj5j-({m! Ar2qf` literal 0 HcmV?d00001 diff --git a/cpg-language-jvm/src/test/resources/class/arrays/mypackage/Arrays.java b/cpg-language-jvm/src/test/resources/class/arrays/mypackage/Arrays.java new file mode 100644 index 0000000000..e91aeb1999 --- /dev/null +++ b/cpg-language-jvm/src/test/resources/class/arrays/mypackage/Arrays.java @@ -0,0 +1,21 @@ +package mypackage; + +public class Arrays { + + public Element[] create() { + var arrays = new Element[2]; + arrays[0] = new Element(); + arrays[1] = arrays[0]; + + int len = arrays.length; + + return arrays; + } + + public Element[][] createMulti() { + var multi = new Element[2][10]; + + return multi; + } + +} \ No newline at end of file diff --git a/cpg-language-jvm/src/test/resources/class/arrays/mypackage/Element.class b/cpg-language-jvm/src/test/resources/class/arrays/mypackage/Element.class new file mode 100644 index 0000000000000000000000000000000000000000..80ab8317cc60f307fe93f56992aa73f4efe64ff0 GIT binary patch literal 198 zcmX^0Z`VEs1_oOOUM>bE24;2!79Ivx1~x_pfvm)`ME#t^ymWp4q^#8B5=I6#o6Nk- z5<5l)W)00Sb_Nbc2Ep9Qg2d$P#Pn2s*PPVc)I6Xn7U%qwR7M7VpUk{eztY^K)S{5Y zq#U3KS8#r5QF5wVCWylWRj&uKhCz{m9cViUFajaaejv#S{c?>h>oVj1$AD;k*I0|6F_ThwZ;R&L-dR9rH%2{%k&UID`yvw}I>nnlXA50q9 zLBmH7qKT07#mke*9@R`Iqg)r$l|U5VI3TYbqGM)ym0sz3XrOM09(Dze0hs#Z!G!60 zIn{#BONjM2-Niui< literal 0 HcmV?d00001 diff --git a/cpg-language-jvm/src/test/resources/class/fields/mypackage/Fields.java b/cpg-language-jvm/src/test/resources/class/fields/mypackage/Fields.java new file mode 100644 index 0000000000..3a7fed1211 --- /dev/null +++ b/cpg-language-jvm/src/test/resources/class/fields/mypackage/Fields.java @@ -0,0 +1,18 @@ +package mypackage; + +public class Fields { + + private int a = 2; + + Fields() { + resetA(); + } + + public void setA(int a) { + this.a = a; + } + + private void resetA() { + setA(10); + } +} \ No newline at end of file diff --git a/cpg-language-jvm/src/test/resources/class/inheritance/mypackage/AnotherExtendedClass.class b/cpg-language-jvm/src/test/resources/class/inheritance/mypackage/AnotherExtendedClass.class new file mode 100644 index 0000000000000000000000000000000000000000..144452d835cb35333308a5acc3785bca43ebdb37 GIT binary patch literal 227 zcmX^0Z`VEs1_oOOUM>bE24;2!79Ivx1~x_p;oQoC#N_P6^i+MP#Nt%voW$Z{Mg}&U z%)HDJJ4Oa(4b3oi1`b9BIfOdLy!?`k)FRi4lGMDE)D)-|7U%qwR7M7VpUk{eztY^K z)S{5Yq#U3KS8#r5QF5wVCWs@6O|xEBVp$@CA_F_nX&}G|gh0muNlqY77R+a0VAa~r Yz_<}C%>^Vu(hLmDK$06sGcoW00O0X4cK`qY literal 0 HcmV?d00001 diff --git a/cpg-language-jvm/src/test/resources/class/inheritance/mypackage/AnotherExtendedClass.java b/cpg-language-jvm/src/test/resources/class/inheritance/mypackage/AnotherExtendedClass.java new file mode 100644 index 0000000000..06fea507f4 --- /dev/null +++ b/cpg-language-jvm/src/test/resources/class/inheritance/mypackage/AnotherExtendedClass.java @@ -0,0 +1,4 @@ +package mypackage; + +public class AnotherExtendedClass extends BaseClass { +} \ No newline at end of file diff --git a/cpg-language-jvm/src/test/resources/class/inheritance/mypackage/Application.class b/cpg-language-jvm/src/test/resources/class/inheritance/mypackage/Application.class new file mode 100644 index 0000000000000000000000000000000000000000..0104bfda3e327c705b89adf73eddba58f8a0cf6e GIT binary patch literal 864 zcmZuwZEq4m5Pr7MF1pTR&8S9SAUaHXHOESF_+BE&dfeD^UUmzUthlg*u;i`5rjCx7Dh3~5I+{LM6o5D zM)B#IDcT?T=ZAW0L&-d4D)t${iX=Vu~YW;R4bOsX?;sPN!wpgtA?S!K}Mg zw=GrEb{Y(`+43N+(knSidM`vxZfLw2%yL|`a0!pnw&QqJ^7SrIi1<#lSlnqea}LvVU=dIT9#QL`0al zh1(o=G_f@5A!3IJV+ilMb;%Gb+m77twvVLuOdPey60ItldPQ{lXQS$Ny_(#!wKG06 zoE2Rj=4p;2v}7R3^mdWFOQ#PA(+50RJ_S1ie}0Pba{dIyWI3 zzyBWQ88Q?!w_niy`NDVbw=h3pDY1Nl8y|xN>m=7`=@5XAk~W4(+O#wxGS6ot=C(xgq3wpv|@3tdQ?g?Rz%!h%rn50vgFaR{mTDM>2io!kf&!G#at zLy31nDGF}p-nsLgbMAb7e|!R%VBdxY-Gbqu1(PsXh-cv^B8&XnU?IbbV4lTUT%8m2 z(RkiL8yyR_gDxDxFs+v&d=ioLFGMM)i6~2goz{0nzLZ5(6KF;-rum~J^snMf-mKC< z77rpwc;ZH~y8Z=o1mkY`cWR7g<2l>j=c^)=m$6dzwt1&&WgPKh8h?qy)N0)6-1DFK zboq9|uf)~%-a%gb4c2Qz_tdOW>a28`>0_|b9CB5fmbZrWvc;7p8si=8ZqQ@4Q1PaB Q@Q=04i*%VBGNEDb2dOGO{r~^~ literal 0 HcmV?d00001 diff --git a/cpg-language-jvm/src/test/resources/class/inheritance/mypackage/BaseClass.java b/cpg-language-jvm/src/test/resources/class/inheritance/mypackage/BaseClass.java new file mode 100644 index 0000000000..3962a24f17 --- /dev/null +++ b/cpg-language-jvm/src/test/resources/class/inheritance/mypackage/BaseClass.java @@ -0,0 +1,14 @@ +package mypackage; + +public class BaseClass { + + public int getMyProperty() { + return myProperty; + } + + public void setMyProperty(int myProperty) { + this.myProperty = myProperty; + } + + protected int myProperty = 5; +} \ No newline at end of file diff --git a/cpg-language-jvm/src/test/resources/class/inheritance/mypackage/ExtendedClass.class b/cpg-language-jvm/src/test/resources/class/inheritance/mypackage/ExtendedClass.class new file mode 100644 index 0000000000000000000000000000000000000000..21739710f47a87b5da02fef285b03315a21547d9 GIT binary patch literal 1100 zcmaJ=T~8B16g^W4OP8fZ5D*KxC~8}k_4}*p1C7Kcpd=;m;M24_(51UG&F+-gf8yWJ z7)W^V2l%6mcW7fvN_?2PGjs2`bMBeBzyF;50`LxH8!3!gNIMwE1jAg^ZSzKp9|~`S zC!!MaBw?6%8^}PvXBaCK_cEA7#)9o&3J$~K0P*3g7Sb1fA7w|~omjO+th)@XY7;K& zAcq--Y#dGhgc%fP??in((4(4!`VLB0ddniG=Kmi#nV9~-g2TQolFmuF@ zxfgPI=+(N377;O29nCPmb?yn2w-X0a*K{m+RGy`hmmRF&hM~<{qM~Gj>-|7CX_aU$ zH5I#HsPyo?%$iahl29&IE!=W&8*3!gB^NMQZF5~Hi9umBMRCuWkV^Q=$N;+ z38efaH-U8<9v)eEZ0di)ur`n`kjJVeJQ9iW_axcm4Xt8&mv6&m^aFcIKXU1ZB3au~ zs?}+iN2?t=xd7blw#6mkUxwm8*N;R)mxo`WpQ6IUuoh@hp2RZ-cVD=TCOyCKUELJ! zF%LV!J&0B0GFK~?F*sEzMST7vMPlJ4!<~`JFDuBxYla*D52eQFRgYEr+bK|P`eA9Z z)JrtqqumNwMs~b(3ih2A3_6O$k%V)>~C%8Gln$`krOjFk~6#wyCqEka$cFDj a8Gs6b7BVm}0?lM)U<0uj*nuPy0|x*baUqQW literal 0 HcmV?d00001 diff --git a/cpg-language-jvm/src/test/resources/class/inheritance/mypackage/MyInterface.java b/cpg-language-jvm/src/test/resources/class/inheritance/mypackage/MyInterface.java new file mode 100644 index 0000000000..0c08afdd7d --- /dev/null +++ b/cpg-language-jvm/src/test/resources/class/inheritance/mypackage/MyInterface.java @@ -0,0 +1,7 @@ +package mypackage; + +public interface MyInterface { + + public void doSomething(); + +} \ No newline at end of file diff --git a/cpg-language-jvm/src/test/resources/class/literals/mypackage/Literals.class b/cpg-language-jvm/src/test/resources/class/literals/mypackage/Literals.class new file mode 100644 index 0000000000000000000000000000000000000000..03362eb916054356c780afcc35c41add0953c702 GIT binary patch literal 1524 zcmbVM+foxj5IvLIB%2KhAzpwIHG(EViPxZ6LC@e(yQjbZ+WQ9JF zjvEwlT~apgWy&Yjw4OoN(T)73Yt|gyGV@ip*)()%YYNiHs>orIA>hasnYohbQuqfV zyB5VQOmXBr=eHR~JA_ZyW=LiF8FdS%k*@`7Lo&q6x+z!O#=5j$i*UC2%u1$6xoXKM5lq$_?*);~G(LPKL!?|O@ z_XRUs$#>De^tLB zK*I#6#1>QFrDUWSNGOHiAm1;9Qix7-e<8AqSPDb$-Xp?_$_IpUMP<-`u#0oWXbPjd zxKvb=>0&IG+XIc7&&YT#eI1vxh|@kyUlDSt&=VzhDuxilMcR!bfeD&olO&m<`BcCK z%+g4i#|X-FdY{mv@ckx*Fh|-)L-YY-0pjr!J`&I|U3fC@(HeJ$7M{@&WDGzQn3i(A^#>UK&(EJpg&=0_@Q+`Gc?OV(KCwCk7rmy3Cko=Nb(XD HtYY{Ns clazz = Literals.class; + test(this::mySupplier); + } + + void test(Supplier s) { + s.get(); + } + + Integer mySupplier() { + return 1; + } + +} \ No newline at end of file diff --git a/cpg-language-jvm/src/test/resources/class/methods/mypackage/Adder.class b/cpg-language-jvm/src/test/resources/class/methods/mypackage/Adder.class new file mode 100644 index 0000000000000000000000000000000000000000..9493eaf6a0bfd3c5e30291187d769197a5a676d3 GIT binary patch literal 408 zcmZut%Syvg5IvJV5@TvwA5|AFTnMR(`GAU01R+rDLh0I@7a!+p*vGc|QAuc@r+Hdl5E|ZK zYQw^s3EROs976ARJjhF(=z?ISc{x>CrB{qV7#j{;bP48C@X0-)?*)VLKl0GTCc&LQ zJ*wzICE6dwu}C&A7ctAZ<22XT)jZV2tqL<)pyHS?^lGGPpt_9&`({xUk-kbr&|Ymk zlb_)bJwA>+10?5Rzs1!7N8;EzegnO5LfB?6Z3fwP(C1u6kL0=>J14I&PucF?(RpUz MFU8_&moWo-KWKkci~s-t literal 0 HcmV?d00001 diff --git a/cpg-language-jvm/src/test/resources/class/methods/mypackage/Adder.java b/cpg-language-jvm/src/test/resources/class/methods/mypackage/Adder.java new file mode 100644 index 0000000000..954e22c6a4 --- /dev/null +++ b/cpg-language-jvm/src/test/resources/class/methods/mypackage/Adder.java @@ -0,0 +1,11 @@ +package mypackage; + +import java.lang.Integer; + +public class Adder { + + Integer add(Integer a, Integer b) { + return a + b; + } + +} \ No newline at end of file diff --git a/cpg-language-jvm/src/test/resources/class/methods/mypackage/Main.class b/cpg-language-jvm/src/test/resources/class/methods/mypackage/Main.class new file mode 100644 index 0000000000000000000000000000000000000000..4d448cfe92f584d2327b4e249de234915f364baa GIT binary patch literal 606 zcmZvZ>q-Ja6vzMLuC2RmUem5tcHJ)I0~mn>fuI=}LG)>KCt2HFWm^Rwt3Ff|^Z-3n zbjCiYSd0Q0Awk-k!8WaQbFf}Zgh$F#}8iOi+DB2=m&<5P?i~0jY&Z-<% z|0zlmlroV87ot%HHJ^2@aT_jpAyXe@tkh)I&l23 z5e9;L6w4acHRMffV3Q#;igsE<8TI4~*C)tipns(+$#NAK;^T7FxI*(j{<{BKL=j-;__snS@Z(Y5MyxzK6=gyqp9At3C_`%a6JuhD!Pv48BtF{CgFm=6< z)XJ!M@_d<^=+bG~pNc*fF#@e(=lI9;r^pg$umun!tS(=sgyKpGplohsL1J=tVtT5+ zPi9GKQDRQ9UUE)iadB#pZ~kQmf!bv2dq+0T*559&h)H*gie^@xV^N^yqE#YOE;$R# zF@Jfj&~whIQl+oc6+W^*xFsyeUdOQfU(Y`dmY!IJkTqM3cR&7qukYE<+x`0Sj5f!U zxur@N54*9cEv!&lzR7aR!uvLF=B}voF__PmDDYn3OVr=qozX74Hf`XzdGTkY4C})A zJN3G}xw$nsL~^GDsOg+}%9mo3u+gK<{c)hyw)A&)d!9u`9MWw}mEK<19x!Y3t;97o zqJl?4_|#v0n6V)w`?~di#clQ57sN;I=&ok^F1+!ov~J0yw$RghZ?E7!@2gYbO6LX1TDH|B;mv-*MmV*NJEPpfu4lG4<^^Mh1q7OrSKu$i&5f zJM97!1`>cLPe@8;WD;ROq-Er!21?5apbccgwIZc#kmWEFKuHqWVW1?50Ly?(TnRM5 Uo0Scuk{JjO1L<8rZ!s_c08m;#$N&HU literal 0 HcmV?d00001 diff --git a/cpg-language-jvm/src/test/resources/jimple/helloworld/HelloWorld.jimple b/cpg-language-jvm/src/test/resources/jimple/helloworld/HelloWorld.jimple new file mode 100644 index 0000000000..256700b174 --- /dev/null +++ b/cpg-language-jvm/src/test/resources/jimple/helloworld/HelloWorld.jimple @@ -0,0 +1,22 @@ +public class HelloWorld extends java.lang.Object +{ + public void () + { + HelloWorld r0; + r0 := @this: HelloWorld; + specialinvoke r0.()>(); + return; + } + + public static void main(java.lang.String[]) + { + java.lang.String[] r0; + java.io.PrintStream $r1; + + r0 := @parameter0: java.lang.String[]; + $r1 = ; + virtualinvoke $r1.("Hello world!"); + return; + } +} diff --git a/cpg-language-jvm/src/test/resources/log4j2.xml b/cpg-language-jvm/src/test/resources/log4j2.xml new file mode 100644 index 0000000000..5b73082e2c --- /dev/null +++ b/cpg-language-jvm/src/test/resources/log4j2.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/gradle.properties.example b/gradle.properties.example index c9e3c651dc..f956f34730 100644 --- a/gradle.properties.example +++ b/gradle.properties.example @@ -7,4 +7,5 @@ enableGoFrontend=true enablePythonFrontend=true enableLLVMFrontend=true enableTypeScriptFrontend=true -enableRubyFrontend=true \ No newline at end of file +enableRubyFrontend=true +enableJVMFrontend=true diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6f8e702b41..e6fc0df097 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,6 +6,7 @@ log4j = "2.23.0" sonarqube = "5.1.0.4882" spotless = "6.25.0" nexus-publish = "2.0.0" +sootup = "1.2.0" [libraries] kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin"} @@ -56,6 +57,12 @@ sonarqube-gradle = { module = "org.sonarsource.scanner.gradle:sonarqube-gradle-p 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" } +sootup-core = { module = "org.soot-oss:sootup.core", version.ref = "sootup" } +sootup-java-core = { module = "org.soot-oss:sootup.java.core", version.ref = "sootup" } +sootup-java-sourcecode = { module = "org.soot-oss:sootup.java.sourcecode", version.ref = "sootup" } +sootup-java-bytecode = { module = "org.soot-oss:sootup.java.bytecode", version.ref = "sootup" } +sootup-jimple-parser = { module = "org.soot-oss:sootup.jimple.parser", version.ref = "sootup" } + [bundles] log4j = ["log4j-impl", "log4j-core"] neo4j = ["neo4j-ogm-core", "neo4j-ogm-bolt-driver"] @@ -66,6 +73,7 @@ kotlin-scripting = [ "kotlin-scripting-dependencies", "kotlin-scripting-dependencies-maven-all", ] +sootup = ["sootup-core", "sootup-java-core", "sootup-java-sourcecode", "sootup-java-bytecode", "sootup-jimple-parser"] [plugins] kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin"} diff --git a/settings.gradle.kts b/settings.gradle.kts index b7a66ec8a1..afe927cdee 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -37,6 +37,10 @@ val enableRubyFrontend: Boolean by extra { val enableRubyFrontend: String? by settings enableRubyFrontend.toBoolean() } +val enableJVMFrontend: Boolean by extra { + val enableJVMFrontend: String? by settings + enableJVMFrontend.toBoolean() +} if (enableJavaFrontend) include(":cpg-language-java") if (enableCXXFrontend) include(":cpg-language-cxx") @@ -44,4 +48,5 @@ if (enableGoFrontend) include(":cpg-language-go") if (enableLLVMFrontend) include(":cpg-language-llvm") if (enablePythonFrontend) include(":cpg-language-python") if (enableTypeScriptFrontend) include(":cpg-language-typescript") -if (enableRubyFrontend) include(":cpg-language-ruby") \ No newline at end of file +if (enableRubyFrontend) include(":cpg-language-ruby") +if (enableJVMFrontend) include(":cpg-language-jvm") From 4e106c0b54cc3f0c2f5e2a3dcd0e2b38e95a6b12 Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Thu, 13 Jun 2024 16:03:28 +0200 Subject: [PATCH 2/9] Using sootup 1.3.0 --- cpg-language-jvm/build.gradle.kts | 12 ++++++------ .../aisec/cpg/frontends/jvm/JVMLanguageFrontend.kt | 2 +- gradle/libs.versions.toml | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/cpg-language-jvm/build.gradle.kts b/cpg-language-jvm/build.gradle.kts index e809f20033..6502dfa23a 100644 --- a/cpg-language-jvm/build.gradle.kts +++ b/cpg-language-jvm/build.gradle.kts @@ -39,12 +39,12 @@ publishing { } } -tasks.withType().configureEach { - kotlinOptions { - freeCompilerArgs = listOf("-Xcontext-receivers") - } -} - dependencies { api(libs.bundles.sootup) + // needed until https://github.com/antlr/antlr4/issues/3895 is fixed + runtimeOnly("org.antlr:antlr4-runtime") { + version { + strictly("4.9.3") + } + } } diff --git a/cpg-language-jvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/jvm/JVMLanguageFrontend.kt b/cpg-language-jvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/jvm/JVMLanguageFrontend.kt index 26f111e65e..9b561079a7 100644 --- a/cpg-language-jvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/jvm/JVMLanguageFrontend.kt +++ b/cpg-language-jvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/jvm/JVMLanguageFrontend.kt @@ -42,7 +42,7 @@ import sootup.core.types.ArrayType import sootup.core.types.UnknownType import sootup.core.util.printer.NormalStmtPrinter import sootup.java.bytecode.inputlocation.JavaClassPathAnalysisInputLocation -import sootup.java.bytecode.interceptors.* +import sootup.java.core.interceptors.* import sootup.java.core.views.JavaView import sootup.java.sourcecode.inputlocation.JavaSourcePathAnalysisInputLocation import sootup.jimple.parser.JimpleAnalysisInputLocation diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e6fc0df097..f93c693ba2 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,7 +6,7 @@ log4j = "2.23.0" sonarqube = "5.1.0.4882" spotless = "6.25.0" nexus-publish = "2.0.0" -sootup = "1.2.0" +sootup = "1.3.0" [libraries] kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin"} From ed33115559dc9f147724681d58ba1c003c4f4de1 Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Wed, 3 Jul 2024 13:01:02 +0200 Subject: [PATCH 3/9] Added JVM language to frontend --- README.md | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 88a62bbd45..642db8de34 100644 --- a/README.md +++ b/README.md @@ -128,15 +128,16 @@ Languages are maintained to different degrees, and are noted in the table below The current state of languages is: -| Language | Module | Branch | State | -|---|---|---|---| -| Java | cpg-language-java | [main](https://github.com/Fraunhofer-AISEC/cpg) | `maintained` | -| C++ | cpg-language-cxx | [main](https://github.com/Fraunhofer-AISEC/cpg) | `maintained` | -| Python | cpg-language-python | [main](https://github.com/Fraunhofer-AISEC/cpg) | `maintained` | -| Go | cpg-language-go | [main](https://github.com/Fraunhofer-AISEC/cpg) | `maintained` | -| LLVM | cpg-language-llvm | [main](https://github.com/Fraunhofer-AISEC/cpg) | `incubating` | -| TypeScript/JavaScript | cpg-language-typescript | [main](https://github.com/Fraunhofer-AISEC/cpg) | `experimental` | -| Ruby | cpg-language-ruby | [main](https://github.com/Fraunhofer-AISEC/cpg) | `experimental` | +| Language | Module | Branch | State | +| ------------------------ | ------------------------------------- | ----------------------------------------------------------------------- | -------------- | +| Java (Source) | cpg-language-java | [main](https://github.com/Fraunhofer-AISEC/cpg) | `maintained` | +| C++ | cpg-language-cxx | [main](https://github.com/Fraunhofer-AISEC/cpg) | `maintained` | +| Python | cpg-language-python | [main](https://github.com/Fraunhofer-AISEC/cpg) | `maintained` | +| Go | cpg-language-go | [main](https://github.com/Fraunhofer-AISEC/cpg) | `maintained` | +| JVM (Bytecode) | cpg-language-jvm | [main](https://github.com/Fraunhofer-AISEC/cpg) | `maintained` | +| LLVM | cpg-language-llvm | [main](https://github.com/Fraunhofer-AISEC/cpg) | `incubating` | +| TypeScript/JavaScript | cpg-language-typescript | [main](https://github.com/Fraunhofer-AISEC/cpg) | `experimental` | +| Ruby | cpg-language-ruby | [main](https://github.com/Fraunhofer-AISEC/cpg) | `experimental` | | {OpenQASM,Python-Qiskit} | cpg-language-{openqasm,python-qiskit} | [quantum-cpg](https://github.com/Fraunhofer-AISEC/cpg/tree/quantum-cpg) | `experimental` | ### Languages and Configuration From 99197760cf815e279b5c655d4ea2c38442d23e15 Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Wed, 3 Jul 2024 13:01:24 +0200 Subject: [PATCH 4/9] ++ --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 642db8de34..6ffbec8b9f 100644 --- a/README.md +++ b/README.md @@ -129,7 +129,7 @@ Languages are maintained to different degrees, and are noted in the table below The current state of languages is: | Language | Module | Branch | State | -| ------------------------ | ------------------------------------- | ----------------------------------------------------------------------- | -------------- | +|--------------------------|---------------------------------------|-------------------------------------------------------------------------|----------------| | Java (Source) | cpg-language-java | [main](https://github.com/Fraunhofer-AISEC/cpg) | `maintained` | | C++ | cpg-language-cxx | [main](https://github.com/Fraunhofer-AISEC/cpg) | `maintained` | | Python | cpg-language-python | [main](https://github.com/Fraunhofer-AISEC/cpg) | `maintained` | From 377654f63a4ee45940f0c3c4145c257c9dd98d5a Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Wed, 3 Jul 2024 13:01:46 +0200 Subject: [PATCH 5/9] set to incubating --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6ffbec8b9f..a8a75b8245 100644 --- a/README.md +++ b/README.md @@ -134,7 +134,7 @@ The current state of languages is: | C++ | cpg-language-cxx | [main](https://github.com/Fraunhofer-AISEC/cpg) | `maintained` | | Python | cpg-language-python | [main](https://github.com/Fraunhofer-AISEC/cpg) | `maintained` | | Go | cpg-language-go | [main](https://github.com/Fraunhofer-AISEC/cpg) | `maintained` | -| JVM (Bytecode) | cpg-language-jvm | [main](https://github.com/Fraunhofer-AISEC/cpg) | `maintained` | +| JVM (Bytecode) | cpg-language-jvm | [main](https://github.com/Fraunhofer-AISEC/cpg) | `incubating` | | LLVM | cpg-language-llvm | [main](https://github.com/Fraunhofer-AISEC/cpg) | `incubating` | | TypeScript/JavaScript | cpg-language-typescript | [main](https://github.com/Fraunhofer-AISEC/cpg) | `experimental` | | Ruby | cpg-language-ruby | [main](https://github.com/Fraunhofer-AISEC/cpg) | `experimental` | From d143468b6602a1f903f5fa868894b6f0c161402e Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Thu, 18 Jul 2024 08:58:44 +0200 Subject: [PATCH 6/9] Added fallback --- .../fraunhofer/aisec/cpg/frontends/jvm/ExpressionHandler.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cpg-language-jvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/jvm/ExpressionHandler.kt b/cpg-language-jvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/jvm/ExpressionHandler.kt index 33b8911575..082141e980 100644 --- a/cpg-language-jvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/jvm/ExpressionHandler.kt +++ b/cpg-language-jvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/jvm/ExpressionHandler.kt @@ -100,9 +100,13 @@ class ExpressionHandler(frontend: JVMLanguageFrontend) : map.put(JUshrExpr::class.java) { handleAbstractBinopExpr(it as AbstractBinopExpr) } map.put(JXorExpr::class.java) { handleAbstractBinopExpr(it as AbstractBinopExpr) } + // Fallback, just to be sure + map.put(AbstractBinopExpr::class.java) { handleAbstractBinopExpr(it) } + // Unary operator map.put(JNegExpr::class.java) { handleNegExpr(it as JNegExpr) } + // Special operators, which we need to model as binary/unary operators map.put(JInstanceOfExpr::class.java) { handleInstanceOfExpr(it as JInstanceOfExpr) } map.put(JLengthExpr::class.java) { handleLengthExpr(it as JLengthExpr) } From 6668f87e249c36407098bdab43c14e88493fb7f5 Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Thu, 18 Jul 2024 09:04:06 +0200 Subject: [PATCH 7/9] Removed extra line --- .../de/fraunhofer/aisec/cpg/frontends/jvm/ExpressionHandler.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/cpg-language-jvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/jvm/ExpressionHandler.kt b/cpg-language-jvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/jvm/ExpressionHandler.kt index 082141e980..6ec2163e0e 100644 --- a/cpg-language-jvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/jvm/ExpressionHandler.kt +++ b/cpg-language-jvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/jvm/ExpressionHandler.kt @@ -106,7 +106,6 @@ class ExpressionHandler(frontend: JVMLanguageFrontend) : // Unary operator map.put(JNegExpr::class.java) { handleNegExpr(it as JNegExpr) } - // Special operators, which we need to model as binary/unary operators map.put(JInstanceOfExpr::class.java) { handleInstanceOfExpr(it as JInstanceOfExpr) } map.put(JLengthExpr::class.java) { handleLengthExpr(it as JLengthExpr) } From a35b2341cbbf9a224943195cd841d07f6c5a255c Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Thu, 18 Jul 2024 10:44:51 +0200 Subject: [PATCH 8/9] Fixed compile error --- .../de/fraunhofer/aisec/cpg/frontends/jvm/ExpressionHandler.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpg-language-jvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/jvm/ExpressionHandler.kt b/cpg-language-jvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/jvm/ExpressionHandler.kt index 6ec2163e0e..1560b03cf3 100644 --- a/cpg-language-jvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/jvm/ExpressionHandler.kt +++ b/cpg-language-jvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/jvm/ExpressionHandler.kt @@ -101,7 +101,7 @@ class ExpressionHandler(frontend: JVMLanguageFrontend) : map.put(JXorExpr::class.java) { handleAbstractBinopExpr(it as AbstractBinopExpr) } // Fallback, just to be sure - map.put(AbstractBinopExpr::class.java) { handleAbstractBinopExpr(it) } + map.put(AbstractBinopExpr::class.java) { handleAbstractBinopExpr(it as AbstractBinopExpr) } // Unary operator map.put(JNegExpr::class.java) { handleNegExpr(it as JNegExpr) } From 90fe9ecd30bf4adff1e8ecca8afe3232320ddd4b Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Thu, 18 Jul 2024 14:31:34 +0200 Subject: [PATCH 9/9] warning for multiple language matches --- .../de/fraunhofer/aisec/cpg/TranslationManager.kt | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationManager.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TranslationManager.kt index 9eb4d07da5..199f03fe91 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 @@ -423,9 +423,21 @@ private constructor( } else null } + /** + * This extension function returns an appropriate [Language] for this [File] based on the + * registered file extensions of [TranslationConfiguration.languages]. It will emit a warning if + * multiple languages are registered for the same extension (and the first one that was + * registered will be returned). + */ private val File.language: Language<*>? get() { - return config.languages.firstOrNull { it.handlesFile(this) } + val languages = config.languages.filter { it.handlesFile(this) } + if (languages.size > 1) { + log.warn( + "Multiple languages match for file extension ${this.extension}, the first registered language will be used." + ) + } + return languages.firstOrNull() } class Builder {