Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Opening a connection to a jar:war: URL created by Tomcat results in an illegal reflective access warning on Java 13+ #18631

Closed
dschulz opened this issue Oct 17, 2019 · 12 comments
Assignees
Milestone

Comments

@dschulz
Copy link

@dschulz dschulz commented Oct 17, 2019

This is a follow-up to #15844. As asked by @philwebb, attached is an example project.

WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by org.springframework.boot.loader.jar.Handler (file:/Users/dschulz/IdeaProjects/testproject/build/libs/demo-0.0.1-SNAPSHOT.jar) to constructor sun.net.www.protocol.jar.Handler()
WARNING: Please consider reporting this to the maintainers of org.springframework.boot.loader.jar.Handler
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release

Issue description

This warning is issued reproducibly when running fat jars (built with (mvn|gradle) bootJar) using springdoc-openapi dependency in Spring Boot projects. The warning is issued when accessing the URL path associated to Swagger UI provided by springdoc-openapi.

Steps to reproduce

unzip testproject.zip && cd testproject
./gradlew bootJar
java -jar build/libs/demo-0.0.1-SNAPSHOT.jar  

Now point a browser to http://localhost:8080 or explicitly http://localhost:8080/swagger-ui.html.

Tested with:

  • Spring Boot versions: 2.1.9-RELEASE, 2.2.0.RELEASE
  • Java version: 13
java -version
openjdk version "13" 2019-09-17
OpenJDK Runtime Environment (build 13+33)
OpenJDK 64-Bit Server VM (build 13+33, mixed mode, sharing)
@wilkinsona
Copy link
Member

@wilkinsona wilkinsona commented Oct 17, 2019

Thanks for the sample, @dschulz. I've reproduced the problem. It's caused by a combination of increased reflective access restrictions in JDK 13 and Tomcat's servlet context resource handling which causes Spring Boot's jar URL handler to try to fall back to the JDK's. This fall back requires the use of reflection and this is now restricted in JDK 13.

While ugly, the failure is benign on JDK 13. It can be avoided by switching to Jetty or swapped for another warning (XNIO-330) by switching to Undertow.

I don't think it's possible to fall back to the JDK's handler without using reflection so I think we'll need to rework our handler so that it copes with Tomcat's */-separated jar:war:file URLs.

@wilkinsona
Copy link
Member

@wilkinsona wilkinsona commented Oct 17, 2019

When illegal access is denied (--illegal-access=deny), accessing the Swagger UI fails with the following exception:

java.io.IOException: Unable to open root Jar file 'war:file:/Users/awilkinson/Downloads/testproject/build/libs/demo-0.0.1-SNAPSHOT.jar*/BOOT-INF/lib/swagger-ui-3.23.5.jar'
	at org.springframework.boot.loader.jar.Handler.getRootJarFile(Handler.java:316) ~[demo-0.0.1-SNAPSHOT.jar:na]
	at org.springframework.boot.loader.jar.Handler.getRootJarFileFromUrl(Handler.java:298) ~[demo-0.0.1-SNAPSHOT.jar:na]
	at org.springframework.boot.loader.jar.Handler.openConnection(Handler.java:84) ~[demo-0.0.1-SNAPSHOT.jar:na]
	at java.base/java.net.URL.openConnection(URL.java:1086) ~[na:na]
	at org.springframework.core.io.AbstractFileResolvingResource.contentLength(AbstractFileResolvingResource.java:239) ~[spring-core-5.2.0.RELEASE.jar!/:5.2.0.RELEASE]
	at org.springframework.web.servlet.resource.ResourceHttpRequestHandler.setHeaders(ResourceHttpRequestHandler.java:686) ~[spring-webmvc-5.2.0.RELEASE.jar!/:5.2.0.RELEASE]
	at org.springframework.web.servlet.resource.ResourceHttpRequestHandler.handleRequest(ResourceHttpRequestHandler.java:488) ~[spring-webmvc-5.2.0.RELEASE.jar!/:5.2.0.RELEASE]
	at org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter.handle(HttpRequestHandlerAdapter.java:53) ~[spring-webmvc-5.2.0.RELEASE.jar!/:5.2.0.RELEASE]
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040) ~[spring-webmvc-5.2.0.RELEASE.jar!/:5.2.0.RELEASE]
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943) ~[spring-webmvc-5.2.0.RELEASE.jar!/:5.2.0.RELEASE]
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) ~[spring-webmvc-5.2.0.RELEASE.jar!/:5.2.0.RELEASE]
	at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898) ~[spring-webmvc-5.2.0.RELEASE.jar!/:5.2.0.RELEASE]
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:634) ~[tomcat-embed-core-9.0.27.jar!/:9.0.27]
	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883) ~[spring-webmvc-5.2.0.RELEASE.jar!/:5.2.0.RELEASE]
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:741) ~[tomcat-embed-core-9.0.27.jar!/:9.0.27]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) ~[tomcat-embed-core-9.0.27.jar!/:9.0.27]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.27.jar!/:9.0.27]
	at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) ~[tomcat-embed-websocket-9.0.27.jar!/:9.0.27]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.27.jar!/:9.0.27]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.27.jar!/:9.0.27]
	at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-5.2.0.RELEASE.jar!/:5.2.0.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.0.RELEASE.jar!/:5.2.0.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.27.jar!/:9.0.27]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.27.jar!/:9.0.27]
	at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-5.2.0.RELEASE.jar!/:5.2.0.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.0.RELEASE.jar!/:5.2.0.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.27.jar!/:9.0.27]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.27.jar!/:9.0.27]
	at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-5.2.0.RELEASE.jar!/:5.2.0.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.0.RELEASE.jar!/:5.2.0.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.27.jar!/:9.0.27]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.27.jar!/:9.0.27]
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202) ~[tomcat-embed-core-9.0.27.jar!/:9.0.27]
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) ~[tomcat-embed-core-9.0.27.jar!/:9.0.27]
	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:526) ~[tomcat-embed-core-9.0.27.jar!/:9.0.27]
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139) ~[tomcat-embed-core-9.0.27.jar!/:9.0.27]
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) ~[tomcat-embed-core-9.0.27.jar!/:9.0.27]
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) ~[tomcat-embed-core-9.0.27.jar!/:9.0.27]
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) ~[tomcat-embed-core-9.0.27.jar!/:9.0.27]
	at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:408) ~[tomcat-embed-core-9.0.27.jar!/:9.0.27]
	at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) ~[tomcat-embed-core-9.0.27.jar!/:9.0.27]
	at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:861) ~[tomcat-embed-core-9.0.27.jar!/:9.0.27]
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1579) ~[tomcat-embed-core-9.0.27.jar!/:9.0.27]
	at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) ~[tomcat-embed-core-9.0.27.jar!/:9.0.27]
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) ~[na:na]
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) ~[na:na]
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-9.0.27.jar!/:9.0.27]
	at java.base/java.lang.Thread.run(Thread.java:830) ~[na:na]
