From 6e1a63845e5c36d64b77c3cb03ba009443e2a33c Mon Sep 17 00:00:00 2001 From: Eric Milles Date: Wed, 27 Dec 2023 14:36:49 -0600 Subject: [PATCH] GROOVY-10651, GROOVY-10671 --- .../tests/search/GenericInferencingTests.java | 37 + .../core/tests/basic/GenericsTests.java | 46 +- .../codehaus/groovy/vmplugin/v8/Java8.java | 10 + .../codehaus/groovy/vmplugin/v8/Java8.java | 10 + base/org.codehaus.groovy50/.checkstyle | 1 + .../codehaus/groovy/vmplugin/v8/Java8.java | 686 ++++++++++++++++++ .../compiler/ast/JDTClassNodeBuilder.java | 17 +- .../jdt/groovy/search/GenericsMapper.java | 3 + 8 files changed, 806 insertions(+), 4 deletions(-) create mode 100644 base/org.codehaus.groovy50/src/org/codehaus/groovy/vmplugin/v8/Java8.java diff --git a/base-test/org.eclipse.jdt.groovy.core.tests.builder/src/org/eclipse/jdt/core/groovy/tests/search/GenericInferencingTests.java b/base-test/org.eclipse.jdt.groovy.core.tests.builder/src/org/eclipse/jdt/core/groovy/tests/search/GenericInferencingTests.java index 5b3cae8c18..b2b9734901 100644 --- a/base-test/org.eclipse.jdt.groovy.core.tests.builder/src/org/eclipse/jdt/core/groovy/tests/search/GenericInferencingTests.java +++ b/base-test/org.eclipse.jdt.groovy.core.tests.builder/src/org/eclipse/jdt/core/groovy/tests/search/GenericInferencingTests.java @@ -1797,6 +1797,43 @@ public void testCircularReference2() { assertType(contents, offset, offset + 8, "Scorable"); } + @Test // GROOVY-10671 + public void testCircularReference3() { + createJavaUnit("p", "ObjectAssert", + "package p;\n" + + "abstract class ObjectAssert> {\n" + + " SELF as(String description) {\n" + + " return (SELF) this;\n" + + " }\n" + + "}\n"); + + createJavaUnit("p", "IterableAssert", + "package p;\n" + + "abstract class IterableAssert> extends ObjectAssert {\n" + + "}\n"); + + createJavaUnit("p", "CollectionAssert", + "package p;\n" + + "abstract class CollectionAssert> extends IterableAssert {\n" + + " void containsExactlyInAnyOrderElementsOf(java.util.Collection expect) {\n" + + " }\n" + + "}\n"); + + createJavaUnit("p", "Assert", + "package p;\n" + + "class Assert {\n" + + " public static CollectionAssert assertThat(java.util.Collection c) {return null;}\n" + + "}\n"); + + String contents = + "import static p.Assert.assertThat\n" + + "def strings = (Collection) ['a','b']\n" + + "assertThat(strings).as('assertion description')\n" + + " .containsExactlyInAnyOrderElementsOf(['a','b'])\n"; + + assertDeclaringType(contents, "containsExactlyInAnyOrderElementsOf", "p.CollectionAssert"); + } + @Test public void testJira1718() throws Exception { createUnit("p2", "Renderer", diff --git a/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/basic/GenericsTests.java b/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/basic/GenericsTests.java index 4f383dec46..fe8df7ca54 100644 --- a/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/basic/GenericsTests.java +++ b/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/basic/GenericsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2009-2022 the original author or authors. + * Copyright 2009-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -2647,6 +2647,50 @@ public void testWildcards7() { "----------\n"); } + @Test // GROOVY-10671 + public void testWildcards8() { + //@formatter:off + String[] sources = { + "Main.groovy", + "import static p.Assert.assertThat\n" + + "def strings = (Collection) ['a','b']\n" + + "assertThat(strings).as('assertion description')\n" + + " .containsExactlyInAnyOrderElementsOf(['a','b'])\n", + + "p/Assert.java", + "package p;\n" + + "class Assert {\n" + + " public static CollectionAssert assertThat(java.util.Collection c) {\n" + + " return new CollectionAssert() {};\n" + + " }\n" + + "}\n", + + "p/ObjectAssert.java", + "package p;\n" + + "abstract class ObjectAssert> {\n" + + " SELF as(String description) {\n" + + " return (SELF) this;\n" + + " }\n" + + "}\n", + + "p/IterableAssert.java", + "package p;\n" + + "abstract class IterableAssert> extends ObjectAssert {\n" + + "}\n", + + "p/CollectionAssert.java", + "package p;\n" + + "abstract class CollectionAssert> extends IterableAssert {\n" + + " void containsExactlyInAnyOrderElementsOf(java.util.Collection expect) {\n" + + " System.out.print(expect);\n" + + " }\n" + + "}\n", + }; + //@formatter:on + + runConformTest(sources, "[a, b]"); + } + @Test public void testUpperBounds1() { //@formatter:off diff --git a/base/org.codehaus.groovy30/src/org/codehaus/groovy/vmplugin/v8/Java8.java b/base/org.codehaus.groovy30/src/org/codehaus/groovy/vmplugin/v8/Java8.java index e713390a89..2a5c7cf325 100644 --- a/base/org.codehaus.groovy30/src/org/codehaus/groovy/vmplugin/v8/Java8.java +++ b/base/org.codehaus.groovy30/src/org/codehaus/groovy/vmplugin/v8/Java8.java @@ -246,6 +246,16 @@ private ClassNode configureWildcardType(final WildcardType wildcardType) { private ClassNode configureParameterizedType(final ParameterizedType parameterizedType) { ClassNode base = configureType(parameterizedType.getRawType()); GenericsType[] gts = configureTypeArguments(parameterizedType.getActualTypeArguments()); + // GRECLIPSE add -- GROOVY-10153, GROOVY-10651, GROOVY-10671, GROOVY-10756, GROOVY-11258 + // fix erasure : ResolveVisitor#resolveWildcardBounding + final int n; if (gts != null && (n = gts.length) > 0) { + for (int i = 0; i < n; i += 1) { GenericsType gt = gts[i]; + if (!gt.isWildcard() || gt.getUpperBounds() != null) continue; + ClassNode[] ubs = base.redirect().getGenericsTypes()[i].getUpperBounds(); + if (ubs != null && !ClassHelper.OBJECT_TYPE.equals(ubs[0])) gt.getType().setRedirect(ubs[0]); + } + } + // GRECLIPSE end base.setGenericsTypes(gts); return base; } diff --git a/base/org.codehaus.groovy40/src/org/codehaus/groovy/vmplugin/v8/Java8.java b/base/org.codehaus.groovy40/src/org/codehaus/groovy/vmplugin/v8/Java8.java index e25f1eabe9..3fa330e534 100644 --- a/base/org.codehaus.groovy40/src/org/codehaus/groovy/vmplugin/v8/Java8.java +++ b/base/org.codehaus.groovy40/src/org/codehaus/groovy/vmplugin/v8/Java8.java @@ -254,6 +254,16 @@ private ClassNode configureWildcardType(final WildcardType wildcardType) { private ClassNode configureParameterizedType(final ParameterizedType parameterizedType) { ClassNode base = configureType(parameterizedType.getRawType()); GenericsType[] gts = configureTypeArguments(parameterizedType.getActualTypeArguments()); + // GRECLIPSE add -- GROOVY-10153, GROOVY-10651, GROOVY-10671, GROOVY-10756, GROOVY-11258 + // fix erasure : ResolveVisitor#resolveWildcardBounding + final int n; if (gts != null && (n = gts.length) > 0) { + for (int i = 0; i < n; i += 1) { GenericsType gt = gts[i]; + if (!gt.isWildcard() || gt.getUpperBounds() != null) continue; + ClassNode[] ubs = base.redirect().getGenericsTypes()[i].getUpperBounds(); + if (ubs != null && !ClassHelper.OBJECT_TYPE.equals(ubs[0])) gt.getType().setRedirect(ubs[0]); + } + } + // GRECLIPSE end base.setGenericsTypes(gts); return base; } diff --git a/base/org.codehaus.groovy50/.checkstyle b/base/org.codehaus.groovy50/.checkstyle index 62e0c3933e..2ea44d0e29 100644 --- a/base/org.codehaus.groovy50/.checkstyle +++ b/base/org.codehaus.groovy50/.checkstyle @@ -60,6 +60,7 @@ + diff --git a/base/org.codehaus.groovy50/src/org/codehaus/groovy/vmplugin/v8/Java8.java b/base/org.codehaus.groovy50/src/org/codehaus/groovy/vmplugin/v8/Java8.java new file mode 100644 index 0000000000..7f2ff1c5ab --- /dev/null +++ b/base/org.codehaus.groovy50/src/org/codehaus/groovy/vmplugin/v8/Java8.java @@ -0,0 +1,686 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.codehaus.groovy.vmplugin.v8; + +import groovy.lang.MetaClass; +import groovy.lang.MetaMethod; +import org.apache.groovy.util.Maps; +import org.codehaus.groovy.GroovyBugError; +import org.codehaus.groovy.ast.AnnotatedNode; +import org.codehaus.groovy.ast.AnnotationNode; +import org.codehaus.groovy.ast.ClassHelper; +import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.ast.CompileUnit; +import org.codehaus.groovy.ast.ConstructorNode; +import org.codehaus.groovy.ast.FieldNode; +import org.codehaus.groovy.ast.GenericsType; +import org.codehaus.groovy.ast.MethodNode; +import org.codehaus.groovy.ast.PackageNode; +import org.codehaus.groovy.ast.Parameter; +import org.codehaus.groovy.ast.expr.AnnotationConstantExpression; +import org.codehaus.groovy.ast.expr.ClassExpression; +import org.codehaus.groovy.ast.expr.ConstantExpression; +import org.codehaus.groovy.ast.expr.Expression; +import org.codehaus.groovy.ast.expr.ListExpression; +import org.codehaus.groovy.ast.expr.PropertyExpression; +import org.codehaus.groovy.ast.stmt.ReturnStatement; +import org.codehaus.groovy.reflection.ReflectionUtils; +import org.codehaus.groovy.runtime.MetaClassHelper; +import org.codehaus.groovy.vmplugin.VMPlugin; +import org.codehaus.groovy.vmplugin.VMPluginFactory; + +import java.lang.annotation.Annotation; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.AccessibleObject; +import java.lang.reflect.Array; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.GenericSignatureFormatError; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.MalformedParameterizedTypeException; +import java.lang.reflect.Member; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.ReflectPermission; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.lang.reflect.WildcardType; +import java.security.Permission; +import java.util.Arrays; +import java.util.EnumMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * Java 8 based functions. + * + * @since 2.5.0 + */ +public class Java8 implements VMPlugin { + + private static final Method[] EMPTY_METHOD_ARRAY = new Method[0]; + private static final Annotation[] EMPTY_ANNOTATION_ARRAY = new Annotation[0]; + private static final Permission ACCESS_PERMISSION = new ReflectPermission("suppressAccessChecks"); + + public static GenericsType configureTypeVariableDefinition(final ClassNode base, final ClassNode[] bounds) { + ClassNode redirect = base.redirect(); + base.setRedirect(null); + GenericsType gt; + if (bounds == null || bounds.length == 0) { + gt = new GenericsType(base); + } else { + // GROOVY-10756: fix erasure -- ResolveVisitor#resolveGenericsHeader + if (!ClassHelper.isObjectType(bounds[0])) redirect = bounds[0]; + gt = new GenericsType(base, bounds, null); + gt.setName(base.getName()); + gt.setPlaceholder(true); + } + base.setRedirect(redirect); + return gt; + } + + public static ClassNode configureTypeVariableReference(final String name) { + ClassNode cn = ClassHelper.makeWithoutCaching(name); + cn.setGenericsPlaceHolder(true); + ClassNode cn2 = ClassHelper.makeWithoutCaching(name); + cn2.setGenericsPlaceHolder(true); + + cn.setGenericsTypes(new GenericsType[]{new GenericsType(cn2)}); + cn.setRedirect(ClassHelper.OBJECT_TYPE); + return cn; + } + + private static ClassNode configureClass(final Class c) { + if (c.isPrimitive()) { + return ClassHelper.make(c); + } else { + return ClassHelper.makeWithoutCaching(c, false); + } + } + + private static void setRetentionPolicy(final RetentionPolicy value, final AnnotationNode node) { + switch (value) { + case RUNTIME: + node.setRuntimeRetention(true); + break; + case SOURCE: + node.setSourceRetention(true); + break; + case CLASS: + node.setClassRetention(true); + break; + default: + throw new GroovyBugError("unsupported Retention " + value); + } + } + + //-------------------------------------------------------------------------- + + @Override + public Class[] getPluginDefaultGroovyMethods() { + return new Class[]{PluginDefaultGroovyMethods.class}; + } + + @Override + public Class[] getPluginStaticGroovyMethods() { + return MetaClassHelper.EMPTY_TYPE_ARRAY; + } + + @Override + public int getVersion() { + return 8; + } + + @Override + public void setAdditionalClassInformation(final ClassNode cn) { + cn.setGenericsTypes(configureTypeParameters(cn.getTypeClass().getTypeParameters())); + } + + private ClassNode[] configureTypes(final Type[] types) { + final int n = types.length; + if (n == 0) return null; + ClassNode[] nodes = new ClassNode[n]; + for (int i = 0; i < n; i += 1) { + nodes[i] = configureType(types[i]); + } + return nodes; + } + + private ClassNode configureType(final Type type) { + if (type instanceof WildcardType) { + return configureWildcardType((WildcardType) type); + } else if (type instanceof ParameterizedType) { + return configureParameterizedType((ParameterizedType) type); + } else if (type instanceof GenericArrayType) { + return configureGenericArray((GenericArrayType) type); + } else if (type instanceof TypeVariable) { + return configureTypeVariableReference(((TypeVariable) type).getName()); + } else if (type instanceof Class) { + return configureClass((Class) type); + } else if (type == null) { + throw new GroovyBugError("Type is null. Most probably you let a transform reuse existing ClassNodes with generics information, that is now used in a wrong context."); + } else { + throw new GroovyBugError("unknown type: " + type + " := " + type.getClass()); + } + } + + private ClassNode configureGenericArray(final GenericArrayType genericArrayType) { + Type component = genericArrayType.getGenericComponentType(); + ClassNode node = configureType(component); + return node.makeArray(); + } + + private ClassNode configureWildcardType(final WildcardType wildcardType) { + ClassNode base = ClassHelper.makeWithoutCaching("?"); + base.setRedirect(ClassHelper.OBJECT_TYPE); + + ClassNode[] lowers = configureTypes(wildcardType.getLowerBounds()); + ClassNode[] uppers = configureTypes(wildcardType.getUpperBounds()); + // beware of [Object] upper bounds; often it's or + if (lowers != null || wildcardType.getTypeName().equals("?")) { + uppers = null; + } + + GenericsType gt = new GenericsType(base, uppers, lowers != null ? lowers[0] : null); + gt.setWildcard(true); + + ClassNode wt = ClassHelper.makeWithoutCaching(Object.class, false); + wt.setGenericsTypes(new GenericsType[]{gt}); + return wt; + } + + private ClassNode configureParameterizedType(final ParameterizedType parameterizedType) { + ClassNode base = configureType(parameterizedType.getRawType()); + GenericsType[] gts = configureTypeArguments(parameterizedType.getActualTypeArguments()); + // GRECLIPSE add -- GROOVY-10153, GROOVY-10651, GROOVY-10671, GROOVY-10756, GROOVY-11258 + // fix erasure : ResolveVisitor#resolveWildcardBounding + final int n; if (gts != null && (n = gts.length) > 0) { + for (int i = 0; i < n; i += 1) { GenericsType gt = gts[i]; + if (!gt.isWildcard() || gt.getUpperBounds() != null) continue; + ClassNode[] ubs = base.redirect().getGenericsTypes()[i].getUpperBounds(); + if (ubs != null && !ClassHelper.OBJECT_TYPE.equals(ubs[0])) gt.getType().setRedirect(ubs[0]); + } + } + // GRECLIPSE end + base.setGenericsTypes(gts); + return base; + } + + private GenericsType[] configureTypeArguments(final Type[] ta) { + final int n = ta.length; + if (n == 0) return null; + GenericsType[] gts = new GenericsType[n]; + for (int i = 0; i < n; i += 1) { + ClassNode t = configureType(ta[i]); + if (ta[i] instanceof WildcardType) { + GenericsType[] gen = t.getGenericsTypes(); + gts[i] = gen[0]; + } else { + gts[i] = new GenericsType(t); + } + } + return gts; + } + + private GenericsType[] configureTypeParameters(final TypeVariable[] tp) { + final int n = tp.length; + if (n == 0) return null; + GenericsType[] gt = new GenericsType[n]; + for (int i = 0; i < n; i += 1) { + ClassNode t = configureTypeVariableReference(tp[i].getName()); + ClassNode[] bounds = configureTypes(tp[i].getBounds()); + gt[i] = configureTypeVariableDefinition(t, bounds); + } + return gt; + } + + // + + @Override + public void configureAnnotation(final AnnotationNode node) { + ClassNode type = node.getClassNode(); + VMPlugin plugin = VMPluginFactory.getPlugin(); + List annotations = type.getAnnotations(); + for (AnnotationNode an : annotations) { + plugin.configureAnnotationNodeFromDefinition(an, node); + } + if (!node.getClassNode().getName().equals("java.lang.annotation.Retention")) { + plugin.configureAnnotationNodeFromDefinition(node, node); + } + } + + private void configureAnnotation(final AnnotationNode node, final Annotation annotation) { + Class type = annotation.annotationType(); + if (type == Retention.class) { + Retention r = (Retention) annotation; + RetentionPolicy value = r.value(); + setRetentionPolicy(value, node); + node.setMember("value", new PropertyExpression( + new ClassExpression(ClassHelper.makeWithoutCaching(RetentionPolicy.class, false)), + value.toString())); + } else if (type == Target.class) { + Target t = (Target) annotation; + ElementType[] elements = t.value(); + ListExpression elementExprs = new ListExpression(); + for (ElementType element : elements) { + elementExprs.addExpression(new PropertyExpression( + new ClassExpression(ClassHelper.ELEMENT_TYPE_TYPE), element.name())); + } + node.setMember("value", elementExprs); + } else { + Method[] declaredMethods; + try { + declaredMethods = type.getDeclaredMethods(); + } catch (SecurityException se) { + declaredMethods = EMPTY_METHOD_ARRAY; + } + for (Method declaredMethod : declaredMethods) { + try { + Object value = declaredMethod.invoke(annotation); + Expression valueExpression = toAnnotationValueExpression(value); + if (valueExpression != null) node.setMember(declaredMethod.getName(), valueExpression); + + } catch (IllegalAccessException | InvocationTargetException ignore) { + } + } + } + } + + private void setAnnotationMetaData(final Annotation[] annotations, final AnnotatedNode target) { + for (Annotation annotation : annotations) { + target.addAnnotation(toAnnotationNode(annotation)); + } + } + + protected AnnotationNode toAnnotationNode(final Annotation annotation) { + ClassNode type = ClassHelper.make(annotation.annotationType()); + AnnotationNode node = new AnnotationNode(type); + configureAnnotation(node, annotation); + return node; + } + + private Expression toAnnotationValueExpression(final Object value) { + if (value == null || value instanceof String || value instanceof Number || value instanceof Character || value instanceof Boolean) + return new ConstantExpression(value); + + if (value instanceof Class) + return new ClassExpression(ClassHelper.makeWithoutCaching((Class)value)); + + if (value instanceof Annotation) + return new AnnotationConstantExpression(toAnnotationNode((Annotation)value)); + + if (value instanceof Enum) + return new PropertyExpression(new ClassExpression(ClassHelper.makeWithoutCaching(value.getClass())), value.toString()); + + if (value.getClass().isArray()) { + ListExpression list = new ListExpression(); + for (int i = 0, n = Array.getLength(value); i < n; i += 1) + list.addExpression(toAnnotationValueExpression(Array.get(value, i))); + return list; + } + + return null; + } + + // + + protected final Map elementTypeToTarget = new EnumMap<>(Maps.of( + ElementType.TYPE, AnnotationNode.TYPE_TARGET, + ElementType.FIELD, AnnotationNode.FIELD_TARGET, + ElementType.METHOD, AnnotationNode.METHOD_TARGET, + ElementType.PARAMETER, AnnotationNode.PARAMETER_TARGET, + ElementType.CONSTRUCTOR, AnnotationNode.CONSTRUCTOR_TARGET, + ElementType.LOCAL_VARIABLE, AnnotationNode.LOCAL_VARIABLE_TARGET, + ElementType.ANNOTATION_TYPE, AnnotationNode.ANNOTATION_TARGET, + ElementType.PACKAGE, AnnotationNode.PACKAGE_TARGET, + ElementType.TYPE_PARAMETER, AnnotationNode.TYPE_PARAMETER_TARGET, + ElementType.TYPE_USE, AnnotationNode.TYPE_USE_TARGET + )); + + @Override + public void configureAnnotationNodeFromDefinition(final AnnotationNode definition, final AnnotationNode root) { + String typeName = definition.getClassNode().getName(); + if ("java.lang.annotation.Retention".equals(typeName)) { + Expression exp = definition.getMember("value"); + if (!(exp instanceof PropertyExpression)) return; + PropertyExpression pe = (PropertyExpression) exp; + String name = pe.getPropertyAsString(); + RetentionPolicy policy = RetentionPolicy.valueOf(name); + setRetentionPolicy(policy, root); + } else if ("java.lang.annotation.Target".equals(typeName)) { + Expression exp = definition.getMember("value"); + if (!(exp instanceof ListExpression)) return; + ListExpression list = (ListExpression) exp; + int targets = 0; + for (Expression e : list.getExpressions()) { + if (!(e instanceof PropertyExpression)) return; + PropertyExpression element = (PropertyExpression) e; + String name = element.getPropertyAsString(); + ElementType type = ElementType.valueOf(name); + Integer target = elementTypeToTarget.get(type); + if (target == null) throw new GroovyBugError("unsupported Target " + type); + targets |= target; + } + root.setAllowedTargets(targets); + } + } + + @Override + public void configureClassNode(final CompileUnit compileUnit, final ClassNode classNode) { + try { + Class clazz = classNode.getTypeClass(); + Field[] fields = clazz.getDeclaredFields(); + for (Field f : fields) { + ClassNode rt = makeClassNode(compileUnit, f.getGenericType(), f.getType()); + FieldNode fn = new FieldNode(f.getName(), f.getModifiers(), rt, classNode, null); + setAnnotationMetaData(f.getAnnotations(), fn); + classNode.addField(fn); + } + Method[] methods = clazz.getDeclaredMethods(); + for (Method m : methods) { + ClassNode rt = makeClassNode(compileUnit, m.getGenericReturnType(), m.getReturnType()); + Parameter[] params = makeParameters(compileUnit, m.getGenericParameterTypes(), m.getParameterTypes(), m.getParameterAnnotations(), m); + ClassNode[] exceptions = makeClassNodes(compileUnit, m.getGenericExceptionTypes(), m.getExceptionTypes()); + MethodNode mn = new MethodNode(m.getName(), m.getModifiers(), rt, params, exceptions, null); + setAnnotationMetaData(m.getAnnotations(), mn); + if (m.getDefaultValue() != null) { + mn.setAnnotationDefault(true); // GROOVY-10862 + mn.setCode(new ReturnStatement(new ConstantExpression(m.getDefaultValue(), true))); + } + mn.setGenericsTypes(configureTypeParameters(m.getTypeParameters())); + mn.setSynthetic(m.isSynthetic()); + classNode.addMethod(mn); + } + Constructor[] constructors = clazz.getDeclaredConstructors(); + for (Constructor c : constructors) { + Parameter[] params = makeParameters(compileUnit, c.getGenericParameterTypes(), c.getParameterTypes(), getConstructorParameterAnnotations(c), c); + ClassNode[] exceptions = makeClassNodes(compileUnit, c.getGenericExceptionTypes(), c.getExceptionTypes()); + ConstructorNode cn = classNode.addConstructor(c.getModifiers(), params, exceptions, null); + setAnnotationMetaData(c.getAnnotations(), cn); + } + + Class sc = clazz.getSuperclass(); + if (sc != null) classNode.setUnresolvedSuperClass(makeClassNode(compileUnit, clazz.getGenericSuperclass(), sc)); + makeInterfaceTypes(compileUnit, classNode, clazz); + makePermittedSubclasses(compileUnit, classNode, clazz); + makeRecordComponents(compileUnit, classNode, clazz); + setAnnotationMetaData(clazz.getAnnotations(), classNode); + + PackageNode packageNode = classNode.getPackage(); + if (packageNode != null) { + setAnnotationMetaData(clazz.getPackage().getAnnotations(), packageNode); + } + } catch (NoClassDefFoundError e) { + throw new NoClassDefFoundError("Unable to configure " + classNode.getName() + " due to missing dependency " + e.getMessage()); + } catch (TypeNotPresentException e) { + throw new NoClassDefFoundError("Unable to configure " + classNode.getName() + " due to missing dependency " + e.typeName()); + } catch (GenericSignatureFormatError | MalformedParameterizedTypeException e) { + throw new RuntimeException( "Unable to configure " + classNode.getName() + " due to malformed type info" , e); + } + } + + /** + * Synthetic parameters such as those added for inner class constructors may + * not be included in the parameter annotations array. This is the case when + * at least one parameter of an inner class constructor has an annotation with + * a RUNTIME retention (this occurs for JDK8 and below). This method will + * normalize the annotations array so that it contains the same number of + * elements as the array returned from {@link Constructor#getParameterTypes()}. + * + * If adjustment is required, the adjusted array will be prepended with a + * zero-length element. If no adjustment is required, the original array + * from {@link Constructor#getParameterAnnotations()} will be returned. + * + * @param constructor the Constructor for which to return parameter annotations + * @return array of arrays containing the annotations on the parameters of the given Constructor + */ + private Annotation[][] getConstructorParameterAnnotations(final Constructor constructor) { + /* + * TODO: Remove after JDK9 is the minimum JDK supported + * + * JDK9+ correctly accounts for the synthetic parameter and when it becomes + * the minimum version this method should no longer be required. + */ + int parameterCount = constructor.getParameterTypes().length; + Annotation[][] annotations = constructor.getParameterAnnotations(); + int diff = parameterCount - annotations.length; + if (diff > 0) { + // May happen on JDK8 and below. We add elements to the front of the array to account for the synthetic params: + // - for an inner class we expect one param to account for the synthetic outer reference + // - for an enum we expect two params to account for the synthetic name and ordinal + if ((!constructor.getDeclaringClass().isEnum() && diff > 1) || diff > 2) { + throw new GroovyBugError( + "Constructor parameter annotations length [" + annotations.length + "] " + + "does not match the parameter length: " + constructor + ); + } + Annotation[][] adjusted = new Annotation[parameterCount][]; + for (int i = 0; i < diff; i += 1) { + adjusted[i] = EMPTY_ANNOTATION_ARRAY; + } + System.arraycopy(annotations, 0, adjusted, diff, annotations.length); + return adjusted; + } + return annotations; + } + + private void makePermittedSubclasses(final CompileUnit cu, final ClassNode classNode, final Class clazz) { + if (!ReflectionUtils.isSealed(clazz)) return; + List permittedSubclasses = Arrays.stream(ReflectionUtils.getPermittedSubclasses(clazz)) + .map(c -> makeClassNode(cu, c, c)) + .collect(Collectors.toList()); + classNode.setPermittedSubclasses(permittedSubclasses); + } + + protected void makeRecordComponents(final CompileUnit cu, final ClassNode classNode, final Class clazz) { + } + + private void makeInterfaceTypes(final CompileUnit cu, final ClassNode classNode, final Class clazz) { + Type[] interfaceTypes = clazz.getGenericInterfaces(); + final int n = interfaceTypes.length; + if (n == 0) { + classNode.setInterfaces(ClassNode.EMPTY_ARRAY); + } else { + ClassNode[] ret = new ClassNode[n]; + for (int i = 0; i < n; i += 1) { + Type type = interfaceTypes[i]; + while (!(type instanceof Class)) { + ParameterizedType pt = (ParameterizedType) type; + Type t2 = pt.getRawType(); + if (t2 == type) { + throw new GroovyBugError("Cannot transform generic signature of " + clazz + " with generic interface " + interfaceTypes[i] + " to a class."); + } + type = t2; + } + ret[i] = makeClassNode(cu, interfaceTypes[i], (Class) type); + } + classNode.setInterfaces(ret); + } + } + + private ClassNode[] makeClassNodes(final CompileUnit cu, final Type[] types, final Class[] cls) { + final int n = types.length; + ClassNode[] nodes = new ClassNode[n]; + for (int i = 0; i < n; i += 1) { + nodes[i] = makeClassNode(cu, types[i], cls[i]); + } + return nodes; + } + + protected ClassNode makeClassNode(final CompileUnit cu, final Type t, final Class c) { + ClassNode back = null; + if (cu != null) back = cu.getClass(c.getName()); + if (back == null) back = ClassHelper.make(c); + if (!(t instanceof Class)) { + ClassNode front = configureType(t); + front.setRedirect(back); + return front; + } + return back.getPlainNodeReference(); + } + + private Parameter[] makeParameters(final CompileUnit cu, final Type[] types, final Class[] cls, final Annotation[][] parameterAnnotations, final Member member) { + Parameter[] params = Parameter.EMPTY_ARRAY; + final int n = types.length; + if (n > 0) { + params = new Parameter[n]; + String[] names = new String[n]; + fillParameterNames(names, member); + for (int i = 0; i < n; i += 1) { + setAnnotationMetaData(parameterAnnotations[i], + params[i] = new Parameter(makeClassNode(cu, types[i], cls[i]), names[i])); + } + } + return params; + } + + protected void fillParameterNames(final String[] names, final Member member) { + try { + java.lang.reflect.Parameter[] parameters = ((java.lang.reflect.Executable) member).getParameters(); + for (int i = 0, n = names.length; i < n; i += 1) { + names[i] = parameters[i].getName(); + } + } catch (RuntimeException e) { + throw new GroovyBugError(e); + } + } + + //-------------------------------------------------------------------------- + + /** + * The following scenarios can not set accessible, i.e. the return value is false + * 1) SecurityException occurred + * 2) the accessible object is a Constructor object for the Class class + * + * @param accessibleObject the accessible object to check + * @param callerClass the callerClass to invoke {@code setAccessible} + * @return the check result + */ + @Override + public boolean checkCanSetAccessible(final AccessibleObject accessibleObject, final Class callerClass) { + SecurityManager sm = System.getSecurityManager(); + try { + if (sm != null) { + sm.checkPermission(ACCESS_PERMISSION); + } + } catch (SecurityException e) { + return false; + } + + if (accessibleObject instanceof Constructor) { + Constructor c = (Constructor) accessibleObject; + if (c.getDeclaringClass() == Class.class) { + return false; // Cannot make a java.lang.Class constructor accessible + } + } + + return true; + } + + @Override + public boolean checkAccessible(final Class callerClass, final Class declaringClass, final int memberModifiers, final boolean allowIllegalAccess) { + return true; + } + + @Override + public boolean trySetAccessible(final AccessibleObject ao) { + try { + ao.setAccessible(true); + return true; + } catch (SecurityException e) { + throw e; + } catch (Throwable t) { + return false; + } + } + + @Override + public MetaMethod transformMetaMethod(final MetaClass metaClass, final MetaMethod metaMethod) { + return transformMetaMethod(metaClass, metaMethod, null); + } + + @Override + public MetaMethod transformMetaMethod(final MetaClass metaClass, final MetaMethod metaMethod, final Class caller) { + return metaMethod; + } + + @Override + public void invalidateCallSites() { + IndyInterface.invalidateSwitchPoints(); + } + + @Override + public Object getInvokeSpecialHandle(final Method method, final Object receiver) { + final Class receiverClass = receiver.getClass(); + try { + return newLookup(receiverClass).unreflectSpecial(method, receiverClass).bindTo(receiver); + } catch (ReflectiveOperationException e1) { + if (!method.isAccessible()) { + doPrivilegedInternal(() -> ReflectionUtils.trySetAccessible(method)); + } + final Class declaringClass = method.getDeclaringClass(); + try { + return newLookup(declaringClass).unreflectSpecial(method, declaringClass).bindTo(receiver); + } catch (ReflectiveOperationException e2) { + var e3 = new GroovyBugError(e1); + e3.addSuppressed(e2); + throw e3; + } + } + } + + @Override + public Object invokeHandle(final Object handle, final Object[] arguments) throws Throwable { + return ((MethodHandle) handle).invokeWithArguments(arguments); + } + + //-------------------------------------------------------------------------- + + @Deprecated(since = "5.0.0") + public static MethodHandles.Lookup of(final Class targetClass) { + return ((Java8) VMPluginFactory.getPlugin()).newLookup(targetClass); + } + + protected MethodHandles.Lookup newLookup(final Class targetClass) { + throw new IllegalStateException(); + } + + @Override + @Deprecated(since = "4.0.2") + public T doPrivileged(final java.security.PrivilegedAction action) { + throw new UnsupportedOperationException("doPrivileged is no longer supported"); + } + + @Override + @Deprecated(since = "4.0.2") + public T doPrivileged(final java.security.PrivilegedExceptionAction action) { + throw new UnsupportedOperationException("doPrivileged is no longer supported"); + } + + private static T doPrivilegedInternal(final java.security.PrivilegedAction action) { + return java.security.AccessController.doPrivileged(action); + } +} diff --git a/base/org.eclipse.jdt.groovy.core/src/org/codehaus/jdt/groovy/internal/compiler/ast/JDTClassNodeBuilder.java b/base/org.eclipse.jdt.groovy.core/src/org/codehaus/jdt/groovy/internal/compiler/ast/JDTClassNodeBuilder.java index bfe6d0bb46..cd17ed8676 100644 --- a/base/org.eclipse.jdt.groovy.core/src/org/codehaus/jdt/groovy/internal/compiler/ast/JDTClassNodeBuilder.java +++ b/base/org.eclipse.jdt.groovy.core/src/org/codehaus/jdt/groovy/internal/compiler/ast/JDTClassNodeBuilder.java @@ -232,11 +232,22 @@ private ClassNode configureParameterizedType(ParameterizedTypeBinding tb) { // the messing about in here is for a few reasons. Contrast it with the ClassHelper.makeWithoutCaching // that code when called for Iterable will set the redirect to point to the generics. That is what // we are trying to achieve here. - if (!(tb instanceof RawTypeBinding)) { - setRedirect(cn, configureType(tb.genericType())); + setRedirect(cn, configureType(tb.genericType())); + } + GenericsType[] gts = configureTypeArguments(tb.arguments); + if (gts != null && cn.isRedirectNode()) { + for (int i = 0, n = gts.length; i < n; i += 1) { // GROOVY-10671 + if (!gts[i].isWildcard() || gts[i].getUpperBounds() != null) continue; + if (tb.genericType().typeVariables()[i].enterRecursiveFunction()) { // GROOVY-10651 + ClassNode[] implicitBounds = cn.redirect().getGenericsTypes()[i].getUpperBounds(); + if (implicitBounds != null && !ClassHelper.OBJECT_TYPE.equals(implicitBounds[0])){ + gts[i].getType().setRedirect(implicitBounds[0]); // "?" erasure is not Object! + } + tb.genericType().typeVariables()[i].exitRecursiveFunction(); + } } } - cn.setGenericsTypes(configureTypeArguments(tb.arguments)); + cn.setGenericsTypes(gts); return cn; } diff --git a/base/org.eclipse.jdt.groovy.core/src/org/eclipse/jdt/groovy/search/GenericsMapper.java b/base/org.eclipse.jdt.groovy.core/src/org/eclipse/jdt/groovy/search/GenericsMapper.java index ee4b6e50b4..2d9cd43bd3 100644 --- a/base/org.eclipse.jdt.groovy.core/src/org/eclipse/jdt/groovy/search/GenericsMapper.java +++ b/base/org.eclipse.jdt.groovy.core/src/org/eclipse/jdt/groovy/search/GenericsMapper.java @@ -186,6 +186,9 @@ public static GenericsMapper gatherGenerics(final List argumentTypes, if (topGT.getUpperBounds() != null) { return topGT.getUpperBounds()[0]; } + if (!topGT.getType().equals(VariableScope.OBJECT_CLASS_NODE)) { + return topGT.getType(); // GROOVY-10671: non-Object erasure + } return VariableScope.OBJECT_CLASS_NODE; }