Skip to content

Commit f16fd59

Browse files
committed
More user-friendly reporting of internal errors.
In case of internal errors, the build process generates an error report and no longer shows daunting stack traces. Instead, it fails with a clear message that tells users how to proceed: inspect the error report and, if unable to resolve the problem, file an issue with the error report. Inspired by HotSpot, the default filename for error reports is `./svm_err_<timestamp>_pid<pid>.md`.
1 parent e875de2 commit f16fd59

File tree

10 files changed

+279
-43
lines changed

10 files changed

+279
-43
lines changed

substratevm/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ This changelog summarizes major changes to GraalVM Native Image.
99
* (GR-41674) Class instanceOf and isAssignableFrom checks do need to make the checked type reachable.
1010
* (GR-41100) Add support for `-XX:HeapDumpPath` to control where heap dumps are created.
1111
* (GR-42148) Adjust build output to report types (primitives, classes, interfaces, and arrays) instead of classes and revise the output schema of `-H:BuildOutputJSONFile`.
12+
* (GR-41912) The builder now generated reports for internal errors, which users can share when creating issues. By default, error reports follow the `svm_err_<timestamp>_pid<pid>.md` pattern and are created in the working directory. Use `-H:ErrorFile` to adjust the path or filename.
1213

1314
## Version 22.3.0
1415
* (GR-35721) Remove old build output style and the `-H:±BuildOutputUseNewStyle` option.

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/BuildArtifacts.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ enum ArtifactType {
3838
HEADER,
3939
IMPORT_LIB,
4040
DEBUG_INFO,
41+
JSON,
42+
MARKDOWN,
43+
TXT,
4144
}
4245

