From 4d15b58ca421488d02f92e882c4a4b52c20328f6 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Sun, 30 Apr 2023 23:05:09 +0200 Subject: [PATCH] Remove LocalVariableTableParameterNameDiscoverer Closes gh-29559 --- .../core/DefaultParameterNameDiscoverer.java | 12 +- ...lVariableTableParameterNameDiscoverer.java | 286 --------------- ...ableTableParameterNameDiscovererTests.java | 326 ------------------ 3 files changed, 2 insertions(+), 622 deletions(-) delete mode 100644 spring-core/src/main/java/org/springframework/core/LocalVariableTableParameterNameDiscoverer.java delete mode 100644 spring-core/src/test/java/org/springframework/core/LocalVariableTableParameterNameDiscovererTests.java diff --git a/spring-core/src/main/java/org/springframework/core/DefaultParameterNameDiscoverer.java b/spring-core/src/main/java/org/springframework/core/DefaultParameterNameDiscoverer.java index b7bee1aea8..18c39c1ad0 100644 --- a/spring-core/src/main/java/org/springframework/core/DefaultParameterNameDiscoverer.java +++ b/spring-core/src/main/java/org/springframework/core/DefaultParameterNameDiscoverer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-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. @@ -18,8 +18,7 @@ package org.springframework.core; /** * Default implementation of the {@link ParameterNameDiscoverer} strategy interface, - * delegating to the Java 8 standard reflection mechanism, with a deprecated fallback - * to {@link LocalVariableTableParameterNameDiscoverer}. + * delegating to the Java 8 standard reflection mechanism. * *

If a Kotlin reflection implementation is present, * {@link KotlinReflectionParameterNameDiscoverer} is added first in the list and @@ -36,7 +35,6 @@ package org.springframework.core; */ public class DefaultParameterNameDiscoverer extends PrioritizedParameterNameDiscoverer { - @SuppressWarnings("removal") public DefaultParameterNameDiscoverer() { if (KotlinDetector.isKotlinReflectPresent()) { addDiscoverer(new KotlinReflectionParameterNameDiscoverer()); @@ -44,12 +42,6 @@ public class DefaultParameterNameDiscoverer extends PrioritizedParameterNameDisc // Recommended approach on Java 8+: compilation with -parameters. addDiscoverer(new StandardReflectionParameterNameDiscoverer()); - - // Deprecated fallback to class file parsing for -debug symbols. - // Does not work on native images without class file resources. - if (!NativeDetector.inNativeImage()) { - addDiscoverer(new LocalVariableTableParameterNameDiscoverer()); - } } } diff --git a/spring-core/src/main/java/org/springframework/core/LocalVariableTableParameterNameDiscoverer.java b/spring-core/src/main/java/org/springframework/core/LocalVariableTableParameterNameDiscoverer.java deleted file mode 100644 index 5d666a51f4..0000000000 --- a/spring-core/src/main/java/org/springframework/core/LocalVariableTableParameterNameDiscoverer.java +++ /dev/null @@ -1,286 +0,0 @@ -/* - * Copyright 2002-2022 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. - * You may obtain a copy of the License at - * - * https://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.springframework.core; - -import java.io.IOException; -import java.io.InputStream; -import java.lang.reflect.Constructor; -import java.lang.reflect.Executable; -import java.lang.reflect.Method; -import java.util.Collections; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.asm.ClassReader; -import org.springframework.asm.ClassVisitor; -import org.springframework.asm.Label; -import org.springframework.asm.MethodVisitor; -import org.springframework.asm.Opcodes; -import org.springframework.asm.SpringAsmInfo; -import org.springframework.asm.Type; -import org.springframework.lang.Nullable; -import org.springframework.util.ClassUtils; - -/** - * Implementation of {@link ParameterNameDiscoverer} that uses the LocalVariableTable - * information in the method attributes to discover parameter names. Returns - * {@code null} if the class file was compiled without debug information. - * - *

Uses ObjectWeb's ASM library for analyzing class files. Each discoverer instance - * caches the ASM discovered information for each introspected Class, in a thread-safe - * manner. It is recommended to reuse ParameterNameDiscoverer instances as far as possible. - * - *

This class is deprecated in the 6.0 generation and scheduled for removal in 6.1 - * since it is effectively superseded by {@link StandardReflectionParameterNameDiscoverer}. - * For the time being, this discoverer logs a warning every time it actually inspects a - * class file which is particularly useful for identifying remaining gaps in usage of - * the standard "-parameters" compiler flag, and also unintended over-inspection of - * e.g. JDK core library classes (which are not compiled with the "-parameters" flag). - * - * @author Adrian Colyer - * @author Costin Leau - * @author Juergen Hoeller - * @author Chris Beams - * @author Sam Brannen - * @since 2.0 - * @see StandardReflectionParameterNameDiscoverer - * @see DefaultParameterNameDiscoverer - * @deprecated as of 6.0.1, in favor of {@link StandardReflectionParameterNameDiscoverer} - * (with the "-parameters" compiler flag) - */ -@Deprecated(since = "6.0.1", forRemoval = true) -public class LocalVariableTableParameterNameDiscoverer implements ParameterNameDiscoverer { - - private static final Log logger = LogFactory.getLog(LocalVariableTableParameterNameDiscoverer.class); - - // marker object for classes that do not have any debug info - private static final Map NO_DEBUG_INFO_MAP = Collections.emptyMap(); - - // the cache uses a nested index (value is a map) to keep the top level cache relatively small in size - private final Map, Map> parameterNamesCache = new ConcurrentHashMap<>(32); - - - @Override - @Nullable - public String[] getParameterNames(Method method) { - Method originalMethod = BridgeMethodResolver.findBridgedMethod(method); - return doGetParameterNames(originalMethod); - } - - @Override - @Nullable - public String[] getParameterNames(Constructor ctor) { - return doGetParameterNames(ctor); - } - - @Nullable - private String[] doGetParameterNames(Executable executable) { - Class declaringClass = executable.getDeclaringClass(); - Map map = this.parameterNamesCache.computeIfAbsent(declaringClass, this::inspectClass); - return (map != NO_DEBUG_INFO_MAP ? map.get(executable) : null); - } - - /** - * Inspects the target class. - *

Exceptions will be logged, and a marker map returned to indicate the - * lack of debug information. - */ - private Map inspectClass(Class clazz) { - InputStream is = clazz.getResourceAsStream(ClassUtils.getClassFileName(clazz)); - if (is == null) { - // We couldn't load the class file, which is not fatal as it - // simply means this method of discovering parameter names won't work. - if (logger.isDebugEnabled()) { - logger.debug("Cannot find '.class' file for class [" + clazz + - "] - unable to determine constructor/method parameter names"); - } - return NO_DEBUG_INFO_MAP; - } - // We cannot use try-with-resources here for the InputStream, since we have - // custom handling of the close() method in a finally-block. - try { - ClassReader classReader = new ClassReader(is); - Map map = new ConcurrentHashMap<>(32); - classReader.accept(new ParameterNameDiscoveringVisitor(clazz, map), 0); - if (logger.isWarnEnabled()) { - logger.warn("Using deprecated '-debug' fallback for parameter name resolution. Compile the " + - "affected code with '-parameters' instead or avoid its introspection: " + clazz.getName()); - } - return map; - } - catch (IOException ex) { - if (logger.isDebugEnabled()) { - logger.debug("Exception thrown while reading '.class' file for class [" + clazz + - "] - unable to determine constructor/method parameter names", ex); - } - } - catch (IllegalArgumentException ex) { - if (logger.isDebugEnabled()) { - logger.debug("ASM ClassReader failed to parse class file [" + clazz + - "], probably due to a new Java class file version that isn't supported yet " + - "- unable to determine constructor/method parameter names", ex); - } - } - finally { - try { - is.close(); - } - catch (IOException ex) { - // ignore - } - } - return NO_DEBUG_INFO_MAP; - } - - - /** - * Helper class that inspects all methods and constructors and then - * attempts to find the parameter names for the given {@link Executable}. - */ - private static class ParameterNameDiscoveringVisitor extends ClassVisitor { - - private static final String STATIC_CLASS_INIT = ""; - - private final Class clazz; - - private final Map executableMap; - - public ParameterNameDiscoveringVisitor(Class clazz, Map executableMap) { - super(SpringAsmInfo.ASM_VERSION); - this.clazz = clazz; - this.executableMap = executableMap; - } - - @Override - @Nullable - public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { - // exclude synthetic + bridged && static class initialization - if (!isSyntheticOrBridged(access) && !STATIC_CLASS_INIT.equals(name)) { - return new LocalVariableTableVisitor(this.clazz, this.executableMap, name, desc, isStatic(access)); - } - return null; - } - - private static boolean isSyntheticOrBridged(int access) { - return (((access & Opcodes.ACC_SYNTHETIC) | (access & Opcodes.ACC_BRIDGE)) > 0); - } - - private static boolean isStatic(int access) { - return ((access & Opcodes.ACC_STATIC) > 0); - } - } - - - private static class LocalVariableTableVisitor extends MethodVisitor { - - private static final String CONSTRUCTOR = ""; - - private final Class clazz; - - private final Map executableMap; - - private final String name; - - private final Type[] args; - - private final String[] parameterNames; - - private final boolean isStatic; - - private boolean hasLvtInfo = false; - - /* - * The nth entry contains the slot index of the LVT table entry holding the - * argument name for the nth parameter. - */ - private final int[] lvtSlotIndex; - - public LocalVariableTableVisitor(Class clazz, Map map, String name, String desc, boolean isStatic) { - super(SpringAsmInfo.ASM_VERSION); - this.clazz = clazz; - this.executableMap = map; - this.name = name; - this.args = Type.getArgumentTypes(desc); - this.parameterNames = new String[this.args.length]; - this.isStatic = isStatic; - this.lvtSlotIndex = computeLvtSlotIndices(isStatic, this.args); - } - - @Override - public void visitLocalVariable(String name, String description, String signature, Label start, Label end, int index) { - this.hasLvtInfo = true; - for (int i = 0; i < this.lvtSlotIndex.length; i++) { - if (this.lvtSlotIndex[i] == index) { - this.parameterNames[i] = name; - } - } - } - - @Override - public void visitEnd() { - if (this.hasLvtInfo || (this.isStatic && this.parameterNames.length == 0)) { - // visitLocalVariable will never be called for static no args methods - // which doesn't use any local variables. - // This means that hasLvtInfo could be false for that kind of methods - // even if the class has local variable info. - this.executableMap.put(resolveExecutable(), this.parameterNames); - } - } - - private Executable resolveExecutable() { - ClassLoader loader = this.clazz.getClassLoader(); - Class[] argTypes = new Class[this.args.length]; - for (int i = 0; i < this.args.length; i++) { - argTypes[i] = ClassUtils.resolveClassName(this.args[i].getClassName(), loader); - } - try { - if (CONSTRUCTOR.equals(this.name)) { - return this.clazz.getDeclaredConstructor(argTypes); - } - return this.clazz.getDeclaredMethod(this.name, argTypes); - } - catch (NoSuchMethodException ex) { - throw new IllegalStateException("Method [" + this.name + - "] was discovered in the .class file but cannot be resolved in the class object", ex); - } - } - - private static int[] computeLvtSlotIndices(boolean isStatic, Type[] paramTypes) { - int[] lvtIndex = new int[paramTypes.length]; - int nextIndex = (isStatic ? 0 : 1); - for (int i = 0; i < paramTypes.length; i++) { - lvtIndex[i] = nextIndex; - if (isWideType(paramTypes[i])) { - nextIndex += 2; - } - else { - nextIndex++; - } - } - return lvtIndex; - } - - private static boolean isWideType(Type aType) { - // float is not a wide type - return (aType == Type.LONG_TYPE || aType == Type.DOUBLE_TYPE); - } - } - -} diff --git a/spring-core/src/test/java/org/springframework/core/LocalVariableTableParameterNameDiscovererTests.java b/spring-core/src/test/java/org/springframework/core/LocalVariableTableParameterNameDiscovererTests.java deleted file mode 100644 index 5a00a7aa9d..0000000000 --- a/spring-core/src/test/java/org/springframework/core/LocalVariableTableParameterNameDiscovererTests.java +++ /dev/null @@ -1,326 +0,0 @@ -/* - * Copyright 2002-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. - * You may obtain a copy of the License at - * - * https://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.springframework.core; - -import java.awt.Component; -import java.io.PrintStream; -import java.lang.reflect.Constructor; -import java.lang.reflect.Method; -import java.util.Date; - -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; - -import org.springframework.tests.sample.objects.TestObject; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Adrian Colyer - */ -class LocalVariableTableParameterNameDiscovererTests { - - @SuppressWarnings("removal") - private final ParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer(); - - - @Test - void methodParameterNameDiscoveryNoArgs() throws NoSuchMethodException { - Method getName = TestObject.class.getMethod("getName"); - String[] names = discoverer.getParameterNames(getName); - assertThat(names).as("should find method info").isNotNull(); - assertThat(names).as("no argument names").isEmpty(); - } - - @Test - void methodParameterNameDiscoveryWithArgs() throws NoSuchMethodException { - Method setName = TestObject.class.getMethod("setName", String.class); - String[] names = discoverer.getParameterNames(setName); - assertThat(names).as("should find method info").isNotNull(); - assertThat(names).as("one argument").hasSize(1); - assertThat(names[0]).isEqualTo("name"); - } - - @Test - void consParameterNameDiscoveryNoArgs() throws NoSuchMethodException { - Constructor noArgsCons = TestObject.class.getConstructor(); - String[] names = discoverer.getParameterNames(noArgsCons); - assertThat(names).as("should find cons info").isNotNull(); - assertThat(names).as("no argument names").isEmpty(); - } - - @Test - void consParameterNameDiscoveryArgs() throws NoSuchMethodException { - Constructor twoArgCons = TestObject.class.getConstructor(String.class, int.class); - String[] names = discoverer.getParameterNames(twoArgCons); - assertThat(names).as("should find cons info").isNotNull(); - assertThat(names).as("one argument").hasSize(2); - assertThat(names[0]).isEqualTo("name"); - assertThat(names[1]).isEqualTo("age"); - } - - @Test - void staticMethodParameterNameDiscoveryNoArgs() throws NoSuchMethodException { - Method m = getClass().getMethod("staticMethodNoLocalVars"); - String[] names = discoverer.getParameterNames(m); - assertThat(names).as("should find method info").isNotNull(); - assertThat(names).as("no argument names").isEmpty(); - } - - @Test - void overloadedStaticMethod() throws Exception { - Class clazz = this.getClass(); - - Method m1 = clazz.getMethod("staticMethod", Long.TYPE, Long.TYPE); - String[] names = discoverer.getParameterNames(m1); - assertThat(names).as("should find method info").isNotNull(); - assertThat(names).as("two arguments").hasSize(2); - assertThat(names[0]).isEqualTo("x"); - assertThat(names[1]).isEqualTo("y"); - - Method m2 = clazz.getMethod("staticMethod", Long.TYPE, Long.TYPE, Long.TYPE); - names = discoverer.getParameterNames(m2); - assertThat(names).as("should find method info").isNotNull(); - assertThat(names).as("three arguments").hasSize(3); - assertThat(names[0]).isEqualTo("x"); - assertThat(names[1]).isEqualTo("y"); - assertThat(names[2]).isEqualTo("z"); - } - - @Test - void overloadedStaticMethodInInnerClass() throws Exception { - Class clazz = InnerClass.class; - - Method m1 = clazz.getMethod("staticMethod", Long.TYPE); - String[] names = discoverer.getParameterNames(m1); - assertThat(names).as("should find method info").isNotNull(); - assertThat(names).as("one argument").hasSize(1); - assertThat(names[0]).isEqualTo("x"); - - Method m2 = clazz.getMethod("staticMethod", Long.TYPE, Long.TYPE); - names = discoverer.getParameterNames(m2); - assertThat(names).as("should find method info").isNotNull(); - assertThat(names).as("two arguments").hasSize(2); - assertThat(names[0]).isEqualTo("x"); - assertThat(names[1]).isEqualTo("y"); - } - - @Test - void overloadedMethod() throws Exception { - Class clazz = this.getClass(); - - Method m1 = clazz.getMethod("instanceMethod", Double.TYPE, Double.TYPE); - String[] names = discoverer.getParameterNames(m1); - assertThat(names).as("should find method info").isNotNull(); - assertThat(names).as("two arguments").hasSize(2); - assertThat(names[0]).isEqualTo("x"); - assertThat(names[1]).isEqualTo("y"); - - Method m2 = clazz.getMethod("instanceMethod", Double.TYPE, Double.TYPE, Double.TYPE); - names = discoverer.getParameterNames(m2); - assertThat(names).as("should find method info").isNotNull(); - assertThat(names).as("three arguments").hasSize(3); - assertThat(names[0]).isEqualTo("x"); - assertThat(names[1]).isEqualTo("y"); - assertThat(names[2]).isEqualTo("z"); - } - - @Test - void overloadedMethodInInnerClass() throws Exception { - Class clazz = InnerClass.class; - - Method m1 = clazz.getMethod("instanceMethod", String.class); - String[] names = discoverer.getParameterNames(m1); - assertThat(names).as("should find method info").isNotNull(); - assertThat(names).as("one argument").hasSize(1); - assertThat(names[0]).isEqualTo("aa"); - - Method m2 = clazz.getMethod("instanceMethod", String.class, String.class); - names = discoverer.getParameterNames(m2); - assertThat(names).as("should find method info").isNotNull(); - assertThat(names).as("two arguments").hasSize(2); - assertThat(names[0]).isEqualTo("aa"); - assertThat(names[1]).isEqualTo("bb"); - } - - @Test - void generifiedClass() throws Exception { - Class clazz = GenerifiedClass.class; - - Constructor ctor = clazz.getDeclaredConstructor(Object.class); - String[] names = discoverer.getParameterNames(ctor); - assertThat(names).hasSize(1); - assertThat(names[0]).isEqualTo("key"); - - ctor = clazz.getDeclaredConstructor(Object.class, Object.class); - names = discoverer.getParameterNames(ctor); - assertThat(names).hasSize(2); - assertThat(names[0]).isEqualTo("key"); - assertThat(names[1]).isEqualTo("value"); - - Method m = clazz.getMethod("generifiedStaticMethod", Object.class); - names = discoverer.getParameterNames(m); - assertThat(names).hasSize(1); - assertThat(names[0]).isEqualTo("param"); - - m = clazz.getMethod("generifiedMethod", Object.class, long.class, Object.class, Object.class); - names = discoverer.getParameterNames(m); - assertThat(names).hasSize(4); - assertThat(names[0]).isEqualTo("param"); - assertThat(names[1]).isEqualTo("x"); - assertThat(names[2]).isEqualTo("key"); - assertThat(names[3]).isEqualTo("value"); - - m = clazz.getMethod("voidStaticMethod", Object.class, long.class, int.class); - names = discoverer.getParameterNames(m); - assertThat(names).hasSize(3); - assertThat(names[0]).isEqualTo("obj"); - assertThat(names[1]).isEqualTo("x"); - assertThat(names[2]).isEqualTo("i"); - - m = clazz.getMethod("nonVoidStaticMethod", Object.class, long.class, int.class); - names = discoverer.getParameterNames(m); - assertThat(names).hasSize(3); - assertThat(names[0]).isEqualTo("obj"); - assertThat(names[1]).isEqualTo("x"); - assertThat(names[2]).isEqualTo("i"); - - m = clazz.getMethod("getDate"); - names = discoverer.getParameterNames(m); - assertThat(names).isEmpty(); - } - - @Disabled("Ignored because Ubuntu packages OpenJDK with debug symbols enabled. See SPR-8078.") - @Test - void classesWithoutDebugSymbols() throws Exception { - // JDK classes don't have debug information (usually) - Class clazz = Component.class; - String methodName = "list"; - - Method m = clazz.getMethod(methodName); - String[] names = discoverer.getParameterNames(m); - assertThat(names).isNull(); - - m = clazz.getMethod(methodName, PrintStream.class); - names = discoverer.getParameterNames(m); - assertThat(names).isNull(); - - m = clazz.getMethod(methodName, PrintStream.class, int.class); - names = discoverer.getParameterNames(m); - assertThat(names).isNull(); - } - - - public static void staticMethodNoLocalVars() { - } - - public static long staticMethod(long x, long y) { - long u = x * y; - return u; - } - - public static long staticMethod(long x, long y, long z) { - long u = x * y * z; - return u; - } - - public double instanceMethod(double x, double y) { - double u = x * y; - return u; - } - - public double instanceMethod(double x, double y, double z) { - double u = x * y * z; - return u; - } - - - public static class InnerClass { - - public int waz = 0; - - public InnerClass() { - } - - public InnerClass(String firstArg, long secondArg, Object thirdArg) { - long foo = 0; - short bar = 10; - this.waz = (int) (foo + bar); - } - - public String instanceMethod(String aa) { - return aa; - } - - public String instanceMethod(String aa, String bb) { - return aa + bb; - } - - public static long staticMethod(long x) { - long u = x; - return u; - } - - public static long staticMethod(long x, long y) { - long u = x * y; - return u; - } - } - - - public static class GenerifiedClass { - - private static long date; - - static { - // some custom static bloc or - date = new Date().getTime(); - } - - public GenerifiedClass() { - this(null, null); - } - - public GenerifiedClass(K key) { - this(key, null); - } - - public GenerifiedClass(K key, V value) { - } - - public static

long generifiedStaticMethod(P param) { - return date; - } - - public

void generifiedMethod(P param, long x, K key, V value) { - // nothing - } - - public static void voidStaticMethod(Object obj, long x, int i) { - // nothing - } - - public static long nonVoidStaticMethod(Object obj, long x, int i) { - return date; - } - - public static long getDate() { - return date; - } - } - -}