Caused by: java.lang.IllegalStateException: Not a file URL
	at org.springframework.boot.loader.jar.Handler.getRootJarFile(Handler.java:304) ~[demo-0.0.1-SNAPSHOT.jar:na]
	... 47 common frames omitted
@wilkinsona
Copy link
Member

@wilkinsona wilkinsona commented Oct 17, 2019

Here is a possible fix. It uses a different approach for falling back when the URL is a jar:war: URL created by Tomcat. It's really hard to test so one existing test has just been tightened up to verify that the existing fall back is still used for jar:file: URLs. I'd like some more eyes on this before we consider merging it. Could you take a look please, @philwebb?

@wilkinsona wilkinsona added this to the 2.2.x milestone Oct 17, 2019
@wilkinsona wilkinsona changed the title WARNING: Illegal reflective access by org.springframework.boot.loader.jar.Handler Opening a connection to a jar:war: URL created by Tomcat results in an illegal reflective access warning on Java 13 Oct 17, 2019
@philwebb philwebb self-assigned this Oct 25, 2019
@JaquelineP
Copy link

@JaquelineP JaquelineP commented Oct 29, 2020

Any updates?

@derTobsch
Copy link

@derTobsch derTobsch commented Nov 27, 2020

We are seeing the same warning on Spring Boot Version 2.4.0 with jdk11 on the https://github.com/synyx/urlaubsverwaltung/ project see synyx/urlaubsverwaltung#1545

@philwebb philwebb modified the milestones: 2.2.x, 2.3.x Dec 16, 2020
@philwebb
Copy link
Member

@philwebb philwebb commented Dec 17, 2020

The original sample provided no longer shows the warning. I think this is due to this Tomcat update.

@philwebb
Copy link
Member

@philwebb philwebb commented Dec 17, 2020

@derTobsch Can you provide instructions on how to replicate the problem with your project? Perhaps it only happens with war packaged applications now.

@derTobsch
Copy link

@derTobsch derTobsch commented Dec 17, 2020

fyi: we are now on Spring Boot 2.4.1.

  1. Clone the repo - git@github.com:synyx/urlaubsverwaltung.git
  2. ./mvnw clean package in the root
  3. docker-compose up for mariaDB and mailhog
  4. Start the application war via java -jar -Dspring.profiles.active=demodata target/urlaubsverwaltung-4.4.0-SNAPSHOT.war

and the warning will occur see

