Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ on:
pull_request:
branches:
- main
- develop

permissions:
contents: read
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ private class NPEForwardFunctions(
// Possibly null arguments
result += method.flowGraph().locals
.filterIsInstance<JcArgument>()
.filter { it.type.nullable != false }
.filter { method.parameters[it.index].isNullable != false }
.map { NPETaintNode(AccessPath.fromLocal(it)) }

// Possibly null statics
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,19 +140,15 @@ class NpeAnalysisTest : BaseTest() {
fun `npe on virtual call when possible`() {
testOneMethod<NPEExamples>(
"possibleNPEOnVirtualCall",

// TODO: first location is false-positive here due to not-parsed @NotNull annotation
listOf("%0 = arg\$0.functionThatCanThrowNPEOnNull(arg\$1)", "%0 = arg\$0.length()")
listOf("%0 = arg\$0.length()")
)
}

@Test
fun `no npe on virtual call when impossible`() {
testOneMethod<NPEExamples>(
"noNPEOnVirtualCall",

// TODO: false-positive here due to not-parsed @NotNull annotation
listOf("%0 = arg\$0.functionThatCanNotThrowNPEOnNull(arg\$1)")
emptyList()
)
}

Expand Down
2 changes: 0 additions & 2 deletions jacodb-api/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,4 @@ dependencies {
api(group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-jdk8", version = coroutinesVersion)

api(group = "org.jooq", name = "jooq", version = jooqVersion)
implementation(group = "org.jetbrains.kotlinx", name = "kotlinx-metadata-jvm", version = kmetadataVersion)

}
5 changes: 3 additions & 2 deletions jacodb-api/src/main/kotlin/org/jacodb/api/JcClasspath.kt
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,10 @@ interface JcClasspath : Closeable {
*/
fun findTypeOrNull(name: String): JcType?

fun typeOf(jcClass: JcClassOrInterface): JcRefType
fun typeOf(jcClass: JcClassOrInterface, nullability: Boolean? = null, annotations: List<JcAnnotation> = listOf()): JcRefType

fun arrayTypeOf(elementType: JcType, nullability: Boolean? = null, annotations: List<JcAnnotation> = listOf()): JcArrayType

fun arrayTypeOf(elementType: JcType): JcArrayType
fun toJcClass(source: ClassSource): JcClassOrInterface

suspend fun refreshed(closeOld: Boolean): JcClasspath
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ object PredefinedPrimitives {
private val valueSet = values.toHashSet()

@JvmStatic
fun of(name: String, cp: JcClasspath): JcPrimitiveType? {
fun of(name: String, cp: JcClasspath, annotations: List<JcAnnotation> = listOf()): JcPrimitiveType? {
if (valueSet.contains(name)) {
return PredefinedPrimitive(cp, name)
return PredefinedPrimitive(cp, name, annotations)
}
return null
}
Expand All @@ -52,7 +52,8 @@ object PredefinedPrimitives {
/**
* Predefined primitive types
*/
class PredefinedPrimitive(override val classpath: JcClasspath, override val typeName: String) : JcPrimitiveType {
class PredefinedPrimitive(override val classpath: JcClasspath, override val typeName: String,
override val annotations: List<JcAnnotation> = listOf()) : JcPrimitiveType {

override fun equals(other: Any?): Boolean {
if (this === other) return true
Expand All @@ -69,4 +70,6 @@ class PredefinedPrimitive(override val classpath: JcClasspath, override val type
return typeName.hashCode()
}

override fun copyWithAnnotations(annotations: List<JcAnnotation>): JcType =
PredefinedPrimitive(classpath, typeName, annotations)
}
8 changes: 7 additions & 1 deletion jacodb-api/src/main/kotlin/org/jacodb/api/Types.kt
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,16 @@ interface JcTypedMethodParameter {
val type: JcType
val name: String?
val enclosingMethod: JcTypedMethod
val nullable: Boolean?
}

interface JcType {
val classpath: JcClasspath
val typeName: String

val nullable: Boolean?
val annotations: List<JcAnnotation>

fun copyWithAnnotations(annotations: List<JcAnnotation>): JcType
}

interface JcPrimitiveType : JcType {
Expand Down Expand Up @@ -109,12 +111,16 @@ interface JcTypeVariable : JcRefType {
interface JcBoundedWildcard : JcRefType {
val upperBounds: List<JcRefType>
val lowerBounds: List<JcRefType>

override fun copyWithAnnotations(annotations: List<JcAnnotation>): JcType = this
}

interface JcUnboundWildcard : JcRefType {
override val jcClass: JcClassOrInterface
get() = classpath.objectClass

override fun copyWithAnnotations(annotations: List<JcAnnotation>): JcType = this

}

interface JcTypeVariableDeclaration {
Expand Down
25 changes: 21 additions & 4 deletions jacodb-api/src/main/kotlin/org/jacodb/api/ext/Nullables.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,23 +19,40 @@
package org.jacodb.api.ext

import org.jacodb.api.JcAnnotated
import org.jacodb.api.JcAnnotation
import org.jacodb.api.JcField
import org.jacodb.api.JcMethod
import org.jacodb.api.JcParameter
import org.jacodb.api.PredefinedPrimitives
import org.jacodb.api.TypeName

const val NotNull = "org.jetbrains.annotations.NotNull"
const val Nullable = "org.jetbrains.annotations.Nullable"
val JcAnnotation.isNotNullAnnotation: Boolean
get() = NullabilityAnnotations.notNullAnnotations.any { matches(it) }

val JcAnnotation.isNullableAnnotation: Boolean
get() = NullabilityAnnotations.nullableAnnotations.any { matches(it) }

private object NullabilityAnnotations {
val notNullAnnotations = listOf(
"org.jetbrains.annotations.NotNull",
"lombok.NonNull"
)

val nullableAnnotations = listOf(
"org.jetbrains.annotations.Nullable"
)
}

private fun JcAnnotated.isNullable(type: TypeName): Boolean? =
when {
PredefinedPrimitives.matches(type.typeName) -> false
annotations.any { it.matches(NotNull) } -> false
annotations.any { it.matches(Nullable) } -> true
annotations.any { it.isNotNullAnnotation } -> false
annotations.any { it.isNullableAnnotation } -> true
else -> null
}

// TODO: maybe move these methods from ext into class definitions?
// We already have many nullability-related methods there, furthermore this way we can use jacodb-core in implementation
val JcField.isNullable: Boolean?
get() = isNullable(type)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,15 @@ open class DefaultCatalog : CatalogImpl("") {
/**
* The reference instance of <code>DEFAULT_CATALOG</code>
*/
val DEFAULT_CATALOG = org.jacodb.impl.storage.jooq.DefaultCatalog()
val DEFAULT_CATALOG = DefaultCatalog()
}

/**
* The schema <code>DEFAULT_SCHEMA</code>.
*/
val DEFAULT_SCHEMA get() = org.jacodb.impl.storage.jooq.DefaultSchema.Companion.DEFAULT_SCHEMA
val DEFAULT_SCHEMA get() = DefaultSchema.DEFAULT_SCHEMA

override fun getSchemas(): List<Schema> = listOf(
org.jacodb.impl.storage.jooq.DefaultSchema.Companion.DEFAULT_SCHEMA
DefaultSchema.DEFAULT_SCHEMA
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@
package org.jacodb.impl.storage.jooq


import kotlin.collections.List

import org.jooq.Catalog
import org.jooq.Table
import org.jooq.impl.SchemaImpl
import org.jacodb.impl.storage.jooq.tables.Annotations
import org.jacodb.impl.storage.jooq.tables.Annotationvalues
import org.jacodb.impl.storage.jooq.tables.Builders
import org.jacodb.impl.storage.jooq.tables.Bytecodelocations
Expand All @@ -32,28 +38,25 @@ import org.jacodb.impl.storage.jooq.tables.Methodparameters
import org.jacodb.impl.storage.jooq.tables.Methods
import org.jacodb.impl.storage.jooq.tables.Outerclasses
import org.jacodb.impl.storage.jooq.tables.Symbols
import org.jooq.Catalog
import org.jooq.Table
import org.jooq.impl.SchemaImpl


/**
* This class is generated by jOOQ.
*/
@Suppress("UNCHECKED_CAST")
open class DefaultSchema : SchemaImpl("", org.jacodb.impl.storage.jooq.DefaultCatalog.Companion.DEFAULT_CATALOG) {
open class DefaultSchema : SchemaImpl("", DefaultCatalog.DEFAULT_CATALOG) {
companion object {

/**
* The reference instance of <code>DEFAULT_SCHEMA</code>
*/
val DEFAULT_SCHEMA = org.jacodb.impl.storage.jooq.DefaultSchema()
val DEFAULT_SCHEMA = DefaultSchema()
}

/**
* The table <code>Annotations</code>.
*/
val ANNOTATIONS get() = org.jacodb.impl.storage.jooq.tables.Annotations.ANNOTATIONS
val ANNOTATIONS get() = Annotations.ANNOTATIONS

/**
* The table <code>AnnotationValues</code>.
Expand Down Expand Up @@ -115,10 +118,10 @@ open class DefaultSchema : SchemaImpl("", org.jacodb.impl.storage.jooq.DefaultCa
*/
val SYMBOLS get() = Symbols.SYMBOLS

override fun getCatalog(): Catalog = org.jacodb.impl.storage.jooq.DefaultCatalog.Companion.DEFAULT_CATALOG
override fun getCatalog(): Catalog = DefaultCatalog.DEFAULT_CATALOG

override fun getTables(): List<Table<*>> = listOf(
org.jacodb.impl.storage.jooq.tables.Annotations.ANNOTATIONS,
Annotations.ANNOTATIONS,
Annotationvalues.ANNOTATIONVALUES,
Builders.BUILDERS,
Bytecodelocations.BYTECODELOCATIONS,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,19 @@
package org.jacodb.impl.storage.jooq.indexes


import org.jacodb.impl.storage.jooq.tables.Bytecodelocations
import org.jacodb.impl.storage.jooq.tables.Classhierarchies
import org.jacodb.impl.storage.jooq.tables.Fields
import org.jacodb.impl.storage.jooq.tables.Methods
import org.jacodb.impl.storage.jooq.tables.Symbols
import org.jooq.Index
import org.jooq.impl.DSL
import org.jooq.impl.Internal
import org.jacodb.impl.storage.jooq.tables.Builders
import org.jacodb.impl.storage.jooq.tables.Calls



// -------------------------------------------------------------------------
// INDEX definitions
// -------------------------------------------------------------------------

val BYTECODELOCATIONS_HASH: Index = Internal.createIndex(DSL.name("Bytecodelocations_hash"), Bytecodelocations.BYTECODELOCATIONS, arrayOf(Bytecodelocations.BYTECODELOCATIONS.UNIQUEID), true)
val `CLASS HIERARCHIES`: Index = Internal.createIndex(DSL.name("Class Hierarchies"), Classhierarchies.CLASSHIERARCHIES, arrayOf(Classhierarchies.CLASSHIERARCHIES.SUPER_ID), false)
val FIELDS_CLASS_ID_NAME: Index = Internal.createIndex(DSL.name("Fields_class_id_name"), Fields.FIELDS, arrayOf(Fields.FIELDS.CLASS_ID, Fields.FIELDS.NAME), true)
val METHODS_CLASS_ID_NAME_DESC: Index = Internal.createIndex(DSL.name("Methods_class_id_name_desc"), Methods.METHODS, arrayOf(Methods.METHODS.CLASS_ID, Methods.METHODS.NAME, Methods.METHODS.DESC), true)
val SYMBOLS_NAME: Index = Internal.createIndex(DSL.name("Symbols_name"), Symbols.SYMBOLS, arrayOf(Symbols.SYMBOLS.NAME), true)
val BUILDERSJOIN: Index = Internal.createIndex(DSL.name("BuildersJoin"), Builders.BUILDERS, arrayOf(Builders.BUILDERS.BUILDER_CLASS_SYMBOL_ID), false)
val BUILDERSSEARCH: Index = Internal.createIndex(DSL.name("BuildersSearch"), Builders.BUILDERS, arrayOf(Builders.BUILDERS.LOCATION_ID, Builders.BUILDERS.CLASS_SYMBOL_ID, Builders.BUILDERS.PRIORITY), false)
val BUILDERSSORTING: Index = Internal.createIndex(DSL.name("BuildersSorting"), Builders.BUILDERS, arrayOf(Builders.BUILDERS.PRIORITY), false)
val CALLSSEARCH: Index = Internal.createIndex(DSL.name("CallsSearch"), Calls.CALLS, arrayOf(Calls.CALLS.OPCODE, Calls.CALLS.LOCATION_ID, Calls.CALLS.CALLEE_CLASS_SYMBOL_ID, Calls.CALLS.CALLEE_NAME_SYMBOL_ID, Calls.CALLS.CALLEE_DESC_HASH), false)
Loading