Make sure malformed types don't break algos
This commit is contained in:
12 files changed
+124
-30
No files matched your search
@@ -168,7 +168,7 @@ class ClassTypeImpl implements JClassType {
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings( { "unchecked", "rawtypes" })
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
public List<JTypeMirror> getTypeArgs() {
|
||||
return isGenericTypeDeclaration() ? (List) getFormalTypeParams() : typeArgs;
|
||||
}
|
||||
|
||||
@@ -186,7 +186,7 @@ public final class JIntersectionType implements JTypeMirror {
|
||||
}
|
||||
} else if (ci instanceof JClassType) {
|
||||
// must be an interface, as per isExclusiveBlabla
|
||||
assert ci.isInterface();
|
||||
assert ci.isInterface() || TypeOps.hasUnresolvedSymbol(ci);
|
||||
} else {
|
||||
throw malformedIntersection(primary, flattened);
|
||||
}
|
||||
|
||||
@@ -159,8 +159,6 @@ public final class TypeConversion {
|
||||
List<JTypeMirror> typeArgs = type.getTypeArgs();
|
||||
List<JTypeVar> typeParams = type.getFormalTypeParams();
|
||||
|
||||
assert typeParams.size() == typeArgs.size() : "Type is not well formed " + type + " (expects " + typeParams.size() + " params)";
|
||||
|
||||
// This is the algorithm described at https://docs.oracle.com/javase/specs/jls/se10/html/jls-5.html#jls-5.1.10
|
||||
|
||||
// Let G name a generic type declaration (§8.1.2, §9.1.2)
|
||||
@@ -176,11 +174,14 @@ public final class TypeConversion {
|
||||
|
||||
List<JTypeMirror> freshVars = makeFreshVars(type);
|
||||
|
||||
// types may be non-well formed if the symbol is unresolved
|
||||
// in this case the typeParams list is most likely empty
|
||||
boolean wellFormed = typeParams.size() == freshVars.size();
|
||||
|
||||
// Map of Ai to Si, for the substitution
|
||||
Substitution subst = Substitution.mapping(typeParams, freshVars);
|
||||
Substitution subst = wellFormed ? Substitution.mapping(typeParams, freshVars) : Substitution.EMPTY;
|
||||
|
||||
for (int i = 0; i < typeArgs.size(); i++) {
|
||||
JTypeVar param = typeParams.get(i); // Ai
|
||||
JTypeMirror fresh = freshVars.get(i); // Si
|
||||
JTypeMirror arg = typeArgs.get(i); // Ti
|
||||
|
||||
@@ -191,7 +192,7 @@ public final class TypeConversion {
|
||||
JWildcardType w = (JWildcardType) arg; // Ti alias
|
||||
TypeVarImpl.CapturedTypeVar freshVar = (TypeVarImpl.CapturedTypeVar) fresh; // Si alias
|
||||
|
||||
JTypeMirror prevUpper = param.getUpperBound(); // Ui
|
||||
JTypeMirror prevUpper = wellFormed ? typeParams.get(i).getUpperBound() : ts.OBJECT; // Ui
|
||||
JTypeMirror substituted = TypeOps.subst(prevUpper, subst);
|
||||
|
||||
if (w.isUnbounded()) {
|
||||
|
||||
@@ -387,11 +387,15 @@ public final class TypeOps {
|
||||
} else if (isSpecialUnresolved(t)) {
|
||||
// error type or unresolved type
|
||||
return Convertibility.SUBTYPING;
|
||||
} else if (hasUnresolvedSymbol(t)) {
|
||||
} else if (hasUnresolvedSymbol(t) && t instanceof JClassType) {
|
||||
// This also considers types with an unresolved symbol
|
||||
// subtypes of (nearly) anything. This allows them to
|
||||
// pass bound checks on type variables.
|
||||
return Convertibility.subtypeIf(s instanceof JClassType); // excludes array or so
|
||||
if (Objects.equals(t.getSymbol(), s.getSymbol())) {
|
||||
return typeArgsAreContained((JClassType) t, (JClassType) s);
|
||||
} else {
|
||||
return Convertibility.subtypeIf(s instanceof JClassType); // excludes array or so
|
||||
}
|
||||
} else if (s instanceof JIntersectionType) { // TODO test intersection with tvars & arrays
|
||||
// If S is an intersection, then T must conform to *all* bounds of S
|
||||
// Symmetrically, if T is an intersection, T <: S requires only that
|
||||
@@ -692,6 +696,11 @@ public final class TypeOps {
|
||||
: Convertibility.UNCHECKED_WARNING;
|
||||
}
|
||||
|
||||
if (targs.size() != sargs.size()) {
|
||||
// types are not well-formed
|
||||
return Convertibility.NEVER;
|
||||
}
|
||||
|
||||
Convertibility result = Convertibility.SUBTYPING;
|
||||
for (int i = 0; i < targs.size(); i++) {
|
||||
Convertibility sub = typeArgContains(sargs.get(i), targs.get(i));
|
||||
@@ -1360,8 +1369,8 @@ public final class TypeOps {
|
||||
* this does not check the static modifier, and tests for hiding
|
||||
* if the method is static.
|
||||
*
|
||||
* @param m Method to test
|
||||
* @param origin Site of the potential override
|
||||
* @param m Method to test
|
||||
* @param origin Site of the potential override
|
||||
*/
|
||||
public static boolean isOverridableIn(JExecutableSymbol m, JTypeDeclSymbol origin) {
|
||||
if (m instanceof JConstructorSymbol) {
|
||||
@@ -1664,11 +1673,12 @@ public final class TypeOps {
|
||||
|
||||
// Notice that this loop needs a well-behaved subtyping relation,
|
||||
// i.e. antisymmetric: A <: B && A != B implies not(B <: A)
|
||||
// This is not the case if we include unchecked conversion in there.
|
||||
// This is not the case if we include unchecked conversion in there,
|
||||
// or special provisions for unresolved types.
|
||||
vLoop:
|
||||
for (JTypeMirror v : set) {
|
||||
for (JTypeMirror w : set) {
|
||||
if (!w.equals(v) && isSubtypePure(w, v).bySubtyping()) {
|
||||
if (!w.equals(v) && !hasUnresolvedSymbol(w) && isSubtypePure(w, v).bySubtyping()) {
|
||||
continue vLoop;
|
||||
}
|
||||
}
|
||||
@@ -1938,6 +1948,9 @@ public final class TypeOps {
|
||||
|
||||
public static boolean hasUnresolvedSymbol(@Nullable JTypeMirror t) {
|
||||
if (!(t instanceof JClassType)) {
|
||||
if (t instanceof JArrayType) {
|
||||
return hasUnresolvedSymbol(((JArrayType) t).getElementType());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return t.getSymbol() != null && t.getSymbol().isUnresolved();
|
||||
|
||||
+5
-1
@@ -63,7 +63,7 @@ class BrokenClasspathTest : FunSpec({
|
||||
// and now since the super interface *type* is parameterized, we'll try to create SuperItf<D,D>
|
||||
// Except SuperItf is unresolved.
|
||||
|
||||
val expected = ts.parameterise(unresolvedItfSym, listOf(tvarD, tvarD))
|
||||
val expected = ts.parameterise(unresolvedItfSym, listOf(tvarD, tvarD))
|
||||
|
||||
expected shouldBe superItfType.withTypeArguments(listOf(tvarD, tvarD))
|
||||
subclassType.superInterfaces[0] shouldBe expected
|
||||
@@ -72,3 +72,7 @@ class BrokenClasspathTest : FunSpec({
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
fun TypeSystem.createUnresolvedAsmSymbol(binaryName: String) =
|
||||
AsmSymbolResolver(this, Classpath.contextClasspath())
|
||||
.resolveFromInternalNameCannotFail(binaryName.replace('.', '/'))
|
||||
@@ -7,6 +7,8 @@ package net.sourceforge.pmd.lang.java.types
|
||||
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import net.sourceforge.pmd.lang.java.symbols.JClassSymbol
|
||||
import net.sourceforge.pmd.lang.java.symbols.internal.asm.createUnresolvedAsmSymbol
|
||||
import net.sourceforge.pmd.lang.java.types.TypeConversion.*
|
||||
|
||||
/**
|
||||
@@ -55,6 +57,19 @@ class CaptureTest : FunSpec({
|
||||
}
|
||||
}
|
||||
|
||||
test("Capture of malformed types") {
|
||||
val sym = ts.createUnresolvedAsmSymbol("does.not.Exist") as JClassSymbol
|
||||
|
||||
val matcher = captureMatcher(`?` extends t_String).also {
|
||||
capture(sym[t_String, `?` extends t_String]) shouldBe sym[t_String, it]
|
||||
}
|
||||
|
||||
matcher.also {
|
||||
it.isCaptured shouldBe true
|
||||
it.isCaptureOf(`?` extends t_String) shouldBe true
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,8 +10,8 @@ import io.kotest.matchers.collections.shouldContainExactly
|
||||
import io.kotest.matchers.nulls.shouldBeNull
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.kotest.property.checkAll
|
||||
import io.kotest.property.forAll
|
||||
import net.sourceforge.pmd.lang.ast.test.shouldBeA
|
||||
import net.sourceforge.pmd.lang.java.symbols.internal.asm.createUnresolvedAsmSymbol
|
||||
|
||||
/**
|
||||
* Tests "the greatest lower bound" (glb).
|
||||
@@ -26,8 +26,8 @@ class GlbTest : FunSpec({
|
||||
|
||||
test("Test intersection minimization") {
|
||||
|
||||
forAll(ts.subtypesArb()) { (t, s) ->
|
||||
glb(t, s) == t
|
||||
checkAll(ts.subtypesArb()) { (t, s) ->
|
||||
glb(t, s) shouldBe t
|
||||
}
|
||||
|
||||
// in particular
|
||||
@@ -112,6 +112,20 @@ class GlbTest : FunSpec({
|
||||
it.components.shouldContainExactly(`t_Enum{JPrimitiveType}`, `t_List{String}`, `t_List{? extends Number}`)
|
||||
}
|
||||
}
|
||||
|
||||
test("Test GLB with unresolved things") {
|
||||
val tA = ts.declaration(ts.createUnresolvedAsmSymbol("a.A"))
|
||||
val tB = ts.declaration(ts.createUnresolvedAsmSymbol("a.B"))
|
||||
|
||||
tA shouldBeSubtypeOf tB
|
||||
tB shouldBeSubtypeOf tA
|
||||
|
||||
TypeOps.mostSpecific(setOf(tA, tB)) shouldBe setOf(tA, tB)
|
||||
|
||||
glb(tA, tB).shouldBeA<JIntersectionType> {
|
||||
it.components.shouldContainExactly(tA, tB)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,9 @@ import io.kotest.property.exhaustive.ints
|
||||
import io.kotest.property.forAll
|
||||
import net.sourceforge.pmd.lang.ast.test.shouldBeA
|
||||
import net.sourceforge.pmd.lang.java.ast.ParserTestCtx
|
||||
import net.sourceforge.pmd.lang.java.symbols.JClassSymbol
|
||||
import net.sourceforge.pmd.lang.java.symbols.internal.UnresolvedClassStore
|
||||
import net.sourceforge.pmd.lang.java.symbols.internal.asm.createUnresolvedAsmSymbol
|
||||
import net.sourceforge.pmd.lang.java.types.TypeConversion.*
|
||||
import net.sourceforge.pmd.lang.java.types.TypeOps.Convertibility.*
|
||||
import net.sourceforge.pmd.lang.java.types.testdata.ComparableList
|
||||
@@ -209,7 +211,7 @@ class SubtypingTest : FunSpec({
|
||||
|
||||
test("Test wildcard subtyping (property-based)") {
|
||||
|
||||
checkAll(ts.subtypesArb(false)) { (t, s) ->
|
||||
checkAll(ts.subtypesArb()) { (t, s) ->
|
||||
t_List[t] shouldBeSubtypeOf t_List[`?` extends s]
|
||||
t_List[s] shouldBeSubtypeOf t_List[`?` `super` t]
|
||||
}
|
||||
@@ -260,7 +262,7 @@ class SubtypingTest : FunSpec({
|
||||
|
||||
test("Unresolved symbol is compatible with any class/interface") {
|
||||
val t = ts.declaration(UnresolvedClassStore(ts).makeUnresolvedReference("obj.foo", 0))
|
||||
.shouldBeUnresolvedClass("obj.foo")
|
||||
.shouldBeUnresolvedClass("obj.foo")
|
||||
|
||||
checkAll(ts.primitiveGen) { s ->
|
||||
t shouldNotBeSubtypeOf s
|
||||
@@ -294,7 +296,7 @@ class SubtypingTest : FunSpec({
|
||||
|
||||
test("Captured subtyping wild vs wild") {
|
||||
|
||||
checkAll(ts.subtypesArb(unchecked = false)) { (t, s) ->
|
||||
checkAll(ts.subtypesArb()) { (t, s) ->
|
||||
// println("$t <: $s")
|
||||
|
||||
capture(t_List[`?` extends t]) shouldSubtypeNoCapture t_List[`?` extends s]
|
||||
@@ -315,6 +317,13 @@ class SubtypingTest : FunSpec({
|
||||
t_List[`?` extends t] shouldNotSubtypeNoCapture t_List[`?` extends someCapture]
|
||||
}
|
||||
}
|
||||
|
||||
test("Test non well-formed types") {
|
||||
val sym = ts.createUnresolvedAsmSymbol("does.not.Exist") as JClassSymbol
|
||||
sym[t_String, t_String] shouldBeUnrelatedTo sym[t_String]
|
||||
sym[t_String] shouldBeSubtypeOf sym[t_String]
|
||||
sym[t_String] shouldBeSubtypeOf sym[`?` extends t_String] // containment
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@ import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.shouldNotBe
|
||||
import io.kotest.property.checkAll
|
||||
import io.kotest.property.forAll
|
||||
import net.sourceforge.pmd.lang.java.symbols.JClassSymbol
|
||||
import net.sourceforge.pmd.lang.java.symbols.internal.asm.createUnresolvedAsmSymbol
|
||||
import net.sourceforge.pmd.lang.java.symbols.internal.forAllEqual
|
||||
|
||||
/**
|
||||
@@ -78,6 +80,20 @@ class TypeEqualityTest : FunSpec({
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
test("Test non well-formed types") {
|
||||
val sym = ts.createUnresolvedAsmSymbol("does.not.Exist") as JClassSymbol
|
||||
// not equal
|
||||
sym[t_String, t_String] shouldNotBe sym[t_String]
|
||||
sym[t_String] shouldNotBe sym[t_String, t_String]
|
||||
sym[t_Integer] shouldNotBe sym[t_String]
|
||||
|
||||
// equal
|
||||
sym[t_String, t_String] shouldBe sym[t_String, t_String]
|
||||
sym[t_String] shouldBe sym[t_String]
|
||||
sym[t_String, t_Integer] shouldBe sym[t_String, t_Integer]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ import net.sourceforge.pmd.lang.java.ast.ASTTypeParameter
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTTypeParameters
|
||||
import net.sourceforge.pmd.lang.java.ast.ParserTestCtx
|
||||
import net.sourceforge.pmd.lang.java.symbols.internal.TestClassesGen
|
||||
import net.sourceforge.pmd.lang.java.symbols.internal.asm.createUnresolvedAsmSymbol
|
||||
import javax.lang.model.type.TypeMirror
|
||||
import kotlin.streams.toList
|
||||
|
||||
@@ -31,9 +32,13 @@ val TypeSystem.allTypesGen: Arb<JTypeMirror>
|
||||
}
|
||||
}
|
||||
|
||||
fun TypeSystem.subtypesArb(unchecked: Boolean = false) =
|
||||
/**
|
||||
* Only well-behaved subtypes
|
||||
*/
|
||||
fun TypeSystem.subtypesArb() =
|
||||
Arb.pair(refTypeGen, refTypeGen)
|
||||
.filter { (t, s) -> t.isConvertibleTo(s).bySubtyping() }
|
||||
.filter { (t, s) -> !TypeOps.hasUnresolvedSymbol(t) && !TypeOps.hasUnresolvedSymbol(s) }
|
||||
|
||||
infix fun Boolean.implies(v: () -> Boolean): Boolean = !this || v()
|
||||
|
||||
@@ -47,6 +52,9 @@ class RefTypeGenArb(val ts: TypeSystem) : Arb<JTypeMirror>() {
|
||||
|
||||
private fun generateTypes(rs : RandomSource) : List<JTypeMirror> {
|
||||
with(TypeDslOf(ts).gen) {
|
||||
val unresolved1 = ts.createUnresolvedAsmSymbol("some.fake.Symbol")
|
||||
val unresolved2 = ts.createUnresolvedAsmSymbol("another.fake.Symbol")
|
||||
|
||||
val pool: List<JTypeMirror> = listOf(
|
||||
`t_List{String}`,
|
||||
t_Enum,
|
||||
@@ -61,7 +69,10 @@ class RefTypeGenArb(val ts: TypeSystem) : Arb<JTypeMirror>() {
|
||||
t_CharSequence,
|
||||
t_StringBuilder,
|
||||
t_MapEntry,
|
||||
`t_Array{Object}`
|
||||
`t_Array{Object}`,
|
||||
ts.declaration(unresolved1)[t_String],
|
||||
ts.declaration(unresolved2)[t_Integer, `?` extends `t_List{? extends Number}`],
|
||||
ts.declaration(unresolved2)
|
||||
).flatMap {
|
||||
it.superTypeSet.toList() + it + it.erasure + it.toArray()
|
||||
}
|
||||
|
||||
@@ -8,9 +8,9 @@ import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.collections.shouldContainExactly
|
||||
import io.kotest.matchers.collections.shouldContainExactlyInAnyOrder
|
||||
import io.kotest.property.Arb
|
||||
import io.kotest.property.arbitrary.map
|
||||
import io.kotest.property.arbitrary.shuffle
|
||||
import io.kotest.property.checkAll
|
||||
import net.sourceforge.pmd.lang.java.symbols.internal.asm.createUnresolvedAsmSymbol
|
||||
|
||||
/**
|
||||
* @author Clément Fournier
|
||||
@@ -31,7 +31,7 @@ class TypeOpsTest : FunSpec({
|
||||
|
||||
test("Test most specific") {
|
||||
|
||||
checkAll(ts.subtypesArb(false)) { (t, s) ->
|
||||
checkAll(ts.subtypesArb()) { (t, s) ->
|
||||
TypeOps.mostSpecific(setOf(t, s)).shouldContainExactly(t)
|
||||
}
|
||||
}
|
||||
@@ -43,6 +43,16 @@ class TypeOpsTest : FunSpec({
|
||||
output = listOf(t_AbstractList))
|
||||
}
|
||||
|
||||
test("Test most specific of unresolved types") {
|
||||
val tA = ts.declaration(ts.createUnresolvedAsmSymbol("a.A"))
|
||||
val tB = ts.declaration(ts.createUnresolvedAsmSymbol("a.B"))
|
||||
|
||||
checkMostSpecific(
|
||||
input = listOf(tA, tB),
|
||||
output = listOf(tA, tB)
|
||||
)
|
||||
}
|
||||
|
||||
test("Test most specific unchecked") {
|
||||
|
||||
|
||||
|
||||
@@ -6,17 +6,18 @@
|
||||
package javasymbols.brokenclasses;
|
||||
|
||||
|
||||
/*
|
||||
For this test we compile this file manually, save the classes in the
|
||||
resource tree, but purposefully exclude SuperItf.class to mimic an
|
||||
incomplete classpath.
|
||||
*/
|
||||
|
||||
|
||||
class SuperKlass<A, B> { }
|
||||
interface SuperItf<A, B> { }
|
||||
|
||||
|
||||
/**
|
||||
* For this test we compile this file manually, save the classes in the
|
||||
* resource tree, but purposefully exclude SuperItf.class to mimic an
|
||||
* incomplete classpath.
|
||||
*
|
||||
* @see net.sourceforge.pmd.lang.java.symbols.internal.asm.BrokenClasspathTest
|
||||
*/
|
||||
public class BrokenGeneric<C, D> extends SuperKlass<C, C> implements SuperItf<D, D> {
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user