➜  urlaubsverwaltung git:(master) java -jar -Dspring.profiles.active=demodata target/urlaubsverwaltung-4.4.0-SNAPSHOT.war                             
Urlaubsverwaltung
4.4.0-SNAPSHOT
          ////       ////////       ////
        /::::://////////////////////:::::/
       //::::::////////////////////:::::://
        //:::://////////////////////:::://
         ////////////////////////////////
         ////////////////////////////////
      /------:::::////////////:::::------/
   ....-:::::::::---..------..---:::::::::-....
    :.://///////////:........://///////////:.:
     .://////////////-.://:.-//////////////:.
     .-/////////////:.:////:.//////////////--
     :.:///////////:.:hhddhh:-:///////////:.:
    //:.-:::////::-.+o:----:++.-::////:::-.://
   //////:---...-:shdo......odhs:-...---://////
   //////////////oddddhs++ohdddds//////////////
  ///////////////+ddddddddddddddo///////////////
  ////////////////yddddddddddddy////////////////
   ////////////////ohddddddddho////////////////
    /////////////////+oossoo+/////////////////
     ////////////////////////////////////////
       ////////////////////////////////////
         ////////////////////////////////
           ////////////////////////////
               ////////////////////
                    //////////

based on Spring Boot version 2.4.1

2020-12-17 10:22:14.563  INFO 3354701 --- [           main] o.s.u.UrlaubsverwaltungApplication       : Starting UrlaubsverwaltungApplication v4.4.0-SNAPSHOT using Java 11.0.2 on turing with PID 3354701 (/home/schneider/projects/urlaubsverwaltung/urlaubsverwaltung/target/urlaubsverwaltung-4.4.0-SNAPSHOT.war started by schneider in /home/schneider/projects/urlaubsverwaltung/urlaubsverwaltung)
2020-12-17 10:22:14.584  INFO 3354701 --- [           main] o.s.u.UrlaubsverwaltungApplication       : The following profiles are active: demodata
2020-12-17 10:22:18.352  INFO 3354701 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode.
2020-12-17 10:22:18.649  INFO 3354701 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 288 ms. Found 18 JPA repository interfaces.
2020-12-17 10:22:19.641  INFO 3354701 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler@5328a9c1' of type [org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2020-12-17 10:22:19.652  INFO 3354701 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'methodSecurityMetadataSource' of type [org.springframework.security.access.method.DelegatingMethodSecurityMetadataSource] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2020-12-17 10:22:20.442  INFO 3354701 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2020-12-17 10:22:20.461  INFO 3354701 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2020-12-17 10:22:20.462  INFO 3354701 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.41]
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by org.springframework.boot.loader.jar.Handler (file:/home/--/projects/urlaubsverwaltung/urlaubsverwaltung/target/urlaubsverwaltung-4.4.0-SNAPSHOT.war) to constructor sun.net.www.protocol.jar.Handler()
WARNING: Please consider reporting this to the maintainers of org.springframework.boot.loader.jar.Handler
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release
...

I hope this will help you. :-)

@philwebb
Copy link
Member

@philwebb philwebb commented Dec 17, 2020

The error is caused by a similar Tomcat URL jar:war:file:/Users/pwebb/projects/spring-boot/samples/gh-18631/urlaubsverwaltung/target/urlaubsverwaltung-4.4.0-SNAPSHOT.war*/WEB-INF/lib/jstl-1.2.jar!/META-INF/c-1_0-rt.tld.

This time, the CachedResourceURLStreamHandler doesn't get involved, so we see the warning:

Here's the stack trace:

