diff --git a/core/src/main/kotlin/com/toasttab/expediter/Expediter.kt b/core/src/main/kotlin/com/toasttab/expediter/Expediter.kt index 8f1c9f7..6dc635f 100644 --- a/core/src/main/kotlin/com/toasttab/expediter/Expediter.kt +++ b/core/src/main/kotlin/com/toasttab/expediter/Expediter.kt @@ -15,20 +15,23 @@ package com.toasttab.expediter +import com.toasttab.expediter.access.AccessCheck import com.toasttab.expediter.ignore.Ignore import com.toasttab.expediter.issue.Issue +import com.toasttab.expediter.provider.ApplicationTypesProvider +import com.toasttab.expediter.provider.PlatformTypeProvider import com.toasttab.expediter.types.ApplicationType import com.toasttab.expediter.types.InspectedTypes import com.toasttab.expediter.types.MemberAccess import com.toasttab.expediter.types.MemberType import com.toasttab.expediter.types.MethodAccessType import com.toasttab.expediter.types.OptionalResolvedTypeHierarchy -import com.toasttab.expediter.types.PlatformTypeProvider +import com.toasttab.expediter.types.PlatformType import com.toasttab.expediter.types.ResolvedTypeHierarchy +import com.toasttab.expediter.types.Type import com.toasttab.expediter.types.members import protokt.v1.toasttab.expediter.v1.AccessDeclaration import protokt.v1.toasttab.expediter.v1.MemberDescriptor -import protokt.v1.toasttab.expediter.v1.TypeDescriptor import protokt.v1.toasttab.expediter.v1.TypeExtensibility import protokt.v1.toasttab.expediter.v1.TypeFlavor @@ -42,21 +45,25 @@ class Expediter( } private fun findIssues(appType: ApplicationType): Collection { - val hierarchy = inspectedTypes.resolveHierarchy(appType.type) + val hierarchy = inspectedTypes.resolveHierarchy(appType) val issues = mutableListOf() + val missingApplicationSupertypes = hashSetOf() + when (hierarchy) { is ResolvedTypeHierarchy.IncompleteTypeHierarchy -> { + hierarchy.missingType.mapTo(missingApplicationSupertypes) { it.name } + issues.add( Issue.MissingApplicationSuperType( appType.name, - hierarchy.missingType.map { it.name }.toSet() + missingApplicationSupertypes ) ) } is ResolvedTypeHierarchy.CompleteTypeHierarchy -> { - val finalSupertypes = hierarchy.superTypes.filter { it.extensibility == TypeExtensibility.FINAL } + val finalSupertypes = hierarchy.superTypes.filter { it.descriptor.extensibility == TypeExtensibility.FINAL } .toList() if (finalSupertypes.isNotEmpty()) { issues.add( @@ -69,13 +76,37 @@ class Expediter( } } + val missingTypes = hashSetOf() + + for (refType in appType.referencedTypes) { + val chain = inspectedTypes.resolveHierarchy(refType) + + if (chain is OptionalResolvedTypeHierarchy.NoType) { + missingTypes.add(refType) + } else if (chain is ResolvedTypeHierarchy.IncompleteTypeHierarchy && chain.type is PlatformType) { + issues.add(Issue.MissingSuperType(appType.name, refType, chain.missingType.map { it.name }.toSet())) + } + } + + issues.addAll(missingTypes.map { Issue.MissingType(appType.name, it) }) + issues.addAll( - appType.refs.mapNotNull { access -> + appType.memberAccess.mapNotNull { access -> findIssue(appType, hierarchy, access, inspectedTypes.resolveHierarchy(access.targetType)) } ) - return issues + return issues.filter { + when (it) { + // if application type A extends a missing type B and refers to it otherwise + // (which is typically via the super constructor), only report the missing supertype issue + is Issue.MissingType -> !missingApplicationSupertypes.contains(it.target) + // if application type A refers to a type B with a missing supertype C + // and also refers to C directly, only report the latter + is Issue.MissingSuperType -> !missingTypes.containsAll(it.missing) + else -> true + } + } } fun findIssues(): Set { @@ -83,46 +114,60 @@ class Expediter( inspectedTypes.classes.flatMap { appType -> findIssues(appType) } + inspectedTypes.duplicateTypes - ).filter { !ignore.ignore(it) }.toSet() + ).filter { !ignore.ignore(it) } + .toSet() } -} -fun findIssue(type: ApplicationType, hierarchy: ResolvedTypeHierarchy, access: MemberAccess, chain: OptionalResolvedTypeHierarchy): Issue? { - return when (chain) { - is ResolvedTypeHierarchy.IncompleteTypeHierarchy -> Issue.MissingSuperType( - type.name, - access.targetType, - chain.missingType.map { it.name }.toSet() - ) + fun findIssue( + type: ApplicationType, + hierarchy: ResolvedTypeHierarchy, + access: MemberAccess, + chain: OptionalResolvedTypeHierarchy + ): Issue? { + return when (chain) { + is OptionalResolvedTypeHierarchy.NoType -> Issue.MissingType(type.name, access.targetType) - is OptionalResolvedTypeHierarchy.NoType -> Issue.MissingType(type.name, access.targetType) - is ResolvedTypeHierarchy.CompleteTypeHierarchy -> { - val member = chain.resolveMember(access) - - if (member == null) { - Issue.MissingMember(type.name, access) - } else { - val resolvedAccess = access.withDeclaringType(member.declaringType.name) - - if (member.member.declaration == AccessDeclaration.STATIC && !access.accessType.isStatic()) { - Issue.AccessStaticMemberNonStatically(type.name, resolvedAccess) - } else if (member.member.declaration == AccessDeclaration.INSTANCE && access.accessType.isStatic()) { - Issue.AccessInstanceMemberStatically(type.name, resolvedAccess) - } else if (!AccessCheck.allowedAccess(hierarchy, chain, member.member, member.declaringType)) { - Issue.AccessInaccessibleMember(type.name, resolvedAccess) + is ResolvedTypeHierarchy.IncompleteTypeHierarchy -> { + if (chain.type is PlatformType) { + Issue.MissingSuperType( + type.name, + access.targetType, + chain.missingType.map { it.name }.toSet() + ) } else { + // missing supertypes of application types will be reported separately null } } + + is ResolvedTypeHierarchy.CompleteTypeHierarchy -> { + val member = chain.resolveMember(access) + + if (member == null) { + Issue.MissingMember(type.name, access) + } else { + val resolvedAccess = access.withDeclaringType(member.declaringType.name) + + if (member.member.declaration == AccessDeclaration.STATIC && !access.accessType.isStatic()) { + Issue.AccessStaticMemberNonStatically(type.name, resolvedAccess) + } else if (member.member.declaration == AccessDeclaration.INSTANCE && access.accessType.isStatic()) { + Issue.AccessInstanceMemberStatically(type.name, resolvedAccess) + } else if (!AccessCheck.allowedAccess(hierarchy, chain, member.member, member.declaringType)) { + Issue.AccessInaccessibleMember(type.name, resolvedAccess) + } else { + null + } + } + } } } } private class MemberWithDeclaringType( val member: MemberDescriptor, - val declaringType: TypeDescriptor + val declaringType: Type ) -private fun ResolvedTypeHierarchy.CompleteTypeHierarchy.filterToAccessType(access: MemberAccess): Sequence { +private fun ResolvedTypeHierarchy.CompleteTypeHierarchy.filterToAccessType(access: MemberAccess): Sequence { return if (access !is MemberAccess.MethodAccess || access.accessType == MethodAccessType.VIRTUAL || access.accessType == MethodAccessType.STATIC || @@ -133,7 +178,7 @@ private fun ResolvedTypeHierarchy.CompleteTypeHierarchy.filterT allTypes } else if (access.accessType == MethodAccessType.INTERFACE) { // methods invoked via invokeinterface must be declared on an interface - allTypes.filter { it.flavor != TypeFlavor.CLASS } + allTypes.filter { it.descriptor.flavor != TypeFlavor.CLASS } } else { // constructors must always be declared by the type being constructed sequenceOf(type) @@ -142,7 +187,7 @@ private fun ResolvedTypeHierarchy.CompleteTypeHierarchy.filterT private fun ResolvedTypeHierarchy.CompleteTypeHierarchy.resolveMember(access: MemberAccess): MemberWithDeclaringType? { for (cls in filterToAccessType(access)) { - for (m in cls.members) { + for (m in cls.descriptor.members) { if (access.ref.same(m.ref)) { return MemberWithDeclaringType(m, cls) } diff --git a/core/src/main/kotlin/com/toasttab/expediter/AccessCheck.kt b/core/src/main/kotlin/com/toasttab/expediter/access/AccessCheck.kt similarity index 92% rename from core/src/main/kotlin/com/toasttab/expediter/AccessCheck.kt rename to core/src/main/kotlin/com/toasttab/expediter/access/AccessCheck.kt index 78ee58d..f862ee6 100644 --- a/core/src/main/kotlin/com/toasttab/expediter/AccessCheck.kt +++ b/core/src/main/kotlin/com/toasttab/expediter/access/AccessCheck.kt @@ -1,6 +1,7 @@ -package com.toasttab.expediter +package com.toasttab.expediter.access import com.toasttab.expediter.types.ResolvedTypeHierarchy +import com.toasttab.expediter.types.Type import protokt.v1.toasttab.expediter.v1.AccessDeclaration import protokt.v1.toasttab.expediter.v1.AccessProtection import protokt.v1.toasttab.expediter.v1.MemberDescriptor @@ -25,10 +26,10 @@ object AccessCheck { caller: ResolvedTypeHierarchy, refType: ResolvedTypeHierarchy.CompleteTypeHierarchy, member: MemberDescriptor, - declaringType: TypeDescriptor + declaringType: Type ): Boolean { // reference type must be accessible from the caller - if (!isClassAccessible(caller, refType.type)) { + if (!isClassAccessible(caller, refType.type.descriptor)) { return false } @@ -40,7 +41,7 @@ object AccessCheck { // private members are accessible to classes in the same nest as the declaring type AccessProtection.PRIVATE -> sameNest(caller.name, declaringType.name) // protected logic is more complicated, see below - AccessProtection.PROTECTED -> isProtectedAccessAllowed(caller, refType, member, declaringType) + AccessProtection.PROTECTED -> isProtectedAccessAllowed(caller, refType, member, declaringType.descriptor) } } @@ -66,8 +67,8 @@ object AccessCheck { // the caller, the reference type, and the declaring type must be part of the same hierarchy, // i.e. the caller must be a subtype or a supertype of the reference type member.declaration != AccessDeclaration.INSTANCE || - refType.isSubtypeOf(caller.type) || - caller.isSubtypeOf(refType.type) + refType.isSubtypeOf(caller.type.descriptor) || + caller.isSubtypeOf(refType.type.descriptor) } else { false } diff --git a/core/src/main/kotlin/com/toasttab/expediter/AttributeParser.kt b/core/src/main/kotlin/com/toasttab/expediter/parser/AttributeParser.kt similarity index 97% rename from core/src/main/kotlin/com/toasttab/expediter/AttributeParser.kt rename to core/src/main/kotlin/com/toasttab/expediter/parser/AttributeParser.kt index 1cafd2c..6bce1a2 100644 --- a/core/src/main/kotlin/com/toasttab/expediter/AttributeParser.kt +++ b/core/src/main/kotlin/com/toasttab/expediter/parser/AttributeParser.kt @@ -13,7 +13,7 @@ * limitations under the License. */ -package com.toasttab.expediter +package com.toasttab.expediter.parser import org.objectweb.asm.Opcodes import protokt.v1.toasttab.expediter.v1.AccessDeclaration diff --git a/core/src/main/kotlin/com/toasttab/expediter/parser/SignatureParser.kt b/core/src/main/kotlin/com/toasttab/expediter/parser/SignatureParser.kt new file mode 100644 index 0000000..d322a53 --- /dev/null +++ b/core/src/main/kotlin/com/toasttab/expediter/parser/SignatureParser.kt @@ -0,0 +1,97 @@ +package com.toasttab.expediter.parser + +class SignatureParser private constructor(private val signature: String) { + private var idx = 0 + + private fun nextType(internal: Boolean): TypeSignature { + var primitive = true + var dimensions = 0 + var c = signature[idx] + while (c == '[') { + dimensions++ + c = signature[++idx] + } + + val name = if (dimensions > 0 || !internal) { + if (c == 'L') { + primitive = false + val next = signature.indexOf(';', startIndex = idx + 1) + if (next < 0) { + error("error parsing type from $signature, cannot find ';' after index $idx") + } + val start = idx + 1 + idx = next + 1 + signature.substring(start, next) + } else { + signature.substring(idx, ++idx) + } + } else { + signature.substring(idx) + } + + return TypeSignature(name, dimensions, primitive) + } + + private fun parseMethod(): MethodSignature { + if (signature[idx++] != '(') { + error("error parsing $signature, expected to start with '('") + } + + val args = mutableListOf() + + while (signature[idx] != ')') { + args.add(nextType(false)) + } + + idx++ + + return MethodSignature(nextType(false), args) + } + + companion object { + /** + * Parses a standard method descriptor, e.g. + * + * (Ljava/lang/Object;)V for void fun(Object) + */ + fun parseMethod(method: String) = SignatureParser(method).parseMethod() + + /** + * Parses a standard type descriptor, as it appears in a method descriptor, e.g. + * + * L/java/lang/Object; for Object + * [L/java/lang/Object; for Object[] + */ + fun parseType(type: String) = SignatureParser(type).nextType(false) + + /** + * Parses a internal type descriptor, as reported by ASM for method owners, instanceof, etc; e.g. + * + * [L/java/lang/Object; for Object[] + * + * but just + * + * java/lang/Object for Object + */ + fun parseInternalType(type: String) = SignatureParser(type).nextType(true) + } +} + +class MethodSignature( + val returnType: TypeSignature, + val argumentTypes: List +) { + fun referencedTypes() = (argumentTypes + returnType).filter { !it.primitive }.map { it.scalarName } +} + +class TypeSignature( + val scalarName: String, + val dimensions: Int, + val primitive: Boolean +) { + fun isArray() = dimensions > 0 + fun referencedTypes() = if (primitive) emptySet() else setOf(scalarName) + + fun scalarSignature() = TypeSignature(scalarName, 0, primitive) + val name get() = scalarName + "[]".repeat(dimensions) +} diff --git a/core/src/main/kotlin/com/toasttab/expediter/TypeParsers.kt b/core/src/main/kotlin/com/toasttab/expediter/parser/TypeParsers.kt similarity index 78% rename from core/src/main/kotlin/com/toasttab/expediter/TypeParsers.kt rename to core/src/main/kotlin/com/toasttab/expediter/parser/TypeParsers.kt index cef7be1..f6cbd9b 100644 --- a/core/src/main/kotlin/com/toasttab/expediter/TypeParsers.kt +++ b/core/src/main/kotlin/com/toasttab/expediter/parser/TypeParsers.kt @@ -13,7 +13,7 @@ * limitations under the License. */ -package com.toasttab.expediter +package com.toasttab.expediter.parser import com.toasttab.expediter.types.ApplicationType import com.toasttab.expediter.types.FieldAccessType @@ -24,6 +24,8 @@ import org.objectweb.asm.ClassReader import org.objectweb.asm.ClassReader.SKIP_DEBUG import org.objectweb.asm.ClassVisitor import org.objectweb.asm.FieldVisitor +import org.objectweb.asm.Handle +import org.objectweb.asm.Label import org.objectweb.asm.MethodVisitor import org.objectweb.asm.Opcodes import org.objectweb.asm.Opcodes.ASM9 @@ -44,8 +46,21 @@ object TypeParsers { private class ApplicationTypeParser(private val source: String) : ClassVisitor(ASM9, TypeDescriptorParser()) { private val refs: MutableSet> = hashSetOf() + private val referencedTypes: MutableSet = hashSetOf() - fun get() = ApplicationType((cv as TypeDescriptorParser).get(), refs, source) + fun get() = ApplicationType((cv as TypeDescriptorParser).get(), refs, referencedTypes, source) + + override fun visitField( + access: Int, + name: String, + descriptor: String, + signature: String?, + value: Any? + ): FieldVisitor? { + referencedTypes.addAll(SignatureParser.parseType(descriptor).referencedTypes()) + + return super.visitField(access, name, descriptor, signature, value) + } override fun visitMethod( access: Int, @@ -56,6 +71,8 @@ private class ApplicationTypeParser(private val source: String) : ClassVisitor(A ): MethodVisitor { super.visitMethod(access, name, descriptor, signature, exceptions) + referencedTypes.addAll(SignatureParser.parseMethod(descriptor).referencedTypes()) + return object : MethodVisitor(ASM9) { override fun visitMethodInsn( opcode: Int, @@ -80,6 +97,8 @@ private class ApplicationTypeParser(private val source: String) : ClassVisitor(A invokeType ) ) + + referencedTypes.addAll(SignatureParser.parseMethod(descriptor).referencedTypes()) } override fun visitFieldInsn(opcode: Int, owner: String, name: String, descriptor: String) { @@ -97,6 +116,27 @@ private class ApplicationTypeParser(private val source: String) : ClassVisitor(A type ) ) + + referencedTypes.addAll(SignatureParser.parseType(descriptor).referencedTypes()) + } + + override fun visitInvokeDynamicInsn( + name: String, + descriptor: String, + bootstrapMethodHandle: Handle, + vararg bootstrapMethodArguments: Any? + ) { + referencedTypes.addAll(SignatureParser.parseMethod(descriptor).referencedTypes()) + } + + override fun visitTryCatchBlock(start: Label?, end: Label?, handler: Label?, type: String?) { + if (type != null) { + referencedTypes.add(type) + } + } + + override fun visitTypeInsn(opcode: Int, type: String) { + referencedTypes.addAll(SignatureParser.parseInternalType(type).referencedTypes()) } } } diff --git a/core/src/main/kotlin/com/toasttab/expediter/ApplicationTypesProvider.kt b/core/src/main/kotlin/com/toasttab/expediter/provider/ApplicationTypesProvider.kt similarity index 94% rename from core/src/main/kotlin/com/toasttab/expediter/ApplicationTypesProvider.kt rename to core/src/main/kotlin/com/toasttab/expediter/provider/ApplicationTypesProvider.kt index f599150..68ec76d 100644 --- a/core/src/main/kotlin/com/toasttab/expediter/ApplicationTypesProvider.kt +++ b/core/src/main/kotlin/com/toasttab/expediter/provider/ApplicationTypesProvider.kt @@ -13,7 +13,7 @@ * limitations under the License. */ -package com.toasttab.expediter +package com.toasttab.expediter.provider import com.toasttab.expediter.types.ApplicationType diff --git a/core/src/main/kotlin/com/toasttab/expediter/ClasspathApplicationTypesProvider.kt b/core/src/main/kotlin/com/toasttab/expediter/provider/ClasspathApplicationTypesProvider.kt similarity index 85% rename from core/src/main/kotlin/com/toasttab/expediter/ClasspathApplicationTypesProvider.kt rename to core/src/main/kotlin/com/toasttab/expediter/provider/ClasspathApplicationTypesProvider.kt index 3923905..1abc949 100644 --- a/core/src/main/kotlin/com/toasttab/expediter/ClasspathApplicationTypesProvider.kt +++ b/core/src/main/kotlin/com/toasttab/expediter/provider/ClasspathApplicationTypesProvider.kt @@ -13,8 +13,10 @@ * limitations under the License. */ -package com.toasttab.expediter +package com.toasttab.expediter.provider +import com.toasttab.expediter.parser.TypeParsers +import com.toasttab.expediter.scanner.ClasspathScanner import java.io.File class ClasspathApplicationTypesProvider( diff --git a/core/src/main/kotlin/com/toasttab/expediter/types/InMemoryPlatformTypeProvider.kt b/core/src/main/kotlin/com/toasttab/expediter/provider/InMemoryPlatformTypeProvider.kt similarity index 95% rename from core/src/main/kotlin/com/toasttab/expediter/types/InMemoryPlatformTypeProvider.kt rename to core/src/main/kotlin/com/toasttab/expediter/provider/InMemoryPlatformTypeProvider.kt index 47e83b1..ae070b9 100644 --- a/core/src/main/kotlin/com/toasttab/expediter/types/InMemoryPlatformTypeProvider.kt +++ b/core/src/main/kotlin/com/toasttab/expediter/provider/InMemoryPlatformTypeProvider.kt @@ -13,7 +13,7 @@ * limitations under the License. */ -package com.toasttab.expediter.types +package com.toasttab.expediter.provider import protokt.v1.toasttab.expediter.v1.TypeDescriptor diff --git a/core/src/main/kotlin/com/toasttab/expediter/types/JvmTypeProvider.kt b/core/src/main/kotlin/com/toasttab/expediter/provider/JvmTypeProvider.kt similarity index 97% rename from core/src/main/kotlin/com/toasttab/expediter/types/JvmTypeProvider.kt rename to core/src/main/kotlin/com/toasttab/expediter/provider/JvmTypeProvider.kt index b4bcb27..7ac203c 100644 --- a/core/src/main/kotlin/com/toasttab/expediter/types/JvmTypeProvider.kt +++ b/core/src/main/kotlin/com/toasttab/expediter/provider/JvmTypeProvider.kt @@ -13,9 +13,9 @@ * limitations under the License. */ -package com.toasttab.expediter.types +package com.toasttab.expediter.provider -import com.toasttab.expediter.TypeParsers +import com.toasttab.expediter.parser.TypeParsers import protokt.v1.toasttab.expediter.v1.TypeDescriptor import java.nio.file.Paths import java.util.jar.JarFile diff --git a/core/src/main/kotlin/com/toasttab/expediter/types/PlatformClassloaderTypeProvider.kt b/core/src/main/kotlin/com/toasttab/expediter/provider/PlatformClassloaderTypeProvider.kt similarity index 90% rename from core/src/main/kotlin/com/toasttab/expediter/types/PlatformClassloaderTypeProvider.kt rename to core/src/main/kotlin/com/toasttab/expediter/provider/PlatformClassloaderTypeProvider.kt index 570dc7e..9b20213 100644 --- a/core/src/main/kotlin/com/toasttab/expediter/types/PlatformClassloaderTypeProvider.kt +++ b/core/src/main/kotlin/com/toasttab/expediter/provider/PlatformClassloaderTypeProvider.kt @@ -13,9 +13,9 @@ * limitations under the License. */ -package com.toasttab.expediter.types +package com.toasttab.expediter.provider -import com.toasttab.expediter.TypeParsers +import com.toasttab.expediter.parser.TypeParsers object PlatformClassloaderTypeProvider : PlatformTypeProvider { override fun lookupPlatformType(name: String) = diff --git a/core/src/main/kotlin/com/toasttab/expediter/types/PlatformTypeProvider.kt b/core/src/main/kotlin/com/toasttab/expediter/provider/PlatformTypeProvider.kt similarity index 94% rename from core/src/main/kotlin/com/toasttab/expediter/types/PlatformTypeProvider.kt rename to core/src/main/kotlin/com/toasttab/expediter/provider/PlatformTypeProvider.kt index b81fa02..cd47c89 100644 --- a/core/src/main/kotlin/com/toasttab/expediter/types/PlatformTypeProvider.kt +++ b/core/src/main/kotlin/com/toasttab/expediter/provider/PlatformTypeProvider.kt @@ -13,7 +13,7 @@ * limitations under the License. */ -package com.toasttab.expediter.types +package com.toasttab.expediter.provider import protokt.v1.toasttab.expediter.v1.TypeDescriptor diff --git a/core/src/main/kotlin/com/toasttab/expediter/types/PlatformTypeProviderChain.kt b/core/src/main/kotlin/com/toasttab/expediter/provider/PlatformTypeProviderChain.kt similarity index 95% rename from core/src/main/kotlin/com/toasttab/expediter/types/PlatformTypeProviderChain.kt rename to core/src/main/kotlin/com/toasttab/expediter/provider/PlatformTypeProviderChain.kt index fe7093a..9b8a3db 100644 --- a/core/src/main/kotlin/com/toasttab/expediter/types/PlatformTypeProviderChain.kt +++ b/core/src/main/kotlin/com/toasttab/expediter/provider/PlatformTypeProviderChain.kt @@ -13,7 +13,7 @@ * limitations under the License. */ -package com.toasttab.expediter.types +package com.toasttab.expediter.provider class PlatformTypeProviderChain( private val providers: Collection diff --git a/core/src/main/kotlin/com/toasttab/expediter/ClasspathScanner.kt b/core/src/main/kotlin/com/toasttab/expediter/scanner/ClasspathScanner.kt similarity index 98% rename from core/src/main/kotlin/com/toasttab/expediter/ClasspathScanner.kt rename to core/src/main/kotlin/com/toasttab/expediter/scanner/ClasspathScanner.kt index e98b7af..329a5f2 100644 --- a/core/src/main/kotlin/com/toasttab/expediter/ClasspathScanner.kt +++ b/core/src/main/kotlin/com/toasttab/expediter/scanner/ClasspathScanner.kt @@ -13,7 +13,7 @@ * limitations under the License. */ -package com.toasttab.expediter +package com.toasttab.expediter.scanner import java.io.File import java.io.InputStream diff --git a/core/src/main/kotlin/com/toasttab/expediter/types/ArrayDescriptor.kt b/core/src/main/kotlin/com/toasttab/expediter/types/ArrayDescriptor.kt index 24073b1..cbb50e9 100644 --- a/core/src/main/kotlin/com/toasttab/expediter/types/ArrayDescriptor.kt +++ b/core/src/main/kotlin/com/toasttab/expediter/types/ArrayDescriptor.kt @@ -56,9 +56,8 @@ object ArrayDescriptor { } ) - fun isArray(typeName: String) = typeName.startsWith("[") - fun create(typeName: String) = TypeDescriptor { - name = typeName + fun create(typeSignature: String) = TypeDescriptor { + name = typeSignature superName = "java/lang/Object" interfaces = INTERFACES protection = AccessProtection.PUBLIC diff --git a/core/src/main/kotlin/com/toasttab/expediter/types/InspectedTypes.kt b/core/src/main/kotlin/com/toasttab/expediter/types/InspectedTypes.kt index 4d58a91..294204c 100644 --- a/core/src/main/kotlin/com/toasttab/expediter/types/InspectedTypes.kt +++ b/core/src/main/kotlin/com/toasttab/expediter/types/InspectedTypes.kt @@ -16,7 +16,9 @@ package com.toasttab.expediter.types import com.toasttab.expediter.issue.Issue -import protokt.v1.toasttab.expediter.v1.TypeDescriptor +import com.toasttab.expediter.parser.SignatureParser +import com.toasttab.expediter.parser.TypeSignature +import com.toasttab.expediter.provider.PlatformTypeProvider import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentMap @@ -30,15 +32,15 @@ private class ApplicationTypeContainer( val types = HashMap() for (type in all) { - types.compute(type.type.name) { _, v -> + types.compute(type.name) { _, v -> if (v == null) { type } else { - val a = duplicates[type.type.name] + val a = duplicates[type.name] if (a != null) { a.add(type.source) } else { - duplicates[type.type.name] = mutableListOf(type.source, v.source) + duplicates[type.name] = mutableListOf(type.source, v.source) } v @@ -55,48 +57,51 @@ class InspectedTypes private constructor( private val appTypes: ApplicationTypeContainer, private val platformTypeProvider: PlatformTypeProvider ) { - private val inspectedCache: ConcurrentMap = appTypes.appTypes.mapValuesTo(ConcurrentHashMap()) { - it.value.type - } + private val inspectedCache: ConcurrentMap = ConcurrentHashMap(appTypes.appTypes) - private val hierarchyCache: MutableMap = hashMapOf() + private val hierarchyCache: MutableMap = hashMapOf() constructor(all: List, platformTypeProvider: PlatformTypeProvider) : this(ApplicationTypeContainer.create(all), platformTypeProvider) - private fun lookup(typeName: String): TypeDescriptor? { - return inspectedCache[typeName] ?: inspectedCache.computeIfAbsent(typeName) { _ -> - if (ArrayDescriptor.isArray(typeName)) { - ArrayDescriptor.create(typeName) + private fun lookup(signature: TypeSignature): Type? { + return if (signature.isArray()) { + if (signature.primitive || lookup(signature.scalarSignature()) != null) { + PlatformType(ArrayDescriptor.create(signature.name)) } else { - platformTypeProvider.lookupPlatformType(typeName) + null + } + } else { + inspectedCache[signature.scalarName] ?: inspectedCache.computeIfAbsent(signature.scalarName) { _ -> + platformTypeProvider.lookupPlatformType(signature.scalarName)?.let(::PlatformType) } } } - private fun traverse(cls: TypeDescriptor): TypeHierarchy { - val cached = hierarchyCache[cls] + private fun traverse(type: Type): TypeHierarchy { + val cached = hierarchyCache[type.name] if (cached == null) { val superTypes = mutableSetOf() val superTypeNames = mutableListOf() - cls.superName?.let { superTypeNames.add(it) } - superTypeNames.addAll(cls.interfaces) + type.descriptor.superName?.let { superTypeNames.add(it) } + superTypeNames.addAll(type.descriptor.interfaces) - for (s in superTypeNames) { - val l = lookup(s) + for (superName in superTypeNames) { + val signature = SignatureParser.parseInternalType(superName) + val superType = lookup(signature) - if (l == null) { - superTypes.add(OptionalType.MissingType(s)) + if (superType == null) { + superTypes.add(OptionalType.MissingType(signature.name)) } else { - val hierarchy = traverse(l) - superTypes.add(OptionalType.Type(l)) + val hierarchy = traverse(superType) + superTypes.add(OptionalType.PresentType(superType)) superTypes.addAll(hierarchy.superTypes) } } - return TypeHierarchy(cls, superTypes).also { - hierarchyCache[cls] = it + return TypeHierarchy(type, superTypes).also { + hierarchyCache[type.name] = it } } else { return cached @@ -104,10 +109,11 @@ class InspectedTypes private constructor( } fun resolveHierarchy(type: String): OptionalResolvedTypeHierarchy { - return lookup(type)?.let { resolveHierarchy(it) } ?: OptionalResolvedTypeHierarchy.NoType + val signature = SignatureParser.parseInternalType(type) + return lookup(signature)?.let { resolveHierarchy(it) } ?: OptionalResolvedTypeHierarchy.NoType(signature.name) } - fun resolveHierarchy(type: TypeDescriptor): ResolvedTypeHierarchy { + fun resolveHierarchy(type: Type): ResolvedTypeHierarchy { return traverse(type).resolve() } diff --git a/core/src/main/kotlin/com/toasttab/expediter/types/TypeHierarchy.kt b/core/src/main/kotlin/com/toasttab/expediter/types/TypeHierarchy.kt index 2560e3b..1939fe7 100644 --- a/core/src/main/kotlin/com/toasttab/expediter/types/TypeHierarchy.kt +++ b/core/src/main/kotlin/com/toasttab/expediter/types/TypeHierarchy.kt @@ -15,10 +15,8 @@ package com.toasttab.expediter.types -import protokt.v1.toasttab.expediter.v1.TypeDescriptor - class TypeHierarchy( - val type: TypeDescriptor, + val type: Type, val superTypes: Set ) { fun resolve(): ResolvedTypeHierarchy { @@ -28,7 +26,7 @@ class TypeHierarchy( } else { ResolvedTypeHierarchy.CompleteTypeHierarchy( type, - superTypes.asSequence().filterIsInstance().map { it.cls } + superTypes.asSequence().filterIsInstance().map { it.type } ) } } @@ -40,9 +38,9 @@ class TypeHierarchy( sealed class OptionalType { abstract val name: String - class Type(val cls: TypeDescriptor) : OptionalType() { + class PresentType(val type: Type) : OptionalType() { override val name: String - get() = cls.name + get() = type.name } class MissingType(override val name: String) : OptionalType() @@ -60,15 +58,16 @@ sealed class OptionalType { } sealed interface OptionalResolvedTypeHierarchy { - object NoType : OptionalResolvedTypeHierarchy + class NoType(val name: String) : OptionalResolvedTypeHierarchy } sealed interface ResolvedTypeHierarchy : OptionalResolvedTypeHierarchy, IdentifiesType { - val type: TypeDescriptor + val type: Type + override val name get() = type.name - class IncompleteTypeHierarchy(override val type: TypeDescriptor, val missingType: Set) : ResolvedTypeHierarchy - class CompleteTypeHierarchy(override val type: TypeDescriptor, val superTypes: Sequence) : ResolvedTypeHierarchy { - val allTypes: Sequence get() = sequenceOf(type) + superTypes + class IncompleteTypeHierarchy(override val type: Type, val missingType: Set) : ResolvedTypeHierarchy + class CompleteTypeHierarchy(override val type: Type, val superTypes: Sequence) : ResolvedTypeHierarchy { + val allTypes: Sequence get() = sequenceOf(type) + superTypes } } diff --git a/core/src/test/kotlin/com/toasttab/expediter/types/JvmTypeProviderTest.kt b/core/src/test/kotlin/com/toasttab/expediter/types/JvmTypeProviderTest.kt index 6a9a220..9b24f54 100644 --- a/core/src/test/kotlin/com/toasttab/expediter/types/JvmTypeProviderTest.kt +++ b/core/src/test/kotlin/com/toasttab/expediter/types/JvmTypeProviderTest.kt @@ -15,6 +15,7 @@ package com.toasttab.expediter.types +import com.toasttab.expediter.provider.JvmTypeProvider import org.junit.jupiter.api.Test import protokt.v1.toasttab.expediter.v1.SymbolicReference import strikt.api.expectThat diff --git a/model/src/main/kotlin/com/toasttab/expediter/types/TypeDescriptor.kt b/model/src/main/kotlin/com/toasttab/expediter/types/Type.kt similarity index 76% rename from model/src/main/kotlin/com/toasttab/expediter/types/TypeDescriptor.kt rename to model/src/main/kotlin/com/toasttab/expediter/types/Type.kt index bdc1f59..029ddc2 100644 --- a/model/src/main/kotlin/com/toasttab/expediter/types/TypeDescriptor.kt +++ b/model/src/main/kotlin/com/toasttab/expediter/types/Type.kt @@ -22,17 +22,26 @@ interface IdentifiesType { val name: String } -val TypeDescriptor.members: Sequence get() = fields.asSequence() + methods.asSequence() +sealed interface Type : IdentifiesType { + val descriptor: TypeDescriptor + + override val name: String get() = descriptor.name +} /** * Represents declared properties of a type and all fields / methods that type's code accesses / invokes. */ class ApplicationType( - val type: TypeDescriptor, - val refs: Set>, + override val descriptor: TypeDescriptor, + val memberAccess: Set>, + val referencedTypes: Set, val source: String -) : IdentifiesType { - override val name = type.name - +) : Type { override fun toString() = "ApplicationType[$name]" } + +class PlatformType( + override val descriptor: TypeDescriptor +) : Type + +val TypeDescriptor.members: Sequence get() = fields.asSequence() + methods.asSequence() diff --git a/plugin/src/main/kotlin/com/toasttab/expediter/gradle/ExpediterTask.kt b/plugin/src/main/kotlin/com/toasttab/expediter/gradle/ExpediterTask.kt index e421e92..b1c11b9 100644 --- a/plugin/src/main/kotlin/com/toasttab/expediter/gradle/ExpediterTask.kt +++ b/plugin/src/main/kotlin/com/toasttab/expediter/gradle/ExpediterTask.kt @@ -15,18 +15,18 @@ package com.toasttab.expediter.gradle -import com.toasttab.expediter.ClasspathApplicationTypesProvider -import com.toasttab.expediter.ClasspathScanner import com.toasttab.expediter.Expediter -import com.toasttab.expediter.TypeParsers import com.toasttab.expediter.ignore.Ignore import com.toasttab.expediter.issue.IssueReport +import com.toasttab.expediter.parser.TypeParsers +import com.toasttab.expediter.provider.ClasspathApplicationTypesProvider +import com.toasttab.expediter.provider.InMemoryPlatformTypeProvider +import com.toasttab.expediter.provider.JvmTypeProvider +import com.toasttab.expediter.provider.PlatformClassloaderTypeProvider +import com.toasttab.expediter.provider.PlatformTypeProvider +import com.toasttab.expediter.provider.PlatformTypeProviderChain +import com.toasttab.expediter.scanner.ClasspathScanner import com.toasttab.expediter.sniffer.AnimalSnifferParser -import com.toasttab.expediter.types.InMemoryPlatformTypeProvider -import com.toasttab.expediter.types.JvmTypeProvider -import com.toasttab.expediter.types.PlatformClassloaderTypeProvider -import com.toasttab.expediter.types.PlatformTypeProvider -import com.toasttab.expediter.types.PlatformTypeProviderChain import org.gradle.api.DefaultTask import org.gradle.api.GradleException import org.gradle.api.artifacts.ArtifactCollection diff --git a/plugin/src/test/kotlin/com/toasttab/expediter/gradle/ExpediterPluginIntegrationTest.kt b/plugin/src/test/kotlin/com/toasttab/expediter/gradle/ExpediterPluginIntegrationTest.kt index fc25b19..e6ff523 100644 --- a/plugin/src/test/kotlin/com/toasttab/expediter/gradle/ExpediterPluginIntegrationTest.kt +++ b/plugin/src/test/kotlin/com/toasttab/expediter/gradle/ExpediterPluginIntegrationTest.kt @@ -34,9 +34,7 @@ class ExpediterPluginIntegrationTest { fun `android compat`(project: TestProject) { project.createRunner() .withArguments("check") - .buildAndFail().let { - println(it.output) - } + .buildAndFail() val report = IssueReport.fromJson(project.dir.resolve("build/expediter.json").readText()) @@ -81,7 +79,9 @@ class ExpediterPluginIntegrationTest { ), MethodAccessType.VIRTUAL ) - ) + ), + + Issue.MissingType("test/Caller", "java/util/function/Function") ) } diff --git a/plugin/src/test/projects/ExpediterPluginIntegrationTest/ignore file/expediter-ignore.json b/plugin/src/test/projects/ExpediterPluginIntegrationTest/ignore file/expediter-ignore.json index 102c27a..93d07fc 100644 --- a/plugin/src/test/projects/ExpediterPluginIntegrationTest/ignore file/expediter-ignore.json +++ b/plugin/src/test/projects/ExpediterPluginIntegrationTest/ignore file/expediter-ignore.json @@ -146,6 +146,11 @@ "caller": "com/fasterxml/jackson/databind/deser/BeanDeserializerBase", "target": "com/fasterxml/jackson/core/StreamReadConstraints" }, + { + "type": "type-missing", + "caller": "com/fasterxml/jackson/databind/deser/BeanDeserializer", + "target": "com/fasterxml/jackson/core/StreamReadConstraints" + }, { "type": "type-missing", "caller": "com/fasterxml/jackson/databind/deser/std/NumberDeserializers$DoubleDeserializer", @@ -156,11 +161,21 @@ "caller": "com/fasterxml/jackson/databind/deser/std/NumberDeserializers$BigIntegerDeserializer", "target": "com/fasterxml/jackson/core/StreamReadConstraints" }, + { + "type": "type-missing", + "caller": "com/fasterxml/jackson/databind/deser/impl/UnwrappedPropertyHandler", + "target": "com/fasterxml/jackson/core/StreamReadConstraints" + }, { "type": "type-missing", "caller": "com/fasterxml/jackson/databind/deser/std/NumberDeserializers$BigDecimalDeserializer", "target": "com/fasterxml/jackson/core/StreamReadConstraints" }, + { + "type": "type-missing", + "caller": "com/fasterxml/jackson/databind/deser/BuilderBasedDeserializer", + "target": "com/fasterxml/jackson/core/StreamReadConstraints" + }, { "type": "type-missing", "caller": "com/fasterxml/jackson/databind/node/BaseJsonNode", @@ -288,6 +303,11 @@ "accessType": "STATIC" } }, + { + "type": "type-missing", + "caller": "com/fasterxml/jackson/databind/node/BaseJsonNode", + "target": "com/fasterxml/jackson/core/exc/StreamConstraintsException" + }, { "type": "member-missing", "caller": "com/fasterxml/jackson/databind/deser/std/NumberDeserializers$NumberDeserializer", @@ -295,8 +315,8 @@ "type": "method", "targetType": "com/fasterxml/jackson/core/io/NumberInput", "ref": { - "name": "parseDouble", - "signature": "(Ljava/lang/String;Z)D" + "name": "parseBigDecimal", + "signature": "(Ljava/lang/String;Z)Ljava/math/BigDecimal;" }, "accessType": "STATIC" } @@ -308,8 +328,8 @@ "type": "method", "targetType": "com/fasterxml/jackson/core/io/NumberInput", "ref": { - "name": "parseBigDecimal", - "signature": "(Ljava/lang/String;Z)Ljava/math/BigDecimal;" + "name": "parseBigInteger", + "signature": "(Ljava/lang/String;Z)Ljava/math/BigInteger;" }, "accessType": "STATIC" } @@ -321,8 +341,8 @@ "type": "method", "targetType": "com/fasterxml/jackson/core/io/NumberInput", "ref": { - "name": "parseBigInteger", - "signature": "(Ljava/lang/String;Z)Ljava/math/BigInteger;" + "name": "parseDouble", + "signature": "(Ljava/lang/String;Z)D" }, "accessType": "STATIC" } @@ -334,8 +354,8 @@ "type": "method", "targetType": "com/fasterxml/jackson/core/io/NumberInput", "ref": { - "name": "parseDouble", - "signature": "(Ljava/lang/String;Z)D" + "name": "parseBigInteger", + "signature": "(Ljava/lang/String;Z)Ljava/math/BigInteger;" }, "accessType": "STATIC" } @@ -360,8 +380,8 @@ "type": "method", "targetType": "com/fasterxml/jackson/core/io/NumberInput", "ref": { - "name": "parseBigInteger", - "signature": "(Ljava/lang/String;Z)Ljava/math/BigInteger;" + "name": "parseDouble", + "signature": "(Ljava/lang/String;Z)D" }, "accessType": "STATIC" } @@ -412,8 +432,8 @@ "type": "method", "targetType": "com/fasterxml/jackson/core/io/NumberInput", "ref": { - "name": "parseDouble", - "signature": "(Ljava/lang/String;Z)D" + "name": "parseFloat", + "signature": "(Ljava/lang/String;Z)F" }, "accessType": "STATIC" } @@ -425,8 +445,8 @@ "type": "method", "targetType": "com/fasterxml/jackson/core/io/NumberInput", "ref": { - "name": "parseFloat", - "signature": "(Ljava/lang/String;)F" + "name": "parseDouble", + "signature": "(Ljava/lang/String;Z)D" }, "accessType": "STATIC" } @@ -439,7 +459,7 @@ "targetType": "com/fasterxml/jackson/core/io/NumberInput", "ref": { "name": "parseFloat", - "signature": "(Ljava/lang/String;Z)F" + "signature": "(Ljava/lang/String;)F" }, "accessType": "STATIC" } diff --git a/tests/base/src/main/java/com/toasttab/expediter/test/Param.java b/tests/base/src/main/java/com/toasttab/expediter/test/Param.java new file mode 100644 index 0000000..cbcc889 --- /dev/null +++ b/tests/base/src/main/java/com/toasttab/expediter/test/Param.java @@ -0,0 +1,4 @@ +package com.toasttab.expediter.test; + +public class Param { +} diff --git a/tests/base/src/main/java/com/toasttab/expediter/test/ParamParam.java b/tests/base/src/main/java/com/toasttab/expediter/test/ParamParam.java new file mode 100644 index 0000000..cd7d4f5 --- /dev/null +++ b/tests/base/src/main/java/com/toasttab/expediter/test/ParamParam.java @@ -0,0 +1,4 @@ +package com.toasttab.expediter.test; + +public class ParamParam { +} diff --git a/tests/lib1/src/main/java/com/toasttab/expediter/test/Bar.java b/tests/lib1/src/main/java/com/toasttab/expediter/test/Bar.java index b5e9885..ae8ac64 100644 --- a/tests/lib1/src/main/java/com/toasttab/expediter/test/Bar.java +++ b/tests/lib1/src/main/java/com/toasttab/expediter/test/Bar.java @@ -28,4 +28,6 @@ public void bar(int x) { } public void bar(long x) { } public void bar(float x) { } + + public void arg(Param[][] param) { } } diff --git a/tests/lib1/src/main/java/com/toasttab/expediter/test/Ex.java b/tests/lib1/src/main/java/com/toasttab/expediter/test/Ex.java new file mode 100644 index 0000000..901648f --- /dev/null +++ b/tests/lib1/src/main/java/com/toasttab/expediter/test/Ex.java @@ -0,0 +1,4 @@ +package com.toasttab.expediter.test; + +public class Ex extends RuntimeException { +} diff --git a/tests/lib1/src/main/java/com/toasttab/expediter/test/Lambda.java b/tests/lib1/src/main/java/com/toasttab/expediter/test/Lambda.java new file mode 100644 index 0000000..ee22e7c --- /dev/null +++ b/tests/lib1/src/main/java/com/toasttab/expediter/test/Lambda.java @@ -0,0 +1,6 @@ +package com.toasttab.expediter.test; + +@FunctionalInterface +public interface Lambda { + void x(); +} diff --git a/tests/lib1/src/main/java/com/toasttab/expediter/test/Var.java b/tests/lib1/src/main/java/com/toasttab/expediter/test/Var.java new file mode 100644 index 0000000..dd55cdf --- /dev/null +++ b/tests/lib1/src/main/java/com/toasttab/expediter/test/Var.java @@ -0,0 +1,4 @@ +package com.toasttab.expediter.test; + +public class Var { +} diff --git a/tests/lib2/src/main/java/com/toasttab/expediter/test/Bar.java b/tests/lib2/src/main/java/com/toasttab/expediter/test/Bar.java index 14deaf7..a3dec65 100644 --- a/tests/lib2/src/main/java/com/toasttab/expediter/test/Bar.java +++ b/tests/lib2/src/main/java/com/toasttab/expediter/test/Bar.java @@ -32,4 +32,6 @@ void bar(long x) { } // changes from public to protected protected void bar(float x) { } + + public void arg(Param[][] param) { } } diff --git a/tests/src/main/java/com/toasttab/expediter/test/caller/Caller.java b/tests/src/main/java/com/toasttab/expediter/test/caller/Caller.java index 5bc95c4..3e17876 100644 --- a/tests/src/main/java/com/toasttab/expediter/test/caller/Caller.java +++ b/tests/src/main/java/com/toasttab/expediter/test/caller/Caller.java @@ -17,13 +17,17 @@ import com.toasttab.expediter.test.Bar; import com.toasttab.expediter.test.Base; -import com.toasttab.expediter.test.BaseFoo; import com.toasttab.expediter.test.Baz; +import com.toasttab.expediter.test.Ex; import com.toasttab.expediter.test.Foo; +import com.toasttab.expediter.test.Lambda; +import com.toasttab.expediter.test.ParamParam; +import com.toasttab.expediter.test.Var; + +import java.util.stream.Stream; public final class Caller extends Base { Foo foo; - BaseFoo baseFoo; Bar bar; void missingMethod() { @@ -38,10 +42,6 @@ void missingSuper() { foo.base(); } - void missingType() { - baseFoo.base(); - } - void privateMethod() { bar.bar(1); } @@ -81,4 +81,28 @@ void fieldMovedFromSuper() { void fieldAccessedViaPublicSubclass() { bar.i = 1; } void accessProtectedField() { w = 0; } + + void missingTypeLocalVar() { + Var v = new Var(); + } + + void missingIndyLambda() { + Lambda l = () -> { }; + } + + void missingTypeException() { + try { + new Object(); + } catch (Ex e) { + + } + } + + void missingTypeMethodArg() { + bar.arg(null); + } + + boolean missingTypeInstanceof(Object o) { + return o instanceof ParamParam[]; + } } diff --git a/tests/src/main/java/com/toasttab/expediter/test/caller/VarVar.java b/tests/src/main/java/com/toasttab/expediter/test/caller/VarVar.java new file mode 100644 index 0000000..9278598 --- /dev/null +++ b/tests/src/main/java/com/toasttab/expediter/test/caller/VarVar.java @@ -0,0 +1,6 @@ +package com.toasttab.expediter.test.caller; + +import com.toasttab.expediter.test.Var; + +public class VarVar extends Var { +} diff --git a/tests/src/test/kotlin/com/toasttab/expediter/test/ExpediterIntegrationTest.kt b/tests/src/test/kotlin/com/toasttab/expediter/test/ExpediterIntegrationTest.kt index 35d24ac..d4a6c26 100644 --- a/tests/src/test/kotlin/com/toasttab/expediter/test/ExpediterIntegrationTest.kt +++ b/tests/src/test/kotlin/com/toasttab/expediter/test/ExpediterIntegrationTest.kt @@ -15,15 +15,15 @@ package com.toasttab.expediter.test -import com.toasttab.expediter.ClasspathApplicationTypesProvider import com.toasttab.expediter.Expediter import com.toasttab.expediter.ignore.Ignore import com.toasttab.expediter.issue.Issue +import com.toasttab.expediter.provider.ClasspathApplicationTypesProvider +import com.toasttab.expediter.provider.PlatformClassloaderTypeProvider import com.toasttab.expediter.types.FieldAccessType import com.toasttab.expediter.types.MemberAccess import com.toasttab.expediter.types.MemberSymbolicReference import com.toasttab.expediter.types.MethodAccessType -import com.toasttab.expediter.types.PlatformClassloaderTypeProvider import org.junit.jupiter.api.Test import strikt.api.expectThat import strikt.assertions.containsExactlyInAnyOrder @@ -140,30 +140,54 @@ class ExpediterIntegrationTest { ) ), - Issue.MissingSuperType( - "com/toasttab/expediter/test/caller/Caller", + Issue.MissingApplicationSuperType( "com/toasttab/expediter/test/Foo", setOf("com/toasttab/expediter/test/BaseFoo") ), + Issue.DuplicateType( + "com/toasttab/expediter/test/Dupe", + listOf("main", "lib2.jar") + ), + + Issue.FinalApplicationSuperType( + "com/toasttab/expediter/test/caller/Caller", + setOf("com/toasttab/expediter/test/Base") + ), + Issue.MissingType( "com/toasttab/expediter/test/caller/Caller", - "com/toasttab/expediter/test/BaseFoo" + "com/toasttab/expediter/test/Var" ), Issue.MissingApplicationSuperType( - "com/toasttab/expediter/test/Foo", - setOf("com/toasttab/expediter/test/BaseFoo") + "com/toasttab/expediter/test/caller/VarVar", + setOf("com/toasttab/expediter/test/Var") ), - Issue.DuplicateType( - "com/toasttab/expediter/test/Dupe", - listOf("main", "lib2.jar") + Issue.MissingType( + "com/toasttab/expediter/test/caller/Caller", + "com/toasttab/expediter/test/Ex" ), - Issue.FinalApplicationSuperType( + Issue.MissingType( "com/toasttab/expediter/test/caller/Caller", - setOf("com/toasttab/expediter/test/Base") + "com/toasttab/expediter/test/Param" + ), + + Issue.MissingType( + "com/toasttab/expediter/test/caller/Caller", + "com/toasttab/expediter/test/ParamParam" + ), + + Issue.MissingType( + "com/toasttab/expediter/test/Bar", + "com/toasttab/expediter/test/Param" + ), + + Issue.MissingType( + "com/toasttab/expediter/test/caller/Caller", + "com/toasttab/expediter/test/Lambda" ) ) }