Skip to content

Commit 32c54f3

Browse files
dougxcvjovanov
andcommitted
Add runtime-configurable assertion support
Co-authored-by: Vojin Jovanovic <vojin.jovanovic@oracle.com>
1 parent 0d28b6f commit 32c54f3

28 files changed

Lines changed: 790 additions & 325 deletions

File tree

docs/reference-manual/native-image/BuildOutput.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,8 +159,13 @@ This can also improve latency in some cases.
159159
Use the `-R:MaxHeapSize` option when building with Native Image to preconfigure the maximum heap size.
160160

161161
#### <a name="glossary-image-assertions"></a>Assertions in the Generated Image
162-
This shows whether Java assertions and system assertions are enabled in the generated image.
163-
Enabling them can help identifying and debugging problems in the Java code built into the image.
162+
This shows the hosted Java assertion defaults configured in the generated image.
163+
Build-time-initialized classes always use these defaults.
164+
When you build with `-H:-StrictRuntimeJavaOptions`, runtime-initialized image classes also use the build-time options
165+
and runtime-loaded classes have assertions disabled unconditionally.
166+
When you build with `-H:+StrictRuntimeJavaOptions`, runtime-initialized image classes and
167+
runtime-loaded classes have their assertion status set by runtime `-ea`, `-da`, `-esa`, and `-dsa` options.
168+
Enabling assertions can help identify and debug problems in the Java code built into the image.
164169

165170
#### <a name="glossary-experimental-options"></a>Experimental Options
166171
A list of all active experimental options, including their origin and possible API option alternatives if available.

substratevm/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
This changelog summarizes major changes to GraalVM Native Image.
44

55
## GraalVM 25.4 (Internal Version 25.4.4)
6+
* (GR-75824) When native executables are built with `-H:+StrictRuntimeJavaOptions`, runtime assertion options (for example, `-ea`, `-da`, `-esa`, and `-dsa`) are supported and configure the assertion status of runtime-loaded classes and runtime-initialized image classes. They do not affect build-time-initialized classes whose assertion status is *only* configured by `native-image -ea ...`.
67
* (GR-71854) On Linux AMD64, Native Image now records the selected x86-64 ISA level in `.note.gnu.property` for `-march` values requiring x86-64-v2 or newer, so tools such as `readelf` report the requirement correctly.
78
* (GR-78784) Default to optional identity hash code fields with SerialGC. Few objects need one, and this optimization adds them during garbage collection. It can be disabled with `-H:-OptionalIdentityHashCodes`.
89
* (GR-78804) Added outlining for StringBuilder/StringBuffer append sequences and invokedynamic string concatenations. This reduces the binary size of native executables.

