Paul King created GROOVY-12184:
----------------------------------
Summary: unsound cast for property access on union type
(instanceof A || instanceof B) throws ClassCastException (fix for GROOVY_5_0_X)
Key: GROOVY-12184
URL: https://issues.apache.org/jira/browse/GROOVY-12184
Project: Groovy
Issue Type: Bug
Reporter: Paul King
Under {{@CompileStatic}} (and {{@TypeChecked}}), accessing a property on a
receiver whose flow-typed type is a union from {{instanceof A || instanceof B}}
narrows the receiver to the *first* type and emits a hard cast to it. When the
object is the other type, this throws {{ClassCastException}} at runtime.
{code:groovy}
import groovy.transform.CompileStatic
@CompileStatic
class X1 { Long id }
@CompileStatic
class X2 { Long id }
@CompileStatic
Long computeId(Object test) {
if (test instanceof X1 || test instanceof X2) {
return test.id // narrowed to X1, cast to X1
}
null
}
assert computeId(new X1(id: 11)) == 11L // ok
assert computeId(new X2(id: 22)) == 22L // java.lang.ClassCastException:
class X2 cannot be cast to class X1
{code}
*Expected:* both calls return the {{id}} (12 / 22).
*Actual:* the second call throws {{ClassCastException}}. Reordering the
{{instanceof}} clauses flips which type fails, confirming the receiver is
narrowed to a single delegate rather than their common supertype.
The subscript form ({{test['id']}}) is sound because it routes through dynamic
{{getAt}} at runtime.
Affects 4.0.x and 5.0.x (not a regression). Passes on 6.0 (master), fixed
incidentally by the union/intersection type work in GROOVY-11998.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)