Skip to content

Commit 781f35e

Browse files
committed
GROOVY-11754: STC: prevent cast exception under imbalanced instanceof
1 parent 7d36495 commit 781f35e

3 files changed

Lines changed: 55 additions & 11 deletions

File tree

src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java

Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,7 @@
266266
import static org.codehaus.groovy.syntax.Types.INTDIV_EQUAL;
267267
import static org.codehaus.groovy.syntax.Types.KEYWORD_IN;
268268
import static org.codehaus.groovy.syntax.Types.KEYWORD_INSTANCEOF;
269+
import static org.codehaus.groovy.syntax.Types.LOGICAL_OR;
269270
import static org.codehaus.groovy.syntax.Types.MINUS_MINUS;
270271
import static org.codehaus.groovy.syntax.Types.MOD;
271272
import static org.codehaus.groovy.syntax.Types.MOD_EQUAL;
@@ -848,10 +849,13 @@ public Expression transform(final Expression expr) {
848849
return;
849850
}
850851

852+
// GROOVY-7971, GROOVY-8965, GROOVY-10702, GROOVY-11754, et al.
853+
if (op == LOGICAL_OR) typeCheckingContext.pushTemporaryTypeInfo();
854+
851855
ClassNode lType;
852856
leftExpression.visit(this);
853857
var setterInfo = removeSetterInfo(leftExpression);
854-
if (setterInfo != null) {
858+
if (setterInfo != null) { assert op != LOGICAL_OR;
855859
if (ensureValidSetter(expression, leftExpression, rightExpression, setterInfo)) {
856860
return;
857861
}
@@ -863,7 +867,16 @@ public Expression transform(final Expression expr) {
863867
} else {
864868
lType = getType(leftExpression);
865869
}
866-
rightExpression.visit(this);
870+
if (op != LOGICAL_OR) {
871+
rightExpression.visit(this);
872+
} else {
873+
var lhs = typeCheckingContext.temporaryIfBranchTypeInformation.pop();
874+
typeCheckingContext.pushTemporaryTypeInfo();
875+
rightExpression.visit(this);
876+
877+
var rhs = typeCheckingContext.temporaryIfBranchTypeInformation.pop();
878+
propagateTemporaryTypeInfo(lhs, rhs); // `instanceof` on either side?
879+
}
867880
}
868881

869882
ClassNode rType = isNullConstant(rightExpression)
@@ -992,6 +1005,43 @@ && isAssignment(enclosingBinaryExpression.getOperation().getType())) {
9921005
}
9931006
}
9941007

1008+
private void propagateTemporaryTypeInfo(final Map<Object, List<ClassNode>> lhs,
1009+
final Map<Object, List<ClassNode>> rhs) {
1010+
// TODO: deal with (x !instanceof T)
1011+
lhs.keySet().removeIf(k -> k instanceof Object[]);
1012+
rhs.keySet().removeIf(k -> k instanceof Object[]);
1013+
1014+
Function<Object, List<ClassNode>> getOrAdd = (key) ->
1015+
typeCheckingContext.temporaryIfBranchTypeInformation.peek().computeIfAbsent(key, x -> new LinkedList<>());
1016+
1017+
for (var entry : lhs.entrySet()) {
1018+
if (rhs.containsKey(entry.getKey())) {
1019+
// main case: (x instanceof A || x instanceof B) produces A|B type
1020+
List<ClassNode> types = getOrAdd.apply(entry.getKey());
1021+
types.addAll(entry.getValue());
1022+
types.addAll(rhs.get(entry.getKey()));
1023+
} else if (entry.getKey() instanceof Variable) {
1024+
// edge case: (x instanceof A || ...) produces A|typeof(x) type
1025+
List<ClassNode> types = getOrAdd.apply(entry.getKey());
1026+
types.addAll(entry.getValue());
1027+
Variable v = (Variable) entry.getKey();
1028+
types.add(v instanceof ASTNode ? getType((ASTNode) v) : v.getType());
1029+
}
1030+
}
1031+
1032+
rhs.keySet().removeAll(lhs.keySet());
1033+
1034+
for (var entry : rhs.entrySet()) {
1035+
if (entry.getKey() instanceof Variable) {
1036+
// edge case: (... || x instanceof B) produces typeof(x)|B type
1037+
List<ClassNode> types = getOrAdd.apply(entry.getKey());
1038+
Variable v = (Variable) entry.getKey();
1039+
types.add(v instanceof ASTNode ? getType((ASTNode) v) : v.getType());
1040+
types.addAll(entry.getValue());
1041+
}
1042+
}
1043+
}
1044+
9951045
private void validateResourceInARM(final BinaryExpression expression, final ClassNode lType) {
9961046
if (expression instanceof DeclarationExpression
9971047
&& TryCatchStatement.isResource(expression)
@@ -1173,8 +1223,8 @@ private boolean ensureValidSetter(final Expression expression, final Expression
11731223
}
11741224
addStaticTypeError(message, leftExpression);
11751225
} else {
1176-
ClassNode[] tergetTypes = visibleSetters.stream().map(setterType).toArray(ClassNode[]::new);
1177-
addAssignmentError(tergetTypes.length == 1 ? tergetTypes[0] : new UnionTypeClassNode(tergetTypes), getType(valueExpression), expression);
1226+
ClassNode[] targetTypes = visibleSetters.stream().map(setterType).toArray(ClassNode[]::new);
1227+
addAssignmentError(targetTypes.length == 1 ? targetTypes[0] : new UnionTypeClassNode(targetTypes), getType(valueExpression), expression);
11781228
}
11791229
return true;
11801230
}

src/test/groovy/groovy/transform/stc/TypeInferenceSTCTest.groovy

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ class TypeInferenceSTCTest extends StaticTypeCheckingTestCase {
241241
'''
242242
}
243243

244-
// GROOVY-11769
244+
// GROOVY-11754
245245
void testInstanceOf10() {
246246
assertScript '''
247247
abstract class Foo {

src/test/groovy/org/codehaus/groovy/classgen/asm/sc/TypeInferenceStaticCompileTest.groovy

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,4 @@ class TypeInferenceStaticCompileTest extends TypeInferenceSTCTest implements Sta
3131
void testInstanceOf9() {
3232
super.testInstanceOf9() // GROOVY-7971
3333
}
34-
35-
@Override
36-
@NotYetImplemented
37-
void testInstanceOf10() {
38-
super.testInstanceOf10() // GROOVY-11769
39-
}
4034
}

0 commit comments

Comments
 (0)