substratevm/docs/crema-onboarding.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -182,8 +182,7 @@ invocation (`CremaSupportImpl.java`: `invokeBasic`, `linkToVirtual`, `linkToStat
182182
## Current boundaries
183183

184184
The current implementation still has several important boundaries: no parallel class loading, no JNI support for
185-
runtime-loaded classes, no `condy`, fixed assertion status, and limited reflection for runtime-loaded
186-
classes.
185+
runtime-loaded classes, no `condy`, and limited reflection for runtime-loaded classes.
187186

188187
The code also shows a few areas that are still under construction:
189188

substratevm/docs/runtime-class-loading.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,5 +63,15 @@ This matches HotSpot-style resource lookup behavior where each directory resourc
6363

6464
## Current Limitations
6565
* Parallel class loading is explicitly disabled and not supported.
66-
* The assertion status of classes is fixed at image build time.
66+
* Assertion status depends on whether you build with `-H:-StrictRuntimeJavaOptions` or
67+
`-H:+StrictRuntimeJavaOptions`:
68+
* Build-time-initialized classes use the assertion status configured when the image is built.
69+
Runtime `-ea`, `-da`, `-esa`, and `-dsa` options never change their status.
70+
* Runtime-initialized image classes use the build-time `-ea`, `-da`, `-esa`, and `-dsa`
71+
options when you build with `-H:-StrictRuntimeJavaOptions`.
72+
When you build with `-H:+StrictRuntimeJavaOptions`, runtime `-ea`, `-da`, `-esa`, and `-dsa`
73+
options control their status instead.
74+
* Runtime-loaded classes use runtime assertion options.
75+
The runtime assertion options are supported only when the image was built with
76+
`-H:+StrictRuntimeJavaOptions`.
6777
* Methods or static fields removed by analysis from a class included in the image have no fallback and cause an error if run-time-loaded code uses them.

substratevm/mx.substratevm/mx_substratevm.py

Lines changed: 102 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,7 @@ def __getattr__(self, name):
264264
'standalone_pointsto_unittests',
265265
'native_unittests',
266266
'generic_field_type',
267+
'runtime_assertions',
267268
'all_native_unittests',
268269
'java_desktop_integration',
269270
'build',
@@ -565,6 +566,11 @@ def svm_gate_body(args, tasks):
565566
if t:
566567
generic_field_type_test_task(args.extra_image_builder_arguments)
567568

569+
runtime_assertions_tags = [GraalTags.runtime_assertions, GraalTags.native_unittests, GraalTags.all_native_unittests]
570+
with Task('runtime assertions', tasks, tags=runtime_assertions_tags) as t:
571+
if t:
572+
runtime_assertions_test_task(args.extra_image_builder_arguments)
573+
568574
with Task('runtime classpath resource lookup', tasks, tags=[GraalTags.native_unittests]) as t:
569575
if t:
570576
with native_image_context(IMAGE_ASSERTION_FLAGS):
@@ -923,6 +929,93 @@ def generic_field_type_test_task(extra_build_args=None):
923929
mx.abort('Unexpected generic field types: ' + str(field_output) + ' != ' + str(expected_field_output))
924930

925931

932+
def runtime_assertions_test_task(extra_image_args=None):
933+
test_dir = join(suite.dir, 'src', 'native-image-tests', 'runtime-assertions')
934+
output_dir = join(svmbuild_dir(), 'runtime-assertions-test')
935+
if exists(output_dir):
936+
mx.rmtree(output_dir)
937+
mx_util.ensure_dir_exists(output_dir)
938+
939+
sources = [
940+
join(test_dir, 'RuntimeAssertions.java'),
941+
join(test_dir, 'RuntimeLoadedAssertions.java'),
942+
]
943+
mx.run([mx.get_jdk().javac, '-d', output_dir] + sources)
944+
945+
test_class = 'runtimeassertions.RuntimeAssertions'
946+
with native_image_context(IMAGE_ASSERTION_FLAGS) as native_image:
947+
def build_assertion_image(name, assertion_args, image_options, main_class=test_class, classpath=output_dir, output_root=output_dir):
948+
build_args = []
949+
if classpath is not None:
950+
build_args += ['-cp', classpath]
951+
build_args += assertion_args + [
952+
'-o', join(output_root, name),
953+
] + svm_experimental_options(image_options)
954+
if main_class is not None:
955+
build_args.append(main_class)
956+
if extra_image_args is not None:
957+
build_args += extra_image_args
958+
return native_image(build_args)
959+
960+
assertion_image = build_assertion_image('runtime-assertions', [
961+
'-ea',
962+
'-esa',
963+
], [
964+
'-H:+StrictRuntimeJavaOptions',
965+
])
966+
967+
# Each execution starts with fresh runtime assertion directives.
968+
test_cases = [
969+
[],
970+
['-ea'],
971+
['-ea', '-da'],
972+
['-ea:runtimeassertions.ClassEnabled'],
973+
['-ea:runtimeassertions...', '-da:runtimeassertions.ClassDisabled'],
974+
['-ea', '-da:runtimeassertions...', '-ea:runtimeassertions.ClassEnabled'],
975+
['-esa'],
976+
['-esa', '-dsa'],
977+
['-enableassertions:runtimeassertions.ClassEnabled', '-enablesystemassertions'],
978+
['-enableassertions', '-disableassertions', '-enablesystemassertions', '-disablesystemassertions'],
979+
]
980+
for runtime_args in test_cases:
981+
scenario = " ".join(runtime_args)
982+
mx.run([assertion_image] + runtime_args + ['--', scenario])
983+
984+
legacy_assertion_image = build_assertion_image('runtime-assertions-legacy', [
985+
'-ea',
986+
], [
987+
'-H:-StrictRuntimeJavaOptions',
988+
])
989+
mx.run([legacy_assertion_image, 'legacy-build-time-status'])
990+
991+
runtime_loaded_excluded_image = build_assertion_image('runtime-assertions-runtime-loaded-excluded', [
992+
'-ea',
993+
], [
994+
'-H:IncludeResources=runtimeassertions/RuntimeLoadedAssertions.class',
995+
'-H:+StrictRuntimeJavaOptions',
996+
'-H:+RuntimeClassLoading',
997+
])
998+
# Runtime-loaded classes must remain controlled by runtime directives even when image assertion code is excluded.
999+
runtime_loaded_test_cases = [
1000+
([], False),
1001+
(['-ea'], True),
1002+
(['-ea:runtimeassertions...'], True),
1003+
(['-ea', '-da:runtimeassertions...'], False),
1004+
(['-ea:runtimeassertions...', '-da:runtimeassertions.RuntimeLoadedAssertions'], False),
1005+
]
1006+
for runtime_args, expected in runtime_loaded_test_cases:
1007+
scenario = 'runtime-loaded:' + str(expected).lower()
1008+
mx.run([runtime_loaded_excluded_image] + runtime_args + ['--', scenario])
1009+
1010+
default_assertion_image = build_assertion_image('runtime-assertions-default', [], ['-H:-StrictRuntimeJavaOptions'])
1011+
mx.run([default_assertion_image, 'code-excluded'])
1012+
1013+
1014+
@mx.command(suite.name, 'runtime-assertionstest', 'Builds and tests runtime assertion options in a native image.')
1015+
def runtime_assertionstest(args):
1016+
runtime_assertions_test_task(args)
1017+
1018+
9261019
def runtime_classpath_resource_test_task(extra_build_args=None):
9271020
svm_tests_jar = mx.distribution('substratevm:SVM_TESTS').path
9281021
build_args = svm_experimental_options(['-H:+ClassForNameRespectsClassLoader']) + [
@@ -2861,17 +2954,16 @@ def _hosted_boolean_option_defaults(native_image):
28612954
boolean_option_defaults = _hosted_boolean_option_defaults(native_image)
28622955
module_path_sep = ';' if mx.is_windows() else ':'
28632956
runtime_class_loading = _bool_option_value('RuntimeClassLoading', boolean_option_defaults)
2957+
strict_runtime_java_options = _bool_option_value('StrictRuntimeJavaOptions', boolean_option_defaults)
28642958

2865-
def moduletest_args(modules, extra_args=None):
2866-
return [
2867-
'-ea',
2868-
] + (extra_args or []) + [
2959+
def moduletest_args(modules, *, on_jvm, extra_args=None):
2960+
return (['-ea'] if on_jvm or not strict_runtime_java_options else []) + (extra_args or []) + [
28692961
'--add-exports=moduletests.hello.lib/hello.privateLib=moduletests.hello.app',
28702962
'--add-opens=moduletests.hello.lib/hello.privateLib2=moduletests.hello.app',
28712963
'-p', module_path_sep.join(modules), '-m', 'moduletests.hello.app'
28722964
]
28732965

2874-
moduletest_run_args = moduletest_args(module_path)
2966+
moduletest_run_args = moduletest_args(module_path, on_jvm=True)
28752967
mx.log('Running module-tests on JVM:')
28762968
build_dir = join(svmbuild_dir(), 'hellomodule')
28772969
mx.run([
@@ -2886,9 +2978,10 @@ def moduletest_args(modules, extra_args=None):
28862978
mx.run([
28872979
# On Windows, java is always an .exe, never a .cmd symlink
28882980
join(_vm_home(None), 'bin', mx.exe_suffix('java')),
2889-
] + moduletest_args(runtime_module_path, runtime_module_path_jvm_args))
2981+
] + moduletest_args(runtime_module_path, on_jvm=True, extra_args=runtime_module_path_jvm_args))
28902982

28912983
# Build module into native image
2984+
moduletest_run_args = moduletest_args(module_path, on_jvm=False)
28922985
mx.log('Building image from java modules: ' + str(module_path))
28932986
moduletest_build_args = list(moduletest_run_args)
28942987
if runtime_class_loading:
@@ -2899,10 +2992,11 @@ def moduletest_args(modules, extra_args=None):
28992992
['--verbose'] + svm_experimental_options(['-H:Path=' + build_dir]) + args + moduletest_build_args
29002993
)
29012994
mx.log('Running image ' + built_image + ' built from module without runtime module path:')
2902-
mx.run([built_image])
2995+
runtime_ea = ["-ea"] if "-ea" not in moduletest_build_args else []
2996+
mx.run([built_image] + runtime_ea)
29032997
if runtime_class_loading:
29042998
mx.log('Running image ' + built_image + ' built from module with runtime module path:')
2905-
runtime_module_path_args = [built_image, '-Dsvm.test.expectRuntimeModulePathFallback=true']
2999+
runtime_module_path_args = [built_image] + runtime_ea + ['-Dsvm.test.expectRuntimeModulePathFallback=true']
29063000
runtime_module_path_args.append('-Dsvm.test.expectRuntimeDefinedModuleLayer=true')
29073001
runtime_module_path_args.append('-Djava.home=' + _vm_home(None))
29083002
runtime_module_path_args.append('--module-path=' + module_path_sep.join(runtime_module_path))

substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GCImpl.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
import org.graalvm.word.impl.Word;
4545

4646
import com.oracle.svm.core.Isolates;
47-
import com.oracle.svm.core.RuntimeAssertionsSupport;
47+
import com.oracle.svm.core.AssertionsSupport;
4848
import com.oracle.svm.guest.staging.SubstrateGCOptions;
4949
import com.oracle.svm.core.SubstrateOptions;
5050
import com.oracle.svm.core.c.NonmovableArray;
@@ -530,7 +530,7 @@ private static void checkSanityAfterCollection() {
530530

531531
@Fold
532532
static boolean runtimeAssertions() {
533-
return RuntimeAssertionsSupport.singleton().desiredAssertionStatus(GCImpl.class);
533+
return AssertionsSupport.singleton().desiredAssertionStatus(GCImpl.class);
534534
}
535535

536536
@Fold

0 commit comments

Comments
 (0)