4346
static BuildArtifacts singleton() {

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/VM.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2019, 2019, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2019, 2022, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -33,9 +33,11 @@
3333
public final class VM {
3434

3535
public final String version;
36+
public final String supportURL;
3637

3738
@Platforms(Platform.HOSTED_ONLY.class)
38-
public VM(String config) {
39+
public VM(String config, String supportURL) {
40+
this.supportURL = supportURL;
3941
String versionStr = System.getProperty("org.graalvm.version");
4042
VMError.guarantee(versionStr != null);
4143
versionStr = "GraalVM " + versionStr;

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGeneratorRunner.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,7 @@ private int buildImage(ImageClassLoader classLoader) {
283283
ForkJoinPool compilationExecutor = null;
284284

285285
ProgressReporter reporter = new ProgressReporter(parsedHostedOptions);
286+
Throwable vmError = null;
286287
boolean wasSuccessfulBuild = false;
287288
try (StopTimer ignored = totalTimer.start()) {
288289
Timer classlistTimer = timerCollection.get(TimerCollection.Registry.CLASSLIST);
@@ -455,12 +456,10 @@ private int buildImage(ImageClassLoader classLoader) {
455456
}
456457
return 1;
457458
} catch (Throwable e) {
458-
NativeImageGeneratorRunner.reportFatalError(e);
459+
vmError = e;
459460
return 1;
460461
} finally {
461-
if (imageName != null && generator != null) {
462-
reporter.printEpilog(imageName, generator, wasSuccessfulBuild, parsedHostedOptions);
463-
}
462+
reporter.printEpilog(imageName, generator, classLoader, vmError, parsedHostedOptions);
464463
NativeImageGenerator.clearSystemPropertiesForImage();
465464
ImageSingletonsSupportImpl.HostedManagement.clear();
466465
}

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageOptions.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,19 +27,24 @@
2727
import static org.graalvm.compiler.options.OptionType.Debug;
2828
import static org.graalvm.compiler.options.OptionType.User;
2929

30+
import java.nio.file.Path;
3031
import java.nio.file.Paths;
32+
import java.text.SimpleDateFormat;
3133
import java.util.Arrays;
34+
import java.util.Date;
3235

3336
import org.graalvm.collections.EconomicMap;
3437
import org.graalvm.compiler.options.Option;
3538
import org.graalvm.compiler.options.OptionKey;
3639
import org.graalvm.compiler.options.OptionValues;
40+
import org.graalvm.compiler.serviceprovider.GraalServices;
3741

3842
import com.oracle.graal.pointsto.reports.ReportUtils;
3943
import com.oracle.graal.pointsto.util.CompletionExecutor;
4044
import com.oracle.svm.core.SubstrateOptions;
4145
import com.oracle.svm.core.option.APIOption;
4246
import com.oracle.svm.core.option.HostedOptionKey;
47+
import com.oracle.svm.core.option.HostedOptionValues;
4348
import com.oracle.svm.core.option.LocatableMultiOptionValue;
4449
import com.oracle.svm.core.util.UserError;
4550
import com.oracle.svm.hosted.classinitialization.ClassInitializationOptions;
@@ -180,6 +185,25 @@ public static CStandards getCStandard() {
180185
@Option(help = "Print unsafe operation offset warnings.)")//
181186
public static final HostedOptionKey<Boolean> UnsafeOffsetWarningsAreFatal = new HostedOptionKey<>(false);
182187

188+
private static final String DEFAULT_ERROR_FILE_NAME = "svm_err_%t_pid%p.md";
189+
190+
public static final Path getErrorFilePath() {
191+
String errorFile = NativeImageOptions.ErrorFile.getValue();
192+
if (errorFile.isEmpty()) {
193+
return NativeImageGenerator.generatedFiles(HostedOptionValues.singleton()).resolve(expandErrorFile(DEFAULT_ERROR_FILE_NAME));
194+
} else {
195+
return Paths.get(expandErrorFile(errorFile));
196+
}
197+
}
198+
199+
private static String expandErrorFile(String errorFile) {
200+
String timestamp = new SimpleDateFormat("yyyyMMdd'T'HHmmss.SSS").format(new Date(GraalServices.getGlobalTimeStamp()));
201+
return errorFile.replaceAll("%p", GraalServices.getExecutionID()).replaceAll("%t", timestamp);
202+
}
203+
204+
@Option(help = "If an error occurs, save a build error report to this file [default: ./" + DEFAULT_ERROR_FILE_NAME + "] (%p replaced with pid, %t with timestamp).)")//
205+
public static final HostedOptionKey<String> ErrorFile = new HostedOptionKey<>("");
206+
183207
@Option(help = "Show exception stack traces for exceptions during image building.)")//
184208
public static final HostedOptionKey<Boolean> ReportExceptionStackTraces = new HostedOptionKey<>(areAssertionsEnabled());
185209

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporter.java

Lines changed: 60 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -74,14 +74,14 @@
7474
import com.oracle.svm.core.SubstrateOptions;
7575
import com.oracle.svm.core.VM;
7676
import com.oracle.svm.core.code.CodeInfoTable;
77+
import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature;
7778
import com.oracle.svm.core.feature.InternalFeature;
7879
import com.oracle.svm.core.heap.Heap;
7980
import com.oracle.svm.core.jdk.Resources;
8081
import com.oracle.svm.core.jdk.resources.ResourceStorageEntry;
8182
import com.oracle.svm.core.meta.SubstrateObjectConstant;
8283
import com.oracle.svm.core.option.HostedOptionValues;
8384
import com.oracle.svm.core.reflect.ReflectionMetadataDecoder;
84-
import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature;
8585
import com.oracle.svm.core.util.VMError;
8686
import com.oracle.svm.hosted.ProgressReporterJsonHelper.AnalysisResults;
8787
import com.oracle.svm.hosted.ProgressReporterJsonHelper.GeneralInfo;
@@ -94,6 +94,7 @@
9494
import com.oracle.svm.hosted.image.NativeImageHeap.ObjectInfo;
9595
import com.oracle.svm.hosted.meta.HostedMetaAccess;
9696
import com.oracle.svm.hosted.reflect.ReflectionHostedSupport;
97+
import com.oracle.svm.hosted.util.VMErrorReporter;
9798
import com.oracle.svm.util.ImageBuildStatistics;
9899
import com.oracle.svm.util.ReflectionUtil;
99100

@@ -612,18 +613,29 @@ private void printBreakdowns() {
612613
.a(String.format("%9s for %s more object types", Utils.bytesToHuman(totalHeapBytes - printedHeapBytes), numHeapItems - printedHeapItems)).flushln();
613614
}
614615

615-
public void printEpilog(String imageName, NativeImageGenerator generator, boolean wasSuccessfulBuild, OptionValues parsedHostedOptions) {
616+
public void printEpilog(String imageName, NativeImageGenerator generator, ImageClassLoader classLoader, Throwable error, OptionValues parsedHostedOptions) {
617+
executor.shutdown();
618+
createAdditionalArtifacts(imageName, generator, classLoader, error, parsedHostedOptions);
619+
620+
if (imageName == null || generator == null) {
621+
printErrorMessage(error);
622+
return;
623+
}
624+
616625
l().printLineSeparator();
617626
printResourceStatistics();
618627

619628
double totalSeconds = Utils.millisToSeconds(getTimer(TimerCollection.Registry.TOTAL).getTotalTime());
620629
recordJsonMetric(ResourceUsageKey.TOTAL_SECS, totalSeconds);
621630

631+
if (jsonHelper != null) {
632+
BuildArtifacts.singleton().add(ArtifactType.JSON, jsonHelper.printToFile());
633+
}
622634
Map<ArtifactType, List<Path>> artifacts = generator.getBuildArtifacts();
623635
if (!artifacts.isEmpty()) {
624-
l().printLineSeparator();
625-
printArtifacts(imageName, generator, parsedHostedOptions, artifacts, wasSuccessfulBuild);
636+
BuildArtifacts.singleton().add(ArtifactType.TXT, reportBuildArtifacts(NativeImageGenerator.generatedFiles(HostedOptionValues.singleton()), imageName, artifacts));
626637
}
638+
printArtifacts(artifacts);
627639

628640
l().printHeadlineSeparator();
629641

@@ -633,12 +645,51 @@ public void printEpilog(String imageName, NativeImageGenerator generator, boolea
633645
} else {
634646
timeStats = String.format("%dm %ds", (int) totalSeconds / 60, (int) totalSeconds % 60);
635647
}
636-
l().a(wasSuccessfulBuild ? "Finished" : "Failed").a(" generating '").bold().a(imageName).reset().a("' ")
637-
.a(wasSuccessfulBuild ? "in" : "after").a(" ").a(timeStats).a(".").println();
638-
executor.shutdown();
648+
l().a(error == null ? "Finished" : "Failed").a(" generating '").bold().a(imageName).reset().a("' ")
649+
.a(error == null ? "in" : "after").a(" ").a(timeStats).a(".").println();
650+
651+
printErrorMessage(error);
639652
}
640653

641-
private void printArtifacts(String imageName, NativeImageGenerator generator, OptionValues parsedHostedOptions, Map<ArtifactType, List<Path>> artifacts, boolean wasSuccessfulBuild) {
654+
private static void createAdditionalArtifacts(String imageName, NativeImageGenerator generator, ImageClassLoader classLoader, Throwable error, OptionValues parsedHostedOptions) {
655+
if (error != null) {
656+
Path errorReportPath = NativeImageOptions.getErrorFilePath();
657+
ReportUtils.report("GraalVM Native Image Error Report", errorReportPath, p -> VMErrorReporter.generateErrorReport(p, classLoader, error), false);
658+
BuildArtifacts.singleton().add(ArtifactType.MARKDOWN, errorReportPath);
659+
}
660+
if (generator.getBigbang() != null && ImageBuildStatistics.Options.CollectImageBuildStatistics.getValue(parsedHostedOptions)) {
661+
BuildArtifacts.singleton().add(ArtifactType.JSON, reportImageBuildStatistics(imageName, generator.getBigbang()));
662+
}
663+
}
664+
665+
private void printErrorMessage(Throwable error) {
666+
if (error == null) {
667+
return;
668+
}
669+
l().println();
670+
l().redBold().a("The build process encountered an unexpected error:").reset().println();
671+
if (NativeImageOptions.ReportExceptionStackTraces.getValue()) {
672+
l().dim().println();
673+
error.printStackTrace(builderIO.getOut());
674+
l().reset().println();
675+
} else {
676+
l().println();
677+
l().dim().a("> %s", error).reset().println();
678+
l().println();
679+
l().a("Please inspect the generated error report at:").println();
680+
l().link(NativeImageOptions.getErrorFilePath()).println();
681+
l().println();
682+
l().a("If you are unable to resolve this problem, please file an issue with the error report at:").println();
683+
var supportURL = ImageSingletons.lookup(VM.class).supportURL;
684+
l().link(supportURL, supportURL).println();
685+
}
686+
}
687+
688+
private void printArtifacts(Map<ArtifactType, List<Path>> artifacts) {
689+
if (artifacts.isEmpty()) {
690+
return;
691+
}
692+
l().printLineSeparator();
642693
l().yellowBold().a("Produced artifacts:").reset().println();
643694
// Use TreeMap to sort paths alphabetically.
644695
Map<Path, List<String>> pathToTypes = new TreeMap<>();
@@ -647,15 +698,6 @@ private void printArtifacts(String imageName, NativeImageGenerator generator, Op
647698
pathToTypes.computeIfAbsent(path, p -> new ArrayList<>()).add(artifactType.name().toLowerCase());
648699
}
649700
});
650-
if (jsonHelper != null && wasSuccessfulBuild) {
651-
Path jsonMetric = jsonHelper.printToFile();
652-
pathToTypes.computeIfAbsent(jsonMetric, p -> new ArrayList<>()).add("json");
653-
}
654-
if (generator.getBigbang() != null && ImageBuildStatistics.Options.CollectImageBuildStatistics.getValue(parsedHostedOptions)) {
655-
Path buildStatisticsPath = reportImageBuildStatistics(imageName, generator.getBigbang());
656-
pathToTypes.computeIfAbsent(buildStatisticsPath, p -> new ArrayList<>()).add("raw");
657-
}
658-
pathToTypes.computeIfAbsent(reportBuildArtifacts(imageName, artifacts), p -> new ArrayList<>()).add("txt");
659701
pathToTypes.forEach((path, typeNames) -> {
660702
l().a(" ").link(path).dim().a(" (").a(String.join(", ", typeNames)).a(")").reset().println();
661703
});
@@ -674,9 +716,7 @@ private static Path reportImageBuildStatistics(String imageName, BigBang bb) {
674716
}
675717
}
676718

677-
private static Path reportBuildArtifacts(String imageName, Map<ArtifactType, List<Path>> buildArtifacts) {
678-
Path buildDir = NativeImageGenerator.generatedFiles(HostedOptionValues.singleton());
679-
719+
private static Path reportBuildArtifacts(Path buildDir, String imageName, Map<ArtifactType, List<Path>> buildArtifacts) {
680720
Consumer<PrintWriter> writerConsumer = writer -> buildArtifacts.forEach((artifactType, paths) -> {
681721
writer.println("[" + artifactType + "]");
682722
if (artifactType == BuildArtifacts.ArtifactType.JDK_LIB_SHIM) {

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/VMFeature.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ public void afterRegistration(AfterRegistrationAccess access) {
5959

6060
protected VM createVMSingletonValue() {
6161
String config = System.getProperty("org.graalvm.config", "CE");
62-
return new VM(config);
62+
return new VM(config, "https://graalvm.org/native-image/bug-report/");
6363
}
6464

6565
@Override

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageDebugInfoFeature.java

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,8 @@
2424
*/
2525
package com.oracle.svm.hosted.image;
2626

27-
import java.lang.management.ManagementFactory;
28-
import java.nio.file.Path;
2927
import java.util.List;
3028
import java.util.function.Function;
31-
import java.util.stream.Collectors;
3229

3330
import org.graalvm.compiler.debug.DebugContext;
3431
import org.graalvm.compiler.printer.GraalDebugHandlersFactory;
@@ -50,6 +47,7 @@
5047
import com.oracle.svm.hosted.FeatureImpl;
5148
import com.oracle.svm.hosted.ProgressReporter;
5249
import com.oracle.svm.hosted.image.sources.SourceManager;
50+
import com.oracle.svm.hosted.util.DiagnosticUtils;
5351

5452
@AutomaticallyRegisteredFeature
5553
@SuppressWarnings("unused")
@@ -121,19 +119,10 @@ public boolean isLoadable() {
121119
};
122120

123121
var imageClassLoader = accessImpl.getImageClassLoader();
124-
125-
var classPath = imageClassLoader.classpath().stream().map(Path::toString).collect(Collectors.toList());
126-
objectFile.newUserDefinedSection(".debug.svm.imagebuild.classpath", makeSectionImpl.apply(classPath));
127-
var modulePath = imageClassLoader.modulepath().stream().map(Path::toString).collect(Collectors.toList());
128-
objectFile.newUserDefinedSection(".debug.svm.imagebuild.modulepath", makeSectionImpl.apply(modulePath));
129-
/* Get original arguments that got passed to the builder when it got started */
130-
var builderArguments = imageClassLoader.classLoaderSupport.getHostedOptionParser().getArguments();
131-
objectFile.newUserDefinedSection(".debug.svm.imagebuild.arguments", makeSectionImpl.apply(builderArguments));
132-
/* System properties that got passed to the VM that runs the builder */
133-
var builderProperties = ManagementFactory.getRuntimeMXBean().getInputArguments().stream()
134-
.filter(arg -> arg.startsWith("-D"))
135-
.sorted().collect(Collectors.toList());
136-
objectFile.newUserDefinedSection(".debug.svm.imagebuild.java.properties", makeSectionImpl.apply(builderProperties));
122+
objectFile.newUserDefinedSection(".debug.svm.imagebuild.classpath", makeSectionImpl.apply(DiagnosticUtils.getClassPath(imageClassLoader)));
123+
objectFile.newUserDefinedSection(".debug.svm.imagebuild.modulepath", makeSectionImpl.apply(DiagnosticUtils.getModulePath(imageClassLoader)));
124+
objectFile.newUserDefinedSection(".debug.svm.imagebuild.arguments", makeSectionImpl.apply(DiagnosticUtils.getBuilderArguments(imageClassLoader)));
125+
objectFile.newUserDefinedSection(".debug.svm.imagebuild.java.properties", makeSectionImpl.apply(DiagnosticUtils.getBuilderProperties()));
137126
}
138127
}
139128
ProgressReporter.singleton().setDebugInfoTimer(timer);
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation. Oracle designates this
8+
* particular file as subject to the "Classpath" exception as provided
9+
* by Oracle in the LICENSE file that accompanied this code.
10+
*
11+
* This code is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
* version 2 for more details (a copy is included in the LICENSE file that
15+
* accompanied this code).
16+
*
17+
* You should have received a copy of the GNU General Public License version
18+
* 2 along with this work; if not, write to the Free Software Foundation,
19+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+
*
21+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+
* or visit www.oracle.com if you need additional information or have any
23+
* questions.
24+
*/
25+
package com.oracle.svm.hosted.util;
26+
27+
import java.lang.management.ManagementFactory;
28+
import java.nio.file.Path;
29+
import java.util.List;
30+
import java.util.stream.Collectors;
31+
32+
import com.oracle.svm.hosted.ImageClassLoader;
33+
34+
public class DiagnosticUtils {
35+
public static List<String> getClassPath(ImageClassLoader imageClassLoader) {
36+
return imageClassLoader.classpath().stream().map(Path::toString).collect(Collectors.toList());
37+
}
38+
39+
public static List<String> getModulePath(ImageClassLoader imageClassLoader) {
40+
return imageClassLoader.modulepath().stream().map(Path::toString).collect(Collectors.toList());
41+
}
42+
43+
/* Get original arguments that got passed to the builder when it got started. */
44+
public static List<String> getBuilderArguments(ImageClassLoader imageClassLoader) {
45+
return imageClassLoader.classLoaderSupport.getHostedOptionParser().getArguments();
46+
}
47+
48+
/* Get system properties that got passed to the VM that runs the builder. */
49+
public static List<String> getBuilderProperties() {
50+
return ManagementFactory.getRuntimeMXBean().getInputArguments().stream()
51+
.filter(arg -> arg.startsWith("-D"))
52+
.sorted().collect(Collectors.toList());
53+
}
54+
}

0 commit comments

Comments
 (0)