Make sure malformed types don't break algos

This commit is contained in:
Clément Fournier committed 2021-05-07 19:11:55 +02:00
1 parent 7eefea221d
commit d4dd12b943
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();
@@ -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> {