diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml new file mode 100644 index 000000000..20f3e4445 --- /dev/null +++ b/.github/workflows/coverage.yml @@ -0,0 +1,74 @@ +name: Generate coverage report + +on: + push: + branches: [ develop, main ] + pull_request: + branches: [ main ] + workflow_dispatch: + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Set up JDK 11 + uses: actions/setup-java@v3 + with: + java-version: '11' + distribution: 'temurin' + - uses: gradle/gradle-build-action@v2 + with: + gradle-version: 7.6.1 + - name: Build and run tests + run: | + gradle clean build --no-daemon --info + + - name: Generate JaCoCo badge + id: jacoco + uses: cicirello/jacoco-badge-generator@v2 + with: + badges-directory: docs/badges + generate-branches-badge: true + generate-summary: true + jacoco-csv-file: > + jacodb-analysis/build/reports/jacoco/test/jacocoTestReport.csv + jacodb-core/build/reports/jacoco/test/jacocoTestReport.csv + + - name: Log coverage percentages to workflow output + run: | + echo "coverage = ${{ steps.jacoco.outputs.coverage }}" + echo "branches = ${{ steps.jacoco.outputs.branches }}" + - name: Upload JaCoCo coverage report as a workflow artifact + uses: actions/upload-artifact@v3 + with: + name: jacoco-report + path: jacodb-*/build/reports/jacoco/ + + - name: Commit and push the coverage badges and summary file + if: ${{ github.event_name != 'pull_request' }} + run: | + cd docs/badges + if [[ `git status --porcelain *.svg *.json` ]]; then + git config --global user.name 'github-actions' + git config --global user.email '41898282+github-actions[bot]@users.noreply.github.com' + git add *.svg *.json + git commit -m "Autogenerated JaCoCo coverage badges" *.svg *.json + git push + fi + - name: Comment on PR with coverage percentages + if: ${{ github.event_name == 'pull_request' }} + run: | + REPORT=$( { useJUnitPlatform() jvmArgs = listOf("-Xmx2g", "-XX:+HeapDumpOnOutOfMemoryError", "-XX:HeapDumpPath=heapdump.hprof") testLogging { events("passed", "skipped", "failed") } + finalizedBy(jacocoTestReport) // report is always generated after tests run } + val sourcesJar by creating(Jar::class) { archiveClassifier.set("sources") from(sourceSets.getByName("main").kotlin.srcDirs) diff --git a/docs/badges/branches.svg b/docs/badges/branches.svg new file mode 100644 index 000000000..3935ab969 --- /dev/null +++ b/docs/badges/branches.svg @@ -0,0 +1 @@ +branches68.3% \ No newline at end of file diff --git a/docs/badges/coverage-summary.json b/docs/badges/coverage-summary.json new file mode 100644 index 000000000..483ff35cc --- /dev/null +++ b/docs/badges/coverage-summary.json @@ -0,0 +1 @@ +{"branches": 68.34195614683419, "coverage": 70.38198957259596} \ No newline at end of file diff --git a/docs/badges/jacoco.svg b/docs/badges/jacoco.svg new file mode 100644 index 000000000..31127ea7b --- /dev/null +++ b/docs/badges/jacoco.svg @@ -0,0 +1 @@ +coverage70.3% \ No newline at end of file diff --git a/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcInst.kt b/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcInst.kt index d6189f9a1..8cec4badb 100644 --- a/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcInst.kt +++ b/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcInst.kt @@ -680,14 +680,14 @@ data class JcLambdaExpr( data class JcDynamicCallExpr( private val bsmRef: TypedMethodRef, val bsmArgs: List, - val callCiteMethodName: String, - val callCiteArgTypes: List, - val callCiteReturnType: JcType, - val callCiteArgs: List + val callSiteMethodName: String, + val callSiteArgTypes: List, + val callSiteReturnType: JcType, + val callSiteArgs: List ) : JcCallExpr { override val method get() = bsmRef.method - override val args get() = callCiteArgs + override val args get() = callSiteArgs override fun accept(visitor: JcExprVisitor): T { return visitor.visitJcDynamicCallExpr(this) diff --git a/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcRawInst.kt b/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcRawInst.kt index cbc4a5dbc..aa8f4cd7c 100644 --- a/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcRawInst.kt +++ b/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcRawInst.kt @@ -690,17 +690,17 @@ data class BsmHandle( data class JcRawDynamicCallExpr( val bsm: BsmHandle, val bsmArgs: List, - val callCiteMethodName: String, - val callCiteArgTypes: List, - val callCiteReturnType: TypeName, - val callCiteArgs: List + val callSiteMethodName: String, + val callSiteArgTypes: List, + val callSiteReturnType: TypeName, + val callSiteArgs: List ) : JcRawCallExpr { override val declaringClass get() = bsm.declaringClass override val methodName get() = bsm.name override val argumentTypes get() = bsm.argTypes override val returnType get() = bsm.returnType override val typeName get() = returnType - override val args get() = callCiteArgs + override val args get() = callSiteArgs override fun accept(visitor: JcRawExprVisitor): T { return visitor.visitJcRawDynamicCallExpr(this) diff --git a/jacodb-core/src/main/kotlin/org/jacodb/impl/analysis/impl/StringConcatSimplifier.kt b/jacodb-core/src/main/kotlin/org/jacodb/impl/analysis/impl/StringConcatSimplifier.kt index a12299168..7e3655d39 100644 --- a/jacodb-core/src/main/kotlin/org/jacodb/impl/analysis/impl/StringConcatSimplifier.kt +++ b/jacodb-core/src/main/kotlin/org/jacodb/impl/analysis/impl/StringConcatSimplifier.kt @@ -58,12 +58,12 @@ class StringConcatSimplifier(val jcGraph: JcGraph) : DefaultJcInstVisitor rhv.callCiteArgs - rhv.callCiteArgs.size == 1 && rhv.bsmArgs.size == 1 && rhv.bsmArgs[0] is BsmStringArg -> listOf( - rhv.callCiteArgs[0], + rhv.callSiteArgs.size == 2 -> rhv.callSiteArgs + rhv.callSiteArgs.size == 1 && rhv.bsmArgs.size == 1 && rhv.bsmArgs[0] is BsmStringArg -> listOf( + rhv.callSiteArgs[0], JcStringConstant((rhv.bsmArgs[0] as BsmStringArg).value, stringType) ) diff --git a/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/JcInstListBuilder.kt b/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/JcInstListBuilder.kt index f11d0a00b..45258409b 100644 --- a/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/JcInstListBuilder.kt +++ b/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/JcInstListBuilder.kt @@ -51,7 +51,6 @@ import org.jacodb.api.cfg.JcFieldRef import org.jacodb.api.cfg.JcFloat import org.jacodb.api.cfg.JcGeExpr import org.jacodb.api.cfg.JcGotoInst -import org.jacodb.api.cfg.JcGraph import org.jacodb.api.cfg.JcGtExpr import org.jacodb.api.cfg.JcIfInst import org.jacodb.api.cfg.JcInst @@ -412,9 +411,9 @@ class JcInstListBuilder(val method: JcMethod,val instList: JcInstList return JcDynamicCallExpr( classpath.methodRef(expr), expr.bsmArgs, - expr.callCiteMethodName, - expr.callCiteArgTypes.map { it.asType() }, - expr.callCiteReturnType.asType(), + expr.callSiteMethodName, + expr.callSiteArgTypes.map { it.asType() }, + expr.callSiteReturnType.asType(), expr.args.map { it.accept(this) as JcValue } ) } diff --git a/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/MethodNodeBuilder.kt b/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/MethodNodeBuilder.kt index 431d08f45..d0bfe95a7 100644 --- a/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/MethodNodeBuilder.kt +++ b/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/MethodNodeBuilder.kt @@ -660,8 +660,8 @@ class MethodNodeBuilder( expr.args.forEach { it.accept(this) } currentInsnList.add( InvokeDynamicInsnNode( - expr.callCiteMethodName, - "(${expr.callCiteArgTypes.joinToString("") { it.jvmTypeName }})${expr.callCiteReturnType.jvmTypeName}", + expr.callSiteMethodName, + "(${expr.callSiteArgTypes.joinToString("") { it.jvmTypeName }})${expr.callSiteReturnType.jvmTypeName}", expr.bsm.asAsmHandle, *expr.bsmArgs.map { when (it) { diff --git a/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/util/ExprMapper.kt b/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/util/ExprMapper.kt index 678cc864a..b598c4fdb 100644 --- a/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/util/ExprMapper.kt +++ b/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/util/ExprMapper.kt @@ -318,9 +318,9 @@ class ExprMapper(val mapping: Map) : JcRawInstVisitor JcRawDynamicCallExpr( expr.bsm, expr.bsmArgs, - expr.callCiteMethodName, - expr.callCiteArgTypes, - expr.callCiteReturnType, + expr.callSiteMethodName, + expr.callSiteArgTypes, + expr.callSiteReturnType, newArgs ) } diff --git a/jacodb-core/src/main/kotlin/org/jacodb/impl/features/HierarchyExtension.kt b/jacodb-core/src/main/kotlin/org/jacodb/impl/features/HierarchyExtension.kt index 9d1c2f6cf..f2ecd52a9 100644 --- a/jacodb-core/src/main/kotlin/org/jacodb/impl/features/HierarchyExtension.kt +++ b/jacodb-core/src/main/kotlin/org/jacodb/impl/features/HierarchyExtension.kt @@ -49,6 +49,7 @@ class HierarchyExtensionImpl(private val cp: JcClasspath) : HierarchyExtension { JOIN Classes ON Classes.id = hierarchy.class_id JOIN Symbols ON Symbols.id = Classes.name WHERE location_id in ($locationIds) + ORDER BY Classes.id """.trimIndent() private fun directSubClassesQuery(locationIds: String, sinceId: Long?) = """ @@ -56,7 +57,8 @@ class HierarchyExtensionImpl(private val cp: JcClasspath) : HierarchyExtension { JOIN Symbols ON Symbols.id = ClassHierarchies.super_id JOIN Symbols as SymbolsName ON SymbolsName.id = Classes.name JOIN Classes ON Classes.id = ClassHierarchies.class_id - WHERE Symbols.name = ? and ($sinceId is null or ClassHierarchies.class_id > $sinceId) AND Classes.location_id in ($locationIds) + WHERE Symbols.name = ? and ($sinceId is null or ClassHierarchies.class_id > $sinceId) AND Classes.location_id in ($locationIds) + ORDER BY Classes.id """.trimIndent() } diff --git a/jacodb-core/src/main/kotlin/org/jacodb/impl/types/Objects.kt b/jacodb-core/src/main/kotlin/org/jacodb/impl/types/Objects.kt index 964462bd2..a40d4669d 100644 --- a/jacodb-core/src/main/kotlin/org/jacodb/impl/types/Objects.kt +++ b/jacodb-core/src/main/kotlin/org/jacodb/impl/types/Objects.kt @@ -108,59 +108,9 @@ sealed class AnnotationValue @Serializable open class AnnotationValueList(val annotations: List) : AnnotationValue() -@Serializable(with = PrimitiveValueSerializer::class) +@Serializable class PrimitiveValue(val dataType: AnnotationValueKind, val value: Any) : AnnotationValue() -object PrimitiveValueSerializer : KSerializer { - override val descriptor: SerialDescriptor = buildClassSerialDescriptor("PrimitiveValue") { - element("dataType", serialDescriptor()) - element("value", buildClassSerialDescriptor("Any")) - } - - @Suppress("UNCHECKED_CAST") - private val dataTypeSerializers: Map> = - mapOf( - AnnotationValueKind.STRING to serializer(), - AnnotationValueKind.BYTE to serializer(), - AnnotationValueKind.SHORT to serializer(), - AnnotationValueKind.CHAR to serializer(), - AnnotationValueKind.LONG to serializer(), - AnnotationValueKind.INT to serializer(), - AnnotationValueKind.FLOAT to serializer(), - AnnotationValueKind.DOUBLE to serializer(), - AnnotationValueKind.BYTE to serializer(), - AnnotationValueKind.BOOLEAN to serializer() - //list them all - ).mapValues { (_, v) -> v as KSerializer } - - private fun getPayloadSerializer(dataType: AnnotationValueKind): KSerializer = dataTypeSerializers[dataType] - ?: throw SerializationException("Serializer for class $dataType is not registered in PacketSerializer") - - override fun serialize(encoder: Encoder, value: PrimitiveValue) { - encoder.encodeStructure(descriptor) { - encodeStringElement(descriptor, 0, value.dataType.name) - encodeSerializableElement(descriptor, 1, getPayloadSerializer(value.dataType), value.value) - } - } - - @ExperimentalSerializationApi - override fun deserialize(decoder: Decoder): PrimitiveValue = decoder.decodeStructure(descriptor) { - val dataType = AnnotationValueKind.valueOf(decodeStringElement(descriptor, 0)) - if (decodeSequentially()) { - val payload = decodeSerializableElement(descriptor, 1, getPayloadSerializer(dataType)) - PrimitiveValue(dataType, payload) - } else { - require(decodeElementIndex(descriptor) == 0) { "dataType field should precede payload field" } - val payload = when (val index = decodeElementIndex(descriptor)) { - 1 -> decodeSerializableElement(descriptor, 1, getPayloadSerializer(dataType)) - CompositeDecoder.DECODE_DONE -> throw SerializationException("payload field is missing") - else -> error("Unexpected index: $index") - } - PrimitiveValue(dataType, payload) - } - } -} - @Serializable class ClassRef(val className: String) : AnnotationValue() diff --git a/jacodb-core/src/main/resources/sqlite/add-indexes.sql b/jacodb-core/src/main/resources/sqlite/add-indexes.sql index ef2a4d7ef..f4465a71b 100644 --- a/jacodb-core/src/main/resources/sqlite/add-indexes.sql +++ b/jacodb-core/src/main/resources/sqlite/add-indexes.sql @@ -1,4 +1,24 @@ CREATE INDEX IF NOT EXISTS "Classes_name" ON "Classes" ("name"); + +CREATE INDEX IF NOT EXISTS "Classes_outerMethodId" ON "Classes" ("outer_method"); +CREATE INDEX IF NOT EXISTS "ClassInnerClasses_classId" ON "ClassInnerClasses" ("class_id"); +CREATE INDEX IF NOT EXISTS "ClassInnerClasses_classId" ON "ClassInnerClasses" ("class_id"); +CREATE INDEX IF NOT EXISTS "ClassInnerClasses_innerClassId" ON "ClassInnerClasses" ("inner_class_id"); + +CREATE INDEX IF NOT EXISTS "ClassHierarchies_classId" ON "ClassHierarchies" ("class_id"); +CREATE INDEX IF NOT EXISTS "ClassHierarchies_superId" ON "ClassHierarchies" ("super_id"); + +CREATE INDEX IF NOT EXISTS "Annotations_classId" ON "Annotations" ("class_id"); +CREATE INDEX IF NOT EXISTS "Annotations_fieldId" ON "Annotations" ("field_id"); +CREATE INDEX IF NOT EXISTS "Annotations_methodId" ON "Annotations" ("method_id"); +CREATE INDEX IF NOT EXISTS "Annotations_paramsId" ON "Annotations" ("param_id"); + +CREATE INDEX IF NOT EXISTS "Classes_location" ON "Classes" ("location_id"); +CREATE INDEX IF NOT EXISTS "Fields_classId" ON "Fields" ("class_id"); +CREATE INDEX IF NOT EXISTS "Methods_classId" ON "Methods" ("class_id"); + +CREATE INDEX IF NOT EXISTS "MethodParameters_methodId" ON "MethodParameters" ("method_id"); + CREATE UNIQUE INDEX IF NOT EXISTS "Symbols_name" ON "Symbols" ("name"); CREATE UNIQUE INDEX IF NOT EXISTS "Bytecodelocations_hash" ON "BytecodeLocations" ("uniqueId"); CREATE UNIQUE INDEX IF NOT EXISTS "Methods_class_id_name_desc" ON "Methods" ("class_id", "name", "desc");