Index: java/javax/el/ImportHandler.java =================================================================== --- java/javax/el/ImportHandler.java (revision 1834353) +++ java/javax/el/ImportHandler.java (working copy) @@ -19,9 +19,11 @@ import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; -import java.util.ArrayList; -import java.util.List; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; import java.util.Map; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; /** @@ -29,7 +31,242 @@ */ public class ImportHandler { - private List packageNames = new ArrayList<>(); + private static final Map> standardPackages = new HashMap<>(); + + static { + // Servlet 4.0 + Set servletClassNames = new HashSet<>(); + // Interfaces + servletClassNames.add("AsyncContext"); + servletClassNames.add("AsyncListener"); + servletClassNames.add("Filter"); + servletClassNames.add("FilterChain"); + servletClassNames.add("FilterConfig"); + servletClassNames.add("FilterRegistration"); + servletClassNames.add("FilterRegistration.Dynamic"); + servletClassNames.add("ReadListener"); + servletClassNames.add("Registration"); + servletClassNames.add("Registration.Dynamic"); + servletClassNames.add("RequestDispatcher"); + servletClassNames.add("Servlet"); + servletClassNames.add("ServletConfig"); + servletClassNames.add("ServletContainerInitializer"); + servletClassNames.add("ServletContext"); + servletClassNames.add("ServletContextAttributeListener"); + servletClassNames.add("ServletContextListener"); + servletClassNames.add("ServletRegistration"); + servletClassNames.add("ServletRegistration.Dynamic"); + servletClassNames.add("ServletRequest"); + servletClassNames.add("ServletRequestAttributeListener"); + servletClassNames.add("ServletRequestListener"); + servletClassNames.add("ServletResponse"); + servletClassNames.add("SessionCookieConfig"); + servletClassNames.add("SingleThreadModel"); + servletClassNames.add("WriteListener"); + // Classes + servletClassNames.add("AsyncEvent"); + servletClassNames.add("GenericFilter"); + servletClassNames.add("GenericServlet"); + servletClassNames.add("HttpConstraintElement"); + servletClassNames.add("HttpMethodConstraintElement"); + servletClassNames.add("MultipartConfigElement"); + servletClassNames.add("ServletContextAttributeEvent"); + servletClassNames.add("ServletContextEvent"); + servletClassNames.add("ServletInputStream"); + servletClassNames.add("ServletOutputStream"); + servletClassNames.add("ServletRequestAttributeEvent"); + servletClassNames.add("ServletRequestEvent"); + servletClassNames.add("ServletRequestWrapper"); + servletClassNames.add("ServletResponseWrapper"); + servletClassNames.add("ServletSecurityElement"); + // Enums + servletClassNames.add("DispatcherType"); + servletClassNames.add("SessionTrackingMode"); + // Exceptions + servletClassNames.add("ServletException"); + servletClassNames.add("UnavailableException"); + standardPackages.put("javax.servlet", servletClassNames); + + // Servlet 4.0 + Set servletHttpClassNames = new HashSet<>(); + // Interfaces + servletHttpClassNames.add("HttpServletMapping"); + servletHttpClassNames.add("HttpServletRequest"); + servletHttpClassNames.add("HttpServletResponse"); + servletHttpClassNames.add("HttpSession"); + servletHttpClassNames.add("HttpSessionActivationListener"); + servletHttpClassNames.add("HttpSessionAttributeListener"); + servletHttpClassNames.add("HttpSessionBindingListener"); + servletHttpClassNames.add("HttpSessionContext"); + servletHttpClassNames.add("HttpSessionIdListener"); + servletHttpClassNames.add("HttpSessionListener"); + servletHttpClassNames.add("HttpUpgradeHandler"); + servletHttpClassNames.add("Part"); + servletHttpClassNames.add("PushBuilder"); + servletHttpClassNames.add("WebConnection"); + // Classes + servletHttpClassNames.add("Cookie"); + servletHttpClassNames.add("HttpFilter"); + servletHttpClassNames.add("HttpServlet"); + servletHttpClassNames.add("HttpServletRequestWrapper"); + servletHttpClassNames.add("HttpServletResponseWrapper"); + servletHttpClassNames.add("HttpSessionBindingEvent"); + servletHttpClassNames.add("HttpSessionEvent"); + servletHttpClassNames.add("HttpUtils"); + // Enums + servletHttpClassNames.add("MappingMatch"); + standardPackages.put("javax.servlet.http", servletHttpClassNames); + + // JSP 2.3 + Set servletJspClassNames = new HashSet<>(); + //Interfaces + servletJspClassNames.add("HttpJspPage"); + servletJspClassNames.add("JspApplicationContext"); + servletJspClassNames.add("JspPage"); + // Classes + servletJspClassNames.add("ErrorData"); + servletJspClassNames.add("JspContext"); + servletJspClassNames.add("JspEngineInfo"); + servletJspClassNames.add("JspFactory"); + servletJspClassNames.add("JspWriter"); + servletJspClassNames.add("PageContext"); + servletJspClassNames.add("Exceptions"); + servletJspClassNames.add("JspException"); + servletJspClassNames.add("JspTagException"); + servletJspClassNames.add("SkipPageException"); + standardPackages.put("javax.servlet.jsp", servletJspClassNames); + + Set javaLangClassNames = new HashSet<>(); + // Taken from Java 11 EA18 Javadoc + // Interfaces + javaLangClassNames.add("Appendable"); + javaLangClassNames.add("AutoCloseable"); + javaLangClassNames.add("CharSequence"); + javaLangClassNames.add("Cloneable"); + javaLangClassNames.add("Comparable"); + javaLangClassNames.add("Iterable"); + javaLangClassNames.add("ProcessHandle"); + javaLangClassNames.add("ProcessHandle.Info"); + javaLangClassNames.add("Readable"); + javaLangClassNames.add("Runnable"); + javaLangClassNames.add("StackWalker.StackFrame"); + javaLangClassNames.add("System.Logger"); + javaLangClassNames.add("Thread.UncaughtExceptionHandler"); + //Classes + javaLangClassNames.add("Boolean"); + javaLangClassNames.add("Byte"); + javaLangClassNames.add("Character"); + javaLangClassNames.add("Character.Subset"); + javaLangClassNames.add("Character.UnicodeBlock"); + javaLangClassNames.add("Class"); + javaLangClassNames.add("ClassLoader"); + javaLangClassNames.add("ClassValue"); + javaLangClassNames.add("Compiler"); + javaLangClassNames.add("Double"); + javaLangClassNames.add("Enum"); + javaLangClassNames.add("Float"); + javaLangClassNames.add("InheritableThreadLocal"); + javaLangClassNames.add("Integer"); + javaLangClassNames.add("Long"); + javaLangClassNames.add("Math"); + javaLangClassNames.add("Module"); + javaLangClassNames.add("ModuleLayer"); + javaLangClassNames.add("ModuleLayer.Controller"); + javaLangClassNames.add("Number"); + javaLangClassNames.add("Object"); + javaLangClassNames.add("Package"); + javaLangClassNames.add("Process"); + javaLangClassNames.add("ProcessBuilder"); + javaLangClassNames.add("ProcessBuilder.Redirect"); + javaLangClassNames.add("Runtime"); + javaLangClassNames.add("Runtime.Version"); + javaLangClassNames.add("RuntimePermission"); + javaLangClassNames.add("SecurityManager"); + javaLangClassNames.add("Short"); + javaLangClassNames.add("StackTraceElement"); + javaLangClassNames.add("StackWalker"); + javaLangClassNames.add("StrictMath"); + javaLangClassNames.add("String"); + javaLangClassNames.add("StringBuffer"); + javaLangClassNames.add("StringBuilder"); + javaLangClassNames.add("System"); + javaLangClassNames.add("System.LoggerFinder"); + javaLangClassNames.add("Thread"); + javaLangClassNames.add("ThreadGroup"); + javaLangClassNames.add("ThreadLocal"); + javaLangClassNames.add("Throwable"); + javaLangClassNames.add("Void"); + //Enums + javaLangClassNames.add("Character.UnicodeScript"); + javaLangClassNames.add("ProcessBuilder.Redirect.Type"); + javaLangClassNames.add("StackWalker.Option"); + javaLangClassNames.add("System.Logger.Level"); + javaLangClassNames.add("Thread.State"); + //Exceptions + javaLangClassNames.add("ArithmeticException"); + javaLangClassNames.add("ArrayIndexOutOfBoundsException"); + javaLangClassNames.add("ArrayStoreException"); + javaLangClassNames.add("ClassCastException"); + javaLangClassNames.add("ClassNotFoundException"); + javaLangClassNames.add("CloneNotSupportedException"); + javaLangClassNames.add("EnumConstantNotPresentException"); + javaLangClassNames.add("Exception"); + javaLangClassNames.add("IllegalAccessException"); + javaLangClassNames.add("IllegalArgumentException"); + javaLangClassNames.add("IllegalCallerException"); + javaLangClassNames.add("IllegalMonitorStateException"); + javaLangClassNames.add("IllegalStateException"); + javaLangClassNames.add("IllegalThreadStateException"); + javaLangClassNames.add("IndexOutOfBoundsException"); + javaLangClassNames.add("InstantiationException"); + javaLangClassNames.add("InterruptedException"); + javaLangClassNames.add("LayerInstantiationException"); + javaLangClassNames.add("NegativeArraySizeException"); + javaLangClassNames.add("NoSuchFieldException"); + javaLangClassNames.add("NoSuchMethodException"); + javaLangClassNames.add("NullPointerException"); + javaLangClassNames.add("NumberFormatException"); + javaLangClassNames.add("ReflectiveOperationException"); + javaLangClassNames.add("RuntimeException"); + javaLangClassNames.add("SecurityException"); + javaLangClassNames.add("StringIndexOutOfBoundsException"); + javaLangClassNames.add("TypeNotPresentException"); + javaLangClassNames.add("UnsupportedOperationException"); + //Errors + javaLangClassNames.add("AbstractMethodError"); + javaLangClassNames.add("AssertionError"); + javaLangClassNames.add("BootstrapMethodError"); + javaLangClassNames.add("ClassCircularityError"); + javaLangClassNames.add("ClassFormatError"); + javaLangClassNames.add("Error"); + javaLangClassNames.add("ExceptionInInitializerError"); + javaLangClassNames.add("IllegalAccessError"); + javaLangClassNames.add("IncompatibleClassChangeError"); + javaLangClassNames.add("InstantiationError"); + javaLangClassNames.add("InternalError"); + javaLangClassNames.add("LinkageError"); + javaLangClassNames.add("NoClassDefFoundError"); + javaLangClassNames.add("NoSuchFieldError"); + javaLangClassNames.add("NoSuchMethodError"); + javaLangClassNames.add("OutOfMemoryError"); + javaLangClassNames.add("StackOverflowError"); + javaLangClassNames.add("ThreadDeath"); + javaLangClassNames.add("UnknownError"); + javaLangClassNames.add("UnsatisfiedLinkError"); + javaLangClassNames.add("UnsupportedClassVersionError"); + javaLangClassNames.add("VerifyError"); + javaLangClassNames.add("VirtualMachineError"); + //Annotation Types + javaLangClassNames.add("Deprecated"); + javaLangClassNames.add("FunctionalInterface"); + javaLangClassNames.add("Override"); + javaLangClassNames.add("SafeVarargs"); + javaLangClassNames.add("SuppressWarnings"); + standardPackages.put("java.lang", javaLangClassNames); + + } + + private Map> packageNames = new ConcurrentHashMap<>(); private Map classNames = new ConcurrentHashMap<>(); private Map> clazzes = new ConcurrentHashMap<>(); private Map> statics = new ConcurrentHashMap<>(); @@ -127,7 +364,12 @@ // a) for sake of performance when used in JSPs (BZ 57142), // b) java.lang.Package.getPackage(name) is not reliable (BZ 57574), // c) such check is not required by specification. - packageNames.add(name); + Set preloaded = standardPackages.get(name); + if (preloaded == null) { + packageNames.put(name, Collections.emptySet()); + } else { + packageNames.put(name, preloaded); + } } @@ -159,8 +401,17 @@ // Search the package imports - note there may be multiple matches // (which correctly triggers an error) - for (String p : packageNames) { - className = p + '.' + name; + for (Map.Entry> entry : packageNames.entrySet()) { + if (!entry.getValue().isEmpty()) { + // Standard package where we know all the class names + if (!entry.getValue().contains(name)) { + // Requested name isn't in the list so it isn't in this + // package so move on to next package. This allows the + // class loader look-up to be skipped. + continue; + } + } + className = entry.getKey() + '.' + name; Class clazz = findClass(className, false); if (clazz != null) { if (result != null) { Index: test/javax/el/TestImportHandlerStandardPackages.java =================================================================== --- test/javax/el/TestImportHandlerStandardPackages.java (nonexistent) +++ test/javax/el/TestImportHandlerStandardPackages.java (working copy) @@ -0,0 +1,155 @@ +/* + * 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 javax.el; + +import java.io.File; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.net.URL; +import java.util.Enumeration; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Stream; + +import org.junit.Assert; +import org.junit.Test; + +import org.apache.tomcat.util.compat.JreCompat; + +public class TestImportHandlerStandardPackages { + + @Test + public void testClassListsAreComplete() throws Exception { + // Use reflection to get hold of the internal Map + Class clazz = ImportHandler.class; + Field f = clazz.getDeclaredField("standardPackages"); + f.setAccessible(true); + Object obj = f.get(null); + + Assert.assertTrue("Not Map", obj instanceof Map); + + @SuppressWarnings("unchecked") + Map> standardPackageName = (Map>) obj; + + for (Map.Entry> entry : standardPackageName.entrySet()) { + checkPackageClassList(entry.getKey(), entry.getValue()); + } + } + + + private void checkPackageClassList(String packageName, Set classNames) throws Exception { + + if ("java.lang".equals(packageName)) { + // The code below is designed to run on Java 9 so skip this check + // if running on Java 8. The test has previously been run with Java + // 9 (and later) so it is not necessary that this is executed on + // every test run. The intention is that it will catch new classes + // when the tests are run on a newer JRE. + // The latest version of the JRE where this test is known to pass is + // Java 11, Early Access 15 + if (!JreCompat.isJre9Available()) { + return; + } + getJavaBaseClasses().filter(c -> (c.startsWith("java/lang/"))) + .filter(c -> c.lastIndexOf('/') == 9) // Exclude sub-packages + .filter(c -> c.endsWith(".class")) // Exclude non-class resources + .map(c -> c.substring(10, c.length() - 6)) // Extract class name + .map(c-> { + try { + return Class.forName("java.lang." + c); // Get the class object + } catch (ClassNotFoundException e) { + throw new RuntimeException(); + } + }) + .filter(c -> Modifier.isPublic(c.getModifiers())) // Exclude non-public classes + .map(c -> c.getName().substring(10)) // Back to the class name + .map(c -> c.replace('$', '.')) + .filter(c -> !classNames.contains(c)) // Skip classes already listed + .filter(c -> !c.startsWith("FdLibm.")) // Skip public inner class + .filter(c -> !c.startsWith("LiveStackFrame.")) // Skip public inner class + .filter(c -> !c.startsWith("WeakPairMap.")) // Skip public inner class + .forEach(c -> Assert.fail("java.lang." + c)); // Should have in list + } else { + // When this test runs, the class loader will be loading resources + // from a directory for each of these packages. + ClassLoader cl = Thread.currentThread().getContextClassLoader(); + + String path = packageName.replace('.', '/'); + Enumeration resources = cl.getResources(path); + while (resources.hasMoreElements()) { + URL resource = resources.nextElement(); + File dir = new File(resource.toURI()); + String[] files = dir.list(); + for (String file : files) { + if (!file.endsWith(".class")) { + // Skip non-class resoucres + continue; + } + if (file.startsWith("Test")) { + // Skip test resources + continue; + } + if (file.matches(".*\\$[0-9]?\\.class")) { + // Skip anonymous inner classes + continue; + } + String name = file.substring(0, file.length() - 6); + name = name.replace('$', '.'); + if (classNames.contains(name)) { + // Skip classes already known + continue; + } + File f = new File (dir, file); + if (!f.isFile()) { + // Skip directories + continue; + } + Class clazz = Class.forName(packageName + "." + name); + if (!Modifier.isPublic(clazz.getModifiers())) { + // Skip non-public classes + continue; + } + + // There should be nothing left unless we missed something + Assert.fail(packageName + "." + name); + } + } + } + } + + + private static Stream getJavaBaseClasses() throws Exception { + // While this code is only used on Java 9 and later, it needs to compile + // with Java 8 so use reflection foir now. + Class clazzModuleFinder = Class.forName("java.lang.module.ModuleFinder"); + Class clazzModuleReference = Class.forName("java.lang.module.ModuleReference"); + Class clazzModuleReader = Class.forName("java.lang.module.ModuleReader"); + + // Returns ModuleFinder + Object mf = clazzModuleFinder.getMethod("ofSystem").invoke(null); + // Returns ModuleReference + Object mRef = ((Optional) clazzModuleFinder.getMethod( + "find", String.class).invoke(mf, "java.base")).get(); + // Returns ModuleReader + Object mr = clazzModuleReference.getMethod("open").invoke(mRef); + // Returns the contents of the module + @SuppressWarnings("unchecked") + Stream result = (Stream) clazzModuleReader.getMethod("list").invoke(mr); + return result; + } +} Property changes on: test/javax/el/TestImportHandlerStandardPackages.java ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Index: test/javax/el/TesterImportHandlerPerformance.java =================================================================== --- test/javax/el/TesterImportHandlerPerformance.java (nonexistent) +++ test/javax/el/TesterImportHandlerPerformance.java (working copy) @@ -0,0 +1,49 @@ +/* + * 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 javax.el; + +import org.junit.Test; + +public class TesterImportHandlerPerformance { + + /* + * This test is looking at the cost of looking up a class when the standard + * JSP package imports are present: + * - java.lang + * - javax.servlet + * - javax.servlet.http + * - javax.servlet.jsp + * + * Before optimisation, this test took ~4.6s on markt's desktop + * After optimisation, this test took ~0.05s on markt's desktop + */ + @Test + public void testBug62453() throws Exception { + long totalTime = 0; + for (int i = 0; i < 100000; i++) { + ImportHandler ih = new ImportHandler(); + ih.importPackage("javax.servlet"); + ih.importPackage("javax.servlet.http"); + ih.importPackage("javax.servlet.jsp"); + long start = System.nanoTime(); + ih.resolveClass("unknown"); + long end = System.nanoTime(); + totalTime += (end -start); + } + System.out.println("Time taken: " + totalTime + "ns"); + } +} Property changes on: test/javax/el/TesterImportHandlerPerformance.java ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Index: test/javax/servlet/jsp/el/TestScopedAttributeELResolver.java =================================================================== --- test/javax/servlet/jsp/el/TestScopedAttributeELResolver.java (nonexistent) +++ test/javax/servlet/jsp/el/TestScopedAttributeELResolver.java (working copy) @@ -0,0 +1,37 @@ +/* + * 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 javax.servlet.jsp.el; + +import org.junit.Assert; +import org.junit.Test; + +import org.apache.catalina.startup.TomcatBaseTest; +import org.apache.tomcat.util.buf.ByteChunk; + +public class TestScopedAttributeELResolver extends TomcatBaseTest { + + @Test + public void testBug49196() throws Exception { + getTomcatInstanceTestWebapp(true, true); + + ByteChunk res = getUrl("http://localhost:" + getPort() + + "/test/bug6nnnn/bug62453.jsp"); + + String result = res.toString(); + Assert.assertTrue(result, result.contains("OK")); + } +} Property changes on: test/javax/servlet/jsp/el/TestScopedAttributeELResolver.java ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Index: test/webapp/WEB-INF/tags/bug62453.tag =================================================================== --- test/webapp/WEB-INF/tags/bug62453.tag (nonexistent) +++ test/webapp/WEB-INF/tags/bug62453.tag (working copy) @@ -0,0 +1,26 @@ +<%-- + 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. +--%> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> +<%@ attribute name="foo" required="true" %> +<%@ attribute name="bar" %> +<%@ attribute name="baz" %> + +
+
foo: ${foo.toString()}
+
bar: ${bar.toString()}
+
baz: ${baz.toString()}
+
Index: test/webapp/bug6nnnn/bug62453.jsp =================================================================== --- test/webapp/bug6nnnn/bug62453.jsp (nonexistent) +++ test/webapp/bug6nnnn/bug62453.jsp (working copy) @@ -0,0 +1,24 @@ +<%-- + 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. +--%> +<%@ taglib prefix="tags" tagdir="/WEB-INF/tags" %> + + + +

Example of Uninitialized Tag Attributes

+ + + Property changes on: test/webapp/bug6nnnn/bug62453.jsp ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property