Thread [main] (Suspended (breakpoint at line 129 in Handler))	
	owns: TomcatEmbeddedContext  (id=111)	
	owns: StandardHost  (id=112)	
	owns: StandardEngine  (id=113)	
	owns: StandardService  (id=114)	
	owns: Object  (id=115)	
	owns: StandardServer  (id=116)	
	owns: Object  (id=117)	
	owns: Object  (id=118)	
	Handler.getFallbackHandler() line: 129	
	Handler.openFallbackConnection(URL, Exception) line: 101	
	Handler.openConnection(URL) line: 89	
	URL.openConnection() line: 1101	
	URL.openStream() line: 1167	
	TldResourcePath.openStream() line: 127	
	TldParser.parse(TldResourcePath) line: 61	
	TldScanner.parseTld(TldResourcePath) line: 275	
	TldScanner$TldScannerCallback.scan(Jar, String, boolean) line: 315	
	StandardJarScanner.process(JarScanType, JarScannerCallback, URL, String, boolean, Deque<URL>) line: 387	
	StandardJarScanner.scan(JarScanType, ServletContext, JarScannerCallback) line: 195	
	TldScanner.scanJars() line: 262	
	TldScanner.scan() line: 104	
	JasperInitializer.onStartup(Set<Class<?>>, ServletContext) line: 83	
	TomcatEmbeddedContext(StandardContext).startInternal() line: 5166	
	TomcatEmbeddedContext(LifecycleBase).start() line: 183	
	ContainerBase$StartChild.call() line: 1384	
	ContainerBase$StartChild.call() line: 1374	
	FutureTask<V>.run() line: 264	
	InlineExecutorService.execute(Runnable) line: 75	
	InlineExecutorService(AbstractExecutorService).submit(Callable<T>) line: 140	
	StandardHost(ContainerBase).startInternal() line: 909	
	StandardHost.startInternal() line: 843	
	StandardHost(LifecycleBase).start() line: 183	
	ContainerBase$StartChild.call() line: 1384	
	ContainerBase$StartChild.call() line: 1374	
	FutureTask<V>.run() line: 264	
	InlineExecutorService.execute(Runnable) line: 75	
	InlineExecutorService(AbstractExecutorService).submit(Callable<T>) line: 140	
	StandardEngine(ContainerBase).startInternal() line: 909	
	StandardEngine.startInternal() line: 262	
	StandardEngine(LifecycleBase).start() line: 183	
	StandardService.startInternal() line: 434	
	StandardService(LifecycleBase).start() line: 183	
	StandardServer.startInternal() line: 930	
	StandardServer(LifecycleBase).start() line: 183	
	Tomcat.start() line: 486	
	TomcatWebServer.initialize() line: 123	
	TomcatWebServer.<init>(Tomcat, boolean, Shutdown) line: 104	
	TomcatServletWebServerFactory.getTomcatWebServer(Tomcat) line: 451	
	TomcatServletWebServerFactory.getWebServer(ServletContextInitializer...) line: 200	
	AnnotationConfigServletWebServerApplicationContext(ServletWebServerApplicationContext).createWebServer() line: 181	
	AnnotationConfigServletWebServerApplicationContext(ServletWebServerApplicationContext).onRefresh() line: 159	
	AnnotationConfigServletWebServerApplicationContext(AbstractApplicationContext).refresh() line: 582	
	AnnotationConfigServletWebServerApplicationContext(ServletWebServerApplicationContext).refresh() line: 144	
	SpringApplication.refresh(ConfigurableApplicationContext) line: 767	
	SpringApplication.refresh(ApplicationContext) line: 759	
	SpringApplication.refreshContext(ConfigurableApplicationContext) line: 426	
	SpringApplication.run(String...) line: 326	
	SpringApplication.run(Class<?>[], String[]) line: 1309	
	SpringApplication.run(Class<?>, String...) line: 1298	
	UrlaubsverwaltungApplication.main(String[]) line: 16	
	NativeMethodAccessorImpl.invoke0(Method, Object, Object[]) line: not available [native method]	
	NativeMethodAccessorImpl.invoke(Object, Object[]) line: 64	
	DelegatingMethodAccessorImpl.invoke(Object, Object[]) line: 43	
	Method.invoke(Object, Object...) line: 564	
	MainMethodRunner.run() line: 49	
	WarLauncher(Launcher).launch(String[], String, ClassLoader) line: 107	
	WarLauncher(Launcher).launch(String[]) line: 58	
	WarLauncher.main(String[]) line: 59	
@philwebb
Copy link
Member

@philwebb philwebb commented Dec 17, 2020

Tomcat's URLs are interesting and there's probably an opportunity to replace them with our own nested JAR handling.

They take the form jar:war:file:/some.war*/WEB-INF/lib/some.jar!/entry. With a regular JDK the jar Handler is used. It takes the war:file:/some.war*/WEB-INF/lib/some.jar!/entry part an tries to read the jar file. The sun.net.www.protocol.jar.URLJarFile class is responsible getting the actual JarFile.

Since the URL is war: and not file: the getJarFile(...) method needs to do a full retrieval. This means the content is copied to a temp file so that it can be accessed.

@philwebb philwebb changed the title Opening a connection to a jar:war: URL created by Tomcat results in an illegal reflective access warning on Java 13 Opening a connection to a jar:war: URL created by Tomcat results in an illegal reflective access warning on Java 13+ Dec 18, 2020
@philwebb philwebb closed this in c4e4130 Dec 18, 2020
@philwebb
Copy link
Member

@philwebb philwebb commented Dec 18, 2020

@wilkinsona Sorry it took over a year to get to review this 😱

I think I've found another way to solve it that will work with all fallback URLs. I've pushed something in c4e4130.

philwebb added a commit that referenced this issue Dec 18, 2020
Rename "app" projects in `spring-boot-launch-script-tests` and
`spring-boot-loader-tests` to something unique.

See gh-18631
philwebb added a commit that referenced this issue Dec 18, 2020
See gh-18631
@derTobsch
Copy link

@derTobsch derTobsch commented Dec 18, 2020

that is nice to hear @philwebb - thanks! :)

@wilkinsona wilkinsona modified the milestones: 2.3.x, 2.3.8 Dec 18, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
6 participants