8299504: Resolve uses and provides at run time if the service is optional and missing

Co-authored-by: Alan Bateman <alanb@openjdk.org>
Co-authored-by: Alex Buckley <abuckley@openjdk.org>
Reviewed-by: sundar
This commit is contained in:
Alan Bateman 2025-01-25 07:25:24 +00:00
parent f446cefee0
commit 1d2eb2fbae
4 changed files with 532 additions and 304 deletions

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2014, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -75,6 +75,61 @@ import jdk.internal.vm.annotation.Stable;
* ModuleLayer.boot().configuration()}. The configuration for the boot layer
* will often be the parent when creating new configurations. </p>
*
* <h2><a id="optional-services">Optional Services</a></h2>
*
* Resolution requires that if a module {@code M} '{@code uses}' a service or
* '{@code provides}' an implementation of a service, then the service must be available
* to {@code M} at run time, either because {@code M} itself contains the service's
* package or because {@code M} reads another module that exports the service's package.
* However, it is sometimes desirable for the service's package to come from a module
* that is optional at run time, as indicated by the use of 'requires static' in this
* example:
*
* {@snippet :
* module M {
* requires static Y;
* uses p.S;
* }
*
* module Y {
* exports p;
* }
* }
*
* Resolution is resilient when a service's package comes from a module that is optional
* at run time. That is, if a module {@code M} has an optional dependency on some module
* {@code Y}, but {@code Y} is not needed at run time ({@code Y} might be observable but
* no-one reads it), then resolution at run time <i>assumes</i> that {@code Y} exported
* the service's package at compile time. Resolution at run time does not attempt to
* check whether {@code Y} is observable or (if it is observable) whether {@code Y}
* exports the service's package.
*
* <p> The module that '{@code uses}' the service, or '{@code provides}' an implementation
* of it, may depend directly on the optional module, as {@code M} does above, or may
* depend indirectly on the optional module, as shown here:
*
* {@snippet :
* module M {
* requires X;
* uses p.S;
* }
*
* module X {
* requires static transitive Y;
* }
*
* module Y {
* exports p;
* }
* }
*
* In effect, the service that {@code M} '{@code uses}', or '{@code provides}' an
* implementation of, is optional if it comes from an optional dependency. In this case,
* code in {@code M} must be prepared to deal with the class or interface that denotes
* the service being unavailable at run time. This is distinct from the more regular
* case where the service is available but no implementations of the service are
* available.
*
* <h2> Example </h2>
*
* <p> The following example uses the {@link
@ -153,7 +208,6 @@ public final class Configuration {
this.graph = g;
this.modules = Set.of(moduleArray);
this.nameToModule = Map.ofEntries(nameEntries);
this.targetPlatform = resolver.targetPlatform();
}
@ -358,10 +412,17 @@ public final class Configuration {
* module {@code M} containing package {@code p} reads another module
* that exports {@code p} to {@code M}. </p></li>
*
* <li><p> A module {@code M} declares that it "{@code uses p.S}" or
* "{@code provides p.S with ...}" but package {@code p} is neither in
* module {@code M} nor exported to {@code M} by any module that
* {@code M} reads. </p></li>
* <li><p> A module {@code M} declares that it '{@code uses p.S}' or
* '{@code provides p.S with ...}', but the package {@code p} is neither in
* module {@code M} nor exported to {@code M} by any module that {@code M}
* reads. Additionally, neither of the following is {@code true}:
* <ul>
* <li> {@code M} declares '{@code requires static}' for at least one
* module that is not in the readability graph. </li>
* <li> {@code M} reads another module that declares
* '{@code requires transitive static}' for at least one module that is
* not in the readability graph. </li>
* </ul> </li>
*
* </ul>
*

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013, 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2013, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -689,7 +689,6 @@ final class Resolver {
* </ol>
*/
private void checkExportSuppliers(Map<ResolvedModule, Set<ResolvedModule>> graph) {
for (Map.Entry<ResolvedModule, Set<ResolvedModule>> e : graph.entrySet()) {
ModuleDescriptor descriptor1 = e.getKey().descriptor();
String name1 = descriptor1.name();
@ -754,7 +753,6 @@ final class Resolver {
failTwoSuppliers(descriptor1, source, descriptor2, supplier);
}
}
}
}
@ -764,18 +762,21 @@ final class Resolver {
// uses S
for (String service : descriptor1.uses()) {
String pn = packageName(service);
if (!packageToExporter.containsKey(pn)) {
resolveFail("Module %s does not read a module that exports %s",
descriptor1.name(), pn);
if (!packageToExporter.containsKey(pn)
&& !requiresStaticMissingModule(descriptor1, reads)) {
resolveFail("Module %s uses %s but does not read a module that exports %s to %s",
descriptor1.name(), service, pn, descriptor1.name());
}
}
// provides S
for (ModuleDescriptor.Provides provides : descriptor1.provides()) {
String pn = packageName(provides.service());
if (!packageToExporter.containsKey(pn)) {
resolveFail("Module %s does not read a module that exports %s",
descriptor1.name(), pn);
if (!packageToExporter.containsKey(pn)
&& !requiresStaticMissingModule(descriptor1, reads)) {
resolveFail("Module %s provides %s but does not read a module that exports %s to %s",
descriptor1.name(), provides.service(), pn, descriptor1.name());
}
}
@ -785,6 +786,34 @@ final class Resolver {
}
/**
* Returns true if a module 'requires static' a module that is not in the
* readability graph, or reads a module that 'requires static transitive'
* a module that is not in the readability graph.
*/
private boolean requiresStaticMissingModule(ModuleDescriptor descriptor,
Set<ResolvedModule> reads) {
Set<String> moduleNames = reads.stream()
.map(ResolvedModule::name)
.collect(Collectors.toSet());
for (ModuleDescriptor.Requires r : descriptor.requires()) {
if (r.modifiers().contains(Modifier.STATIC)
&& !moduleNames.contains(r.name())) {
return true;
}
}
for (ResolvedModule rm : reads) {
for (ModuleDescriptor.Requires r : rm.descriptor().requires()) {
if (r.modifiers().contains(Modifier.STATIC)
&& r.modifiers().contains(Modifier.TRANSITIVE)
&& !moduleNames.contains(r.name())) {
return true;
}
}
}
return false;
}
/**
* Fail because a module in the configuration exports the same package to
* a module that reads both. This includes the case where a module M

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2013, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -191,6 +191,10 @@
* However, if M is recursively enumerated at step 1 then all modules that are
* enumerated and `requires static M` will read M. </p>
*
* <p> The {@linkplain java.lang.module.Configuration##optional-services Optional
* Services} section of {@link java.lang.module.Configuration} shows how resolution
* can be resilient when a service comes from a module that is optional at run time.
*
* <h3> Completeness </h3>
*
* <p> Resolution may be partial at compile-time in that the complete transitive