From aadd1cda98ec1dad01bfa6ca1802e5d1c68debb8 Mon Sep 17 00:00:00 2001 From: "cengfeng.lzy" Date: Wed, 11 Aug 2021 15:18:47 +0800 Subject: [PATCH] Parse pointsto options for standalone pointsto The standalone pointsto need to take the input options. The options and parsing should follow the same way as native image's hosted options. So we extract the commom part of SubstrateOptionsParser to a new project where both pointsto and svm can share. --- compiler/mx.compiler/suite.py | 2 +- substratevm/mx.substratevm/mx_substratevm.py | 1 + substratevm/mx.substratevm/suite.py | 47 +- .../graal/pointsto/util/AnalysisError.java | 12 + .../pointsto/util/PointsToOptionParser.java | 140 +++++ .../svm/common/option/CommonOptionParser.java | 522 ++++++++++++++++++ .../svm/common/option/CommonOptions.java | 39 ++ .../svm/common}/option/LocatableOption.java | 3 +- .../svm/common}/option/MultiOptionValue.java | 7 +- .../common/option/OptionParsingException.java | 35 ++ .../UnsupportedOptionClassException.java | 35 ++ .../com/oracle/svm/core/SubstrateOptions.java | 6 - .../com/oracle/svm/core/SubstrateUtil.java | 28 +- .../svm/core/option/HostedOptionKey.java | 2 + .../option/LocatableMultiOptionValue.java | 2 + .../svm/core/option/RuntimeOptionParser.java | 7 +- .../core/option/SubstrateOptionsParser.java | 500 ++--------------- .../com/oracle/svm/driver/NativeImage.java | 5 +- .../config/ConfigurationParserUtils.java | 2 +- .../svm/hosted/option/HostedOptionParser.java | 40 +- .../src/com/oracle/svm/util/StringUtil.java | 72 +++ 21 files changed, 970 insertions(+), 537 deletions(-) create mode 100644 substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/util/PointsToOptionParser.java create mode 100644 substratevm/src/com.oracle.svm.common/src/com/oracle/svm/common/option/CommonOptionParser.java create mode 100644 substratevm/src/com.oracle.svm.common/src/com/oracle/svm/common/option/CommonOptions.java rename substratevm/src/{com.oracle.svm.core/src/com/oracle/svm/core => com.oracle.svm.common/src/com/oracle/svm/common}/option/LocatableOption.java (96%) rename substratevm/src/{com.oracle.svm.core/src/com/oracle/svm/core => com.oracle.svm.common/src/com/oracle/svm/common}/option/MultiOptionValue.java (84%) create mode 100644 substratevm/src/com.oracle.svm.common/src/com/oracle/svm/common/option/OptionParsingException.java create mode 100644 substratevm/src/com.oracle.svm.common/src/com/oracle/svm/common/option/UnsupportedOptionClassException.java create mode 100644 substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/StringUtil.java diff --git a/compiler/mx.compiler/suite.py b/compiler/mx.compiler/suite.py index 44c8551c3b11..c528d0471775 100644 --- a/compiler/mx.compiler/suite.py +++ b/compiler/mx.compiler/suite.py @@ -2168,7 +2168,7 @@ "jdk.unsupported" # sun.misc.Unsafe ], "exports" : [ - "* to com.oracle.graal.graal_enterprise,org.graalvm.nativeimage.pointsto,org.graalvm.nativeimage.builder,org.graalvm.nativeimage.llvm,com.oracle.svm.svm_enterprise", + "* to com.oracle.graal.graal_enterprise,org.graalvm.nativeimage.pointsto,org.graalvm.nativeimage.builder,org.graalvm.nativeimage.llvm,com.oracle.svm.svm_enterprise,org.graalvm.nativeimage.base", "org.graalvm.compiler.core.common to jdk.internal.vm.compiler.management,org.graalvm.nativeimage.agent.tracing", "org.graalvm.compiler.debug to jdk.internal.vm.compiler.management,org.graalvm.nativeimage.objectfile", "org.graalvm.compiler.hotspot to jdk.internal.vm.compiler.management", diff --git a/substratevm/mx.substratevm/mx_substratevm.py b/substratevm/mx.substratevm/mx_substratevm.py index 2d468388df9e..061b9a99ad02 100644 --- a/substratevm/mx.substratevm/mx_substratevm.py +++ b/substratevm/mx.substratevm/mx_substratevm.py @@ -769,6 +769,7 @@ def native_image_context_run(func, func_args=None, config=None, build_if_missing 'substratevm:SVM', 'substratevm:OBJECTFILE', 'substratevm:POINTSTO', + 'substratevm:NATIVE_IMAGE_BASE', ], support_distributions=['substratevm:SVM_GRAALVM_SUPPORT'], stability="earlyadopter", diff --git a/substratevm/mx.substratevm/suite.py b/substratevm/mx.substratevm/suite.py index c1b7e79a15bf..f8b4f7afa75e 100644 --- a/substratevm/mx.substratevm/suite.py +++ b/substratevm/mx.substratevm/suite.py @@ -147,6 +147,19 @@ "checkstyle": "com.oracle.svm.core", "workingSets": "SVM", }, + "com.oracle.svm.common": { + "subDir": "src", + "sourceDirs": ["src"], + "dependencies": [ + "com.oracle.svm.util" + ], + "javaCompliance": "8+", + "annotationProcessors": [ + "compiler:GRAAL_PROCESSOR", + ], + "checkstyle": "com.oracle.svm.core", + "workingSets": "SVM", + }, "com.oracle.svm.util.jdk11": { "subDir": "src", "sourceDirs": ["src"], @@ -174,7 +187,7 @@ "headers", ], "dependencies": [ - "com.oracle.svm.util", + "com.oracle.svm.common", ], "javaCompliance": "8+", "checkstyleVersion" : "8.36.1", @@ -387,7 +400,7 @@ "subDir": "src", "sourceDirs": ["src"], "dependencies": [ - "com.oracle.svm.util", + "com.oracle.svm.common", ], "checkstyle": "com.oracle.graal.pointsto", "javaCompliance": "8+", @@ -1106,6 +1119,7 @@ "OBJECTFILE", "POINTSTO", "compiler:GRAAL", + "NATIVE_IMAGE_BASE", ], "moduleInfo" : { "name" : "org.graalvm.nativeimage.builder", @@ -1119,7 +1133,7 @@ "com.oracle.svm.hosted to java.base", "com.oracle.svm.hosted.agent to java.instrument", "com.oracle.svm.truffle.api to org.graalvm.truffle", - "* to jdk.internal.vm.compiler,org.graalvm.nativeimage.driver,org.graalvm.nativeimage.configure,org.graalvm.nativeimage.librarysupport,org.graalvm.nativeimage.junitsupport,org.graalvm.nativeimage.llvm,org.graalvm.nativeimage.agent.jvmtibase,org.graalvm.nativeimage.agent.tracing,org.graalvm.nativeimage.agent.diagnostics,com.oracle.svm.svm_enterprise", + "* to org.graalvm.nativeimage.base,jdk.internal.vm.compiler,org.graalvm.nativeimage.driver,org.graalvm.nativeimage.configure,org.graalvm.nativeimage.librarysupport,org.graalvm.nativeimage.junitsupport,org.graalvm.nativeimage.llvm,org.graalvm.nativeimage.agent.jvmtibase,org.graalvm.nativeimage.agent.tracing,org.graalvm.nativeimage.agent.diagnostics,com.oracle.svm.svm_enterprise", ], "opens" : [ "com.oracle.svm.core to jdk.internal.vm.compiler", @@ -1254,6 +1268,9 @@ "static junit", "static hamcrest", ], + "exports" : [ + "* to org.graalvm.nativeimage.base,org.graalvm.nativeimage.pointsto,org.graalvm.nativeimage.builder", + ], }, }, @@ -1421,6 +1438,26 @@ }, }, + "NATIVE_IMAGE_BASE": { + "subDir": "src", + "description" : "Native Image base that can be shared by native image building and pointsto.", + "dependencies": [ + "com.oracle.svm.common", + "com.oracle.svm.util", + ], + "distDependencies": [ + "compiler:GRAAL", + ], + "exclude": [ + ], + "moduleInfo" : { + "name" : "org.graalvm.nativeimage.base", + "exports" : [ + "com.oracle.svm.util", + "com.oracle.svm.common.option", + ], + } + }, "POINTSTO": { "subDir": "src", @@ -1431,13 +1468,13 @@ ], "distDependencies": [ "compiler:GRAAL", + "NATIVE_IMAGE_BASE", ], "exclude": [ ], "moduleInfo" : { "name" : "org.graalvm.nativeimage.pointsto", "exports" : [ - "com.oracle.svm.util", "com.oracle.graal.pointsto", "com.oracle.graal.pointsto.api", "com.oracle.graal.pointsto.reports", @@ -1614,7 +1651,7 @@ "moduleInfo" : { "name" : "org.graalvm.nativeimage.llvm", "exports" : [ - "* to org.graalvm.nativeimage.builder", + "* to org.graalvm.nativeimage.builder,org.graalvm.nativeimage.base", ], }, "maven" : False, diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/util/AnalysisError.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/util/AnalysisError.java index b9f58533b894..8d59e33cd5a3 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/util/AnalysisError.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/util/AnalysisError.java @@ -125,6 +125,14 @@ private static String message(PointsToAnalysis bb, TypeFlow objectFlow, Bytec } } + public static class InterruptAnalysis extends AnalysisError { + private static final long serialVersionUID = 7126612141948542452L; + + InterruptAnalysis(String msg) { + super(msg); + } + } + public static TypeNotFoundError typeNotFound(ResolvedJavaType type) { throw new TypeNotFoundError(type); } @@ -166,4 +174,8 @@ public static void guarantee(boolean condition, String format, Object... args) { // Checkstyle: resume } } + + public static RuntimeException interruptAnalysis(String msg) { + throw new InterruptAnalysis(msg); + } } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/util/PointsToOptionParser.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/util/PointsToOptionParser.java new file mode 100644 index 000000000000..b2a93a85768d --- /dev/null +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/util/PointsToOptionParser.java @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2021, Alibaba Group Holding Limited. 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.graal.pointsto.util; + +import com.oracle.svm.common.option.CommonOptionParser; +import com.oracle.svm.common.option.CommonOptionParser.BooleanOptionFormat; +import com.oracle.svm.common.option.CommonOptionParser.OptionParseResult; +import com.oracle.svm.common.option.UnsupportedOptionClassException; +import org.graalvm.collections.EconomicMap; +import org.graalvm.compiler.options.OptionDescriptor; +import org.graalvm.compiler.options.OptionDescriptors; +import org.graalvm.compiler.options.OptionKey; +import org.graalvm.compiler.options.OptionValues; + +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.ServiceLoader; +import java.util.Set; +import java.util.function.Predicate; + +import static com.oracle.svm.common.option.CommonOptionParser.BooleanOptionFormat.PLUS_MINUS; + +public final class PointsToOptionParser { + + private static PointsToOptionParser instance = new PointsToOptionParser(); + + private OptionValues optionValues = null; + private EconomicMap, Object> analysisValues = OptionValues.newOptionMap(); + private EconomicMap allAnalysisOptions = EconomicMap.create(); + + public static PointsToOptionParser getInstance() { + return instance; + } + + @SuppressWarnings("unchecked") + private PointsToOptionParser() { + ClassLoader appClassLoader = PointsToOptionParser.class.getClassLoader(); + CommonOptionParser.collectOptions(ServiceLoader.load(OptionDescriptors.class, appClassLoader), descriptor -> { + String name = descriptor.getName(); + if (descriptor.getOptionKey() instanceof OptionKey) { + OptionDescriptor existing = allAnalysisOptions.put(name, descriptor); + if (existing != null) { + AnalysisError.shouldNotReachHere("Option name \"" + name + "\" has multiple definitions: " + existing.getLocation() + " and " + descriptor.getLocation()); + } + } + }); + } + + public void setOptionValue(OptionKey optionKey, Object value) { + if (optionValues == null) { + parse(new String[0]); + } + optionKey.update((EconomicMap, Object>) optionValues.getMap(), value); + } + + public OptionValues parse(String[] args) { + List remainingArgs = new ArrayList<>(); + Set errors = new HashSet<>(); + for (String arg : args) { + boolean isAnalysisOption = false; + isAnalysisOption |= parseOption(CommonOptionParser.HOSTED_OPTION_PREFIX, allAnalysisOptions, analysisValues, PLUS_MINUS, errors, arg, System.out); + if (!isAnalysisOption) { + remainingArgs.add(arg); + } + } + optionValues = new OptionValues(analysisValues); + if (!remainingArgs.isEmpty()) { + AnalysisError.interruptAnalysis(String.format("Unknown options: %s", Arrays.toString(remainingArgs.toArray(new String[0])))); + } + if (!errors.isEmpty()) { + StringBuilder errMsg = new StringBuilder("Option format error:\n"); + for (String err : errors) { + errMsg.append(err).append("\n"); + } + AnalysisError.interruptAnalysis(errMsg.toString()); + } + return optionValues; + } + + private static boolean parseOption(String optionPrefix, EconomicMap options, EconomicMap, Object> valuesMap, + BooleanOptionFormat booleanOptionFormat, Set errors, String arg, PrintStream out) { + if (!arg.startsWith(optionPrefix)) { + return false; + } + try { + Predicate> optionKeyPredicate = optionKey -> { + Class clazz = optionKey.getClass(); + // All classes from com.oracle.graal.pointsto.api.PointstoOptions are taken as non-hosted options. + if (clazz.getName().startsWith("com.oracle.graal.pointsto.api.PointstoOptions")) { + return false; + } + if (!clazz.equals(OptionKey.class) && OptionKey.class.isAssignableFrom(clazz)) { + return true; + } else { + return false; + } + }; + OptionParseResult optionParseResult = CommonOptionParser.parseOption(options, optionKeyPredicate, arg.substring(optionPrefix.length()), valuesMap, + optionPrefix, booleanOptionFormat); + if (optionParseResult.printFlags() || optionParseResult.printFlagsWithExtraHelp()) { + CommonOptionParser.printFlags(d -> optionParseResult.matchesFlags(d, () -> true), options, optionPrefix, out, optionParseResult.printFlagsWithExtraHelp()); + System.out.println("Abort analysis due to print flags are requested"); + System.exit(1); + } + if (!optionParseResult.isValid()) { + errors.add(optionParseResult.getError()); + } + } catch (UnsupportedOptionClassException e) { + AnalysisError.shouldNotReachHere(e); + } + return true; + } +} diff --git a/substratevm/src/com.oracle.svm.common/src/com/oracle/svm/common/option/CommonOptionParser.java b/substratevm/src/com.oracle.svm.common/src/com/oracle/svm/common/option/CommonOptionParser.java new file mode 100644 index 000000000000..823a67bee2c9 --- /dev/null +++ b/substratevm/src/com.oracle.svm.common/src/com/oracle/svm/common/option/CommonOptionParser.java @@ -0,0 +1,522 @@ +/* + * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2021, Alibaba Group Holding Limited. 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.common.option; + +// Checkstyle: allow reflection + +import com.oracle.svm.util.ClassUtil; +import com.oracle.svm.util.StringUtil; +import org.graalvm.collections.EconomicMap; +import org.graalvm.compiler.options.OptionDescriptor; +import org.graalvm.compiler.options.OptionDescriptors; +import org.graalvm.compiler.options.OptionKey; +import org.graalvm.compiler.options.OptionType; +import org.graalvm.compiler.options.OptionsParser; + +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.EnumSet; +import java.util.HashSet; +import java.util.List; +import java.util.ServiceLoader; +import java.util.Set; +import java.util.function.BooleanSupplier; +import java.util.function.Consumer; +import java.util.function.Predicate; + +public class CommonOptionParser { + public static final String HOSTED_OPTION_PREFIX = "-H:"; + public static final String RUNTIME_OPTION_PREFIX = "-R:"; + + public static final int PRINT_OPTION_INDENTATION = 2; + public static final int PRINT_OPTION_WIDTH = 45; + public static final int PRINT_OPTION_WRAP_WIDTH = 120; + + /** + * The result of {@link CommonOptionParser#parseOption}. + */ + public static final class OptionParseResult { + private final EnumSet printFlags; + private final Set optionNameFilter; + private final String error; + private final OptionKey optionKey; + private static final String EXTRA_HELP_OPTIONS_WILDCARD = "*"; + + OptionParseResult(EnumSet printFlags, String error, Set optionNameFilter, OptionKey optionKey) { + this.printFlags = printFlags; + this.error = error; + this.optionNameFilter = optionNameFilter; + this.optionKey = optionKey; + } + + private OptionParseResult(EnumSet printFlags, String error, OptionKey optionKey) { + this(printFlags, error, new HashSet<>(), optionKey); + } + + static OptionParseResult error(String message) { + return new OptionParseResult(EnumSet.noneOf(OptionType.class), message, null); + } + + static OptionParseResult correct(OptionKey optionKey) { + return new OptionParseResult(EnumSet.noneOf(OptionType.class), null, optionKey); + } + + static OptionParseResult printFlags(EnumSet selectedOptionTypes) { + return new OptionParseResult(selectedOptionTypes, null, null); + } + + static OptionParseResult printFlagsWithExtraHelp(Set optionNameFilter) { + Set optionNames = optionNameFilter; + if (optionNames.contains(EXTRA_HELP_OPTIONS_WILDCARD)) { + optionNames = new HashSet<>(); + optionNames.add(EXTRA_HELP_OPTIONS_WILDCARD); + } + return new OptionParseResult(EnumSet.noneOf(OptionType.class), null, optionNames, null); + } + + public boolean printFlags() { + return !printFlags.isEmpty(); + } + + public boolean printFlagsWithExtraHelp() { + return !optionNameFilter.isEmpty(); + } + + public boolean isValid() { + boolean result = optionKey != null; + assert result == (printFlags.isEmpty() && optionNameFilter.isEmpty() && error == null); + return result; + } + + public String getError() { + return error; + } + + public OptionKey getOptionKey() { + return optionKey; + } + + public boolean matchesFlags(OptionDescriptor d, BooleanSupplier svmOption) { + if (!printFlags.isEmpty()) { + boolean showAll = printFlags.equals(EnumSet.allOf(OptionType.class)); + return showAll || svmOption.getAsBoolean() && printFlags.contains(d.getOptionType()); + } + if (!optionNameFilter.isEmpty()) { + if (optionNameFilter.contains(EXTRA_HELP_OPTIONS_WILDCARD) && !d.getExtraHelp().isEmpty()) { + return true; + } + return optionNameFilter.contains(d.getName()); + } + return false; + } + } + + /** + * Constants denoting supported boolean option formats. + */ + public enum BooleanOptionFormat { + NAME_VALUE("="), + PLUS_MINUS("+/-"); + + BooleanOptionFormat(String help) { + this.help = help; + } + + private final String help; + + @Override + public String toString() { + return help; + } + } + + public static void collectOptions(ServiceLoader optionDescriptors, Consumer optionDescriptorConsumer) { + for (OptionDescriptors optionDescriptor : optionDescriptors) { + for (OptionDescriptor descriptor : optionDescriptor) { + optionDescriptorConsumer.accept(descriptor); + } + } + } + + public static OptionParseResult parseOption(EconomicMap options, Predicate> isHosted, String option, EconomicMap, Object> valuesMap, + String optionPrefix, BooleanOptionFormat booleanOptionFormat) throws UnsupportedOptionClassException { + if (option.length() == 0) { + return OptionParseResult.error("Option name must be specified"); + } + + String optionName; + Object value = null; + String valueString = null; + + char first = option.charAt(0); + int eqIndex = option.indexOf('='); + if (first == '+' || first == '-') { + if (eqIndex != -1) { + return OptionParseResult.error("Cannot mix +/- with = format: '" + optionPrefix + option + "'"); + } + optionName = option.substring(1); + if (booleanOptionFormat == BooleanOptionFormat.NAME_VALUE) { + return OptionParseResult.error("Option " + LocatableOption.from(optionName) + " must use = format, not +/- prefix"); + } + value = (first == '+'); + } else { + if (eqIndex == -1) { + optionName = option; + valueString = null; + } else { + optionName = option.substring(0, eqIndex); + valueString = option.substring(eqIndex + 1); + } + } + + LocatableOption current = LocatableOption.from(optionName); + OptionDescriptor desc = options.get(current.name); + if (desc == null && value != null) { + if (eqIndex != -1) { + optionName = option.substring(1, eqIndex); + current = LocatableOption.from(optionName); + desc = options.get(current.name); + } + } + + optionName = current.name; + + if (desc == null) { + List matches = new ArrayList<>(); + OptionsParser.collectFuzzyMatches(options.getValues(), optionName, matches); + StringBuilder msg = new StringBuilder("Could not find option ").append(current); + if (!matches.isEmpty()) { + msg.append(". Did you mean one of these:"); + for (OptionDescriptor match : matches) { + msg.append(' ').append(match.getName()); + } + } + msg.append(". Use ").append(optionPrefix).append(CommonOptions.PrintFlags.getName()).append("= to list all available options."); + return OptionParseResult.error(msg.toString()); + } + + OptionKey optionKey = desc.getOptionKey(); + boolean hostedOption = isHosted.test(optionKey); + Class optionValueType = getMultiOptionValueElementType(optionKey); + Class optionType = hostedOption && optionValueType != null ? optionValueType : desc.getOptionValueType(); + + if (value == null) { + if (optionType == Boolean.class && booleanOptionFormat == BooleanOptionFormat.PLUS_MINUS) { + return OptionParseResult.error("Boolean option " + current + " must have +/- prefix"); + } + if (valueString == null) { + return OptionParseResult.error("Missing value for option " + current); + } + try { + value = parseValue(optionType, current, valueString); + if (value instanceof OptionParseResult) { + return (OptionParseResult) value; + } + } catch (NumberFormatException ex) { + return OptionParseResult.error("Invalid value for option " + current + ": '" + valueString + "' is not a valid number"); + } + } else { + if (optionType != Boolean.class) { + return OptionParseResult.error("Non-boolean option " + current + " can not use +/- prefix. Use '" + current.name + "=' format"); + } + } + + optionKey.update(valuesMap, hostedOption ? LocatableOption.value(value, current.origin) : value); + + if (CommonOptions.PrintFlags.getName().equals(optionName)) { + String optionValue = (String) value; + EnumSet selectedOptionTypes; + if (optionValue.isEmpty()) { + selectedOptionTypes = EnumSet.allOf(OptionType.class); + } else { + selectedOptionTypes = EnumSet.noneOf(OptionType.class); + String enumString = null; + try { + String[] enumStrings = StringUtil.split(optionValue, ","); + + for (String string : enumStrings) { + enumString = string; + selectedOptionTypes.add(OptionType.valueOf(enumString)); + } + } catch (IllegalArgumentException e) { + StringBuilder sb = new StringBuilder(); + boolean firstValue = true; + for (OptionType ot : OptionType.values()) { + if (firstValue) { + firstValue = false; + } else { + sb.append(", "); + } + sb.append(ot.name()); + } + String possibleValues = sb.toString(); + return OptionParseResult.error("Invalid value for option " + current + ". '" + enumString + "' is not one of: " + possibleValues); + } + } + return OptionParseResult.printFlags(selectedOptionTypes); + } + + if (CommonOptions.PrintFlagsWithExtraHelp.getName().equals(optionName)) { + String optionValue = (String) value; + String[] optionNames = StringUtil.split(optionValue, ","); + HashSet selectedOptionNames = new HashSet<>(Arrays.asList(optionNames)); + return OptionParseResult.printFlagsWithExtraHelp(selectedOptionNames); + } + return OptionParseResult.correct(optionKey); + } + + @SuppressWarnings("unchecked") + static Object parseValue(Class optionType, LocatableOption option, String valueString) throws NumberFormatException, UnsupportedOptionClassException { + Object value; + if (optionType == Integer.class) { + long longValue = parseLong(valueString); + if ((int) longValue != longValue) { + return OptionParseResult.error("Wrong value for option " + option + ": '" + valueString + "' is not a valid number"); + } + value = (int) longValue; + } else if (optionType == Long.class) { + value = parseLong(valueString); + } else if (optionType == String.class) { + value = valueString; + } else if (optionType == Double.class) { + value = parseDouble(valueString); + } else if (optionType == Boolean.class) { + if (valueString.equals("true")) { + value = true; + } else if (valueString.equals("false")) { + value = false; + } else { + return OptionParseResult.error("Boolean option " + option + " must have value 'true' or 'false'"); + } + } else if (optionType.isEnum()) { + value = Enum.valueOf(optionType.asSubclass(Enum.class), valueString); + } else { + throw new UnsupportedOptionClassException(option + " uses unsupported option value class: " + ClassUtil.getUnqualifiedName(optionType)); + } + return value; + } + + private static Class getMultiOptionValueElementType(OptionKey optionKey) { + Object defaultValue = optionKey.getDefaultValue(); + if (defaultValue instanceof MultiOptionValue) { + return ((MultiOptionValue) defaultValue).getValueType(); + } + return null; + } + + public static long parseLong(String v) { + String valueString = v.trim().toLowerCase(); + long scale = 1; + if (valueString.endsWith("k")) { + scale = 1024L; + } else if (valueString.endsWith("m")) { + scale = 1024L * 1024L; + } else if (valueString.endsWith("g")) { + scale = 1024L * 1024L * 1024L; + } else if (valueString.endsWith("t")) { + scale = 1024L * 1024L * 1024L * 1024L; + } + + if (scale != 1) { + /* Remove trailing scale character. */ + valueString = valueString.substring(0, valueString.length() - 1); + } + + return Long.parseLong(valueString) * scale; + } + + /** + * Parses the provide string to a double number, avoiding the JDK dependencies (which pull in a + * lot of classes, including the regular expression library). Only simple numbers are supported, + * without fancy exponent styles. + */ + public static double parseDouble(String v) { + String valueString = v.trim(); + + int dotPos = valueString.indexOf('.'); + if (dotPos == -1) { + return parseLong(valueString); + } + + String beforeDot = valueString.substring(0, dotPos); + String afterDot = valueString.substring(dotPos + 1); + + double sign = 1; + if (beforeDot.startsWith("-")) { + sign = -1; + beforeDot = beforeDot.substring(1); + } else if (beforeDot.startsWith("+")) { + beforeDot = beforeDot.substring(1); + } + + if (beforeDot.startsWith("-") || beforeDot.startsWith("+") || afterDot.startsWith("-") || afterDot.startsWith("+") || + (beforeDot.length() == 0 && afterDot.length() == 0)) { + throw new NumberFormatException(v); + } + + double integral = 0; + if (beforeDot.length() > 0) { + integral = Long.parseLong(beforeDot); + } + + double fraction = 0; + if (afterDot.length() > 0) { + fraction = Long.parseLong(afterDot) * Math.pow(10, -afterDot.length()); + } + + return sign * (integral + fraction); + } + + private static String spaces(int length) { + return new String(new char[length]).replace('\0', ' '); + } + + private static String wrap(String s, int width) { + StringBuilder sb = new StringBuilder(s); + int cursor = 0; + while (cursor + width < sb.length()) { + int i = sb.lastIndexOf(" ", cursor + width); + if (i == -1 || i < cursor) { + i = sb.indexOf(" ", cursor + width); + } + if (i != -1) { + sb.replace(i, i + 1, System.lineSeparator()); + cursor = i; + } else { + break; + } + } + return sb.toString(); + } + + private static void printOption(PrintStream out, String option, String description, int wrap) { + printOption(out::println, option, description, PRINT_OPTION_INDENTATION, PRINT_OPTION_WIDTH, wrap); + } + + public static void printOption(Consumer println, String option, String description, int indentation, int optionWidth, int wrapWidth) { + String indent = spaces(indentation); + String desc = description != null ? description : ""; + desc = wrapWidth > 0 ? wrap(desc, wrapWidth) : desc; + String nl = System.lineSeparator(); + String[] descLines = StringUtil.split(desc, nl); + if (option.length() >= optionWidth && description != null) { + println.accept(indent + option + nl + indent + spaces(optionWidth) + descLines[0]); + } else { + println.accept(indent + option + spaces(optionWidth - option.length()) + descLines[0]); + } + for (int i = 1; i < descLines.length; i++) { + println.accept(indent + spaces(optionWidth) + descLines[i]); + } + } + + public static void printFlags(Predicate filter, EconomicMap options, String prefix, PrintStream out, boolean verbose) { + List sortedDescriptors = new ArrayList<>(); + for (OptionDescriptor option : options.getValues()) { + if (filter.test(option)) { + sortedDescriptors.add(option); + } + } + sortedDescriptors.sort(Comparator.comparing(OptionDescriptor::getName)); + + for (OptionDescriptor descriptor : sortedDescriptors) { + String helpMsg = verbose && !descriptor.getExtraHelp().isEmpty() ? "" : descriptor.getHelp(); + int helpLen = helpMsg.length(); + if (helpLen > 0 && helpMsg.charAt(helpLen - 1) != '.') { + helpMsg += '.'; + } + boolean stringifiedArrayValue = false; + Object defaultValue = descriptor.getOptionKey().getDefaultValue(); + if (defaultValue != null && defaultValue.getClass().isArray()) { + Object[] defaultValues = (Object[]) defaultValue; + if (defaultValues.length == 1) { + defaultValue = defaultValues[0]; + } else { + List stringList = new ArrayList<>(); + String optionPrefix = prefix + descriptor.getName() + "="; + for (Object rawValue : defaultValues) { + String value; + if (rawValue instanceof String) { + value = '"' + String.valueOf(rawValue) + '"'; + } else { + value = String.valueOf(rawValue); + } + stringList.add(optionPrefix + value); + } + if (helpLen != 0) { + helpMsg += ' '; + } + helpMsg += "Default: "; + if (stringList.isEmpty()) { + helpMsg += "None"; + } else { + helpMsg += String.join(" ", stringList); + } + stringifiedArrayValue = true; + } + } + String verboseHelp = ""; + if (verbose) { + verboseHelp = System.lineSeparator() + descriptor.getHelp() + System.lineSeparator() + String.join(System.lineSeparator(), descriptor.getExtraHelp()); + } else if (!descriptor.getExtraHelp().isEmpty()) { + verboseHelp = " [Extra help available]"; + } + int wrapWidth = verbose ? 0 : PRINT_OPTION_WRAP_WIDTH; + if (descriptor.getOptionValueType() == Boolean.class) { + Boolean val = (Boolean) defaultValue; + if (helpLen != 0) { + helpMsg += ' '; + } + if (val != null) { + if (val) { + helpMsg += "Default: + (enabled)."; + } else { + helpMsg += "Default: - (disabled)."; + } + } + printOption(out, prefix + "\u00b1" + descriptor.getName(), helpMsg + verboseHelp, wrapWidth); + } else { + if (defaultValue == null) { + if (helpLen != 0) { + helpMsg += ' '; + } + helpMsg += "Default: None"; + } + helpMsg += verboseHelp; + if (stringifiedArrayValue || defaultValue == null) { + printOption(out, prefix + descriptor.getName() + "=...", helpMsg, wrapWidth); + } else { + if (defaultValue instanceof String) { + defaultValue = '"' + String.valueOf(defaultValue) + '"'; + } + printOption(out, prefix + descriptor.getName() + "=" + defaultValue, helpMsg, wrapWidth); + } + } + } + } +} diff --git a/substratevm/src/com.oracle.svm.common/src/com/oracle/svm/common/option/CommonOptions.java b/substratevm/src/com.oracle.svm.common/src/com/oracle/svm/common/option/CommonOptions.java new file mode 100644 index 000000000000..6b2f9e88b5d6 --- /dev/null +++ b/substratevm/src/com.oracle.svm.common/src/com/oracle/svm/common/option/CommonOptions.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2021, Alibaba Group Holding Limited. 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.common.option; + +import org.graalvm.compiler.options.Option; +import org.graalvm.compiler.options.OptionKey; + +public class CommonOptions { + + @Option(help = "Show available options based on comma-separated option-types (allowed categories: User, Expert, Debug).")// + public static final OptionKey PrintFlags = new OptionKey<>(null); + + @Option(help = "Print extra help, if available, based on comma-separated option names. Pass * to show all options that contain extra help.")// + public static final OptionKey PrintFlagsWithExtraHelp = new OptionKey<>(null); +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/LocatableOption.java b/substratevm/src/com.oracle.svm.common/src/com/oracle/svm/common/option/LocatableOption.java similarity index 96% rename from substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/LocatableOption.java rename to substratevm/src/com.oracle.svm.common/src/com/oracle/svm/common/option/LocatableOption.java index d675521de43d..62927c9a3a31 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/LocatableOption.java +++ b/substratevm/src/com.oracle.svm.common/src/com/oracle/svm/common/option/LocatableOption.java @@ -1,5 +1,6 @@ /* * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2021, Alibaba Group Holding Limited. 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 @@ -22,7 +23,7 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ -package com.oracle.svm.core.option; +package com.oracle.svm.common.option; public final class LocatableOption { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/MultiOptionValue.java b/substratevm/src/com.oracle.svm.common/src/com/oracle/svm/common/option/MultiOptionValue.java similarity index 84% rename from substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/MultiOptionValue.java rename to substratevm/src/com.oracle.svm.common/src/com/oracle/svm/common/option/MultiOptionValue.java index ad6207f9036e..0f678439903c 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/MultiOptionValue.java +++ b/substratevm/src/com.oracle.svm.common/src/com/oracle/svm/common/option/MultiOptionValue.java @@ -1,5 +1,6 @@ /* * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2021, Alibaba Group Holding Limited. 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 @@ -22,11 +23,11 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ -package com.oracle.svm.core.option; +package com.oracle.svm.common.option; import java.util.List; -interface MultiOptionValue { +public interface MultiOptionValue { Class getValueType(); @@ -34,7 +35,7 @@ interface MultiOptionValue { * @return a list of option values, one for each place where the option is used * @implSpec Note that it DOES NOT perform any splitting of string values based on a delimiter. * If you want to perform this split, use a utility - * {@link OptionUtils#flatten(String, LocatableMultiOptionValue.Strings)} + * {@link com.oracle.svm.core.option.OptionUtils#flatten(String, com.oracle.svm.core.option.LocatableMultiOptionValue.Strings)} */ List values(); diff --git a/substratevm/src/com.oracle.svm.common/src/com/oracle/svm/common/option/OptionParsingException.java b/substratevm/src/com.oracle.svm.common/src/com/oracle/svm/common/option/OptionParsingException.java new file mode 100644 index 000000000000..87e2d281bdd8 --- /dev/null +++ b/substratevm/src/com.oracle.svm.common/src/com/oracle/svm/common/option/OptionParsingException.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2021, Alibaba Group Holding Limited. 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.common.option; + +public class OptionParsingException extends Exception { + private static final long serialVersionUID = 1413275434484971842L; + + public OptionParsingException(Exception e) { + super(e); + } +} diff --git a/substratevm/src/com.oracle.svm.common/src/com/oracle/svm/common/option/UnsupportedOptionClassException.java b/substratevm/src/com.oracle.svm.common/src/com/oracle/svm/common/option/UnsupportedOptionClassException.java new file mode 100644 index 000000000000..c92195df9443 --- /dev/null +++ b/substratevm/src/com.oracle.svm.common/src/com/oracle/svm/common/option/UnsupportedOptionClassException.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2021, Alibaba Group Holding Limited. 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.common.option; + +public class UnsupportedOptionClassException extends Exception { + private static final long serialVersionUID = -3105370072461246590L; + + public UnsupportedOptionClassException(String msg) { + super(msg); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java index 399eb506bfe0..0ceb845846fa 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java @@ -125,12 +125,6 @@ protected void onValueUpdate(EconomicMap, Object> values, Boolean o private static ValueUpdateHandler optimizeValueUpdateHandler; private static ValueUpdateHandler debugInfoValueUpdateHandler = SubstrateOptions::defaultDebugInfoValueUpdateHandler; - @Option(help = "Show available options based on comma-separated option-types (allowed categories: User, Expert, Debug).")// - public static final OptionKey PrintFlags = new OptionKey<>(null); - - @Option(help = "Print extra help, if available, based on comma-separated option names. Pass * to show all options that contain extra help.")// - public static final OptionKey PrintFlagsWithExtraHelp = new OptionKey<>(null); - @Option(help = "Control native-image code optimizations: 0 - no optimizations, 1 - basic optimizations, 2 - aggressive optimizations.", type = OptionType.User)// public static final HostedOptionKey Optimize = new HostedOptionKey(2) { @Override diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateUtil.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateUtil.java index 0cb5e35fdeb1..fc52b1c7a2d7 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateUtil.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateUtil.java @@ -34,11 +34,11 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Member; import java.lang.reflect.Method; -import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; +import com.oracle.svm.util.StringUtil; import org.graalvm.compiler.graph.Node.NodeIntrinsic; import org.graalvm.compiler.java.LambdaUtils; import org.graalvm.compiler.nodes.BreakpointNode; @@ -270,31 +270,7 @@ public static String[] split(String value, String separator) { * regular expression. This avoids making regular expression code reachable. */ public static String[] split(String value, String separator, int limit) { - int offset = 0; - int next; - ArrayList list = null; - while ((next = value.indexOf(separator, offset)) != -1) { - if (list == null) { - list = new ArrayList<>(); - } - boolean limited = limit > 0; - if (!limited || list.size() < limit - 1) { - list.add(value.substring(offset, next)); - offset = next + separator.length(); - } else { - break; - } - } - - if (offset == 0) { - /* No match found. */ - return new String[]{value}; - } - - /* Add remaining segment. */ - list.add(value.substring(offset)); - - return list.toArray(new String[list.size()]); + return StringUtil.split(value, separator, limit); } public static String toHex(byte[] data) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/HostedOptionKey.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/HostedOptionKey.java index 70c23993eec9..4816367709d2 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/HostedOptionKey.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/HostedOptionKey.java @@ -24,6 +24,8 @@ */ package com.oracle.svm.core.option; +import com.oracle.svm.common.option.LocatableOption; +import com.oracle.svm.common.option.MultiOptionValue; import org.graalvm.collections.EconomicMap; import org.graalvm.compiler.api.replacements.Fold; import org.graalvm.compiler.options.Option; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/LocatableMultiOptionValue.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/LocatableMultiOptionValue.java index bf43136b21d2..176087b6115f 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/LocatableMultiOptionValue.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/LocatableMultiOptionValue.java @@ -29,6 +29,8 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import com.oracle.svm.common.option.LocatableOption; +import com.oracle.svm.common.option.MultiOptionValue; import org.graalvm.collections.Pair; import com.oracle.svm.core.util.VMError; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/RuntimeOptionParser.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/RuntimeOptionParser.java index ff5a104b6fe2..9dc6007070c4 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/RuntimeOptionParser.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/RuntimeOptionParser.java @@ -41,8 +41,8 @@ import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.log.Log; -import com.oracle.svm.core.option.SubstrateOptionsParser.BooleanOptionFormat; -import com.oracle.svm.core.option.SubstrateOptionsParser.OptionParseResult; +import com.oracle.svm.common.option.CommonOptionParser.BooleanOptionFormat; +import com.oracle.svm.common.option.CommonOptionParser.OptionParseResult; import com.oracle.svm.core.properties.RuntimePropertyParser; import com.oracle.svm.core.util.ImageHeapMap; @@ -163,7 +163,8 @@ private void parseOptionAtRuntime(String arg, String optionPrefix, BooleanOption Predicate> isHosted = optionKey -> false; OptionParseResult parseResult = SubstrateOptionsParser.parseOption(options, isHosted, arg.substring(optionPrefix.length()), values, optionPrefix, booleanOptionFormat); if (parseResult.printFlags() || parseResult.printFlagsWithExtraHelp()) { - SubstrateOptionsParser.printFlags(parseResult::matchesFlagsRuntime, options, optionPrefix, Log.logStream(), parseResult.printFlagsWithExtraHelp()); + SubstrateOptionsParser.printFlags(d -> parseResult.matchesFlags(d, () -> d.getOptionKey() instanceof RuntimeOptionKey), + options, optionPrefix, Log.logStream(), parseResult.printFlagsWithExtraHelp()); System.exit(0); } if (!parseResult.isValid()) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/SubstrateOptionsParser.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/SubstrateOptionsParser.java index f049bd43c7e9..81436ccd784c 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/SubstrateOptionsParser.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/SubstrateOptionsParser.java @@ -26,31 +26,28 @@ // Checkstyle: allow reflection +import com.oracle.svm.core.util.InterruptImageBuilding; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.common.option.CommonOptionParser; +import com.oracle.svm.common.option.CommonOptionParser.BooleanOptionFormat; +import com.oracle.svm.common.option.CommonOptionParser.OptionParseResult; +import com.oracle.svm.common.option.UnsupportedOptionClassException; +import org.graalvm.collections.EconomicMap; +import org.graalvm.compiler.options.OptionDescriptor; +import org.graalvm.compiler.options.OptionDescriptors; +import org.graalvm.compiler.options.OptionKey; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; + import java.io.PrintStream; import java.lang.reflect.Field; -import java.util.ArrayList; import java.util.Arrays; -import java.util.Comparator; -import java.util.EnumSet; -import java.util.HashSet; -import java.util.List; +import java.util.ServiceLoader; import java.util.Set; import java.util.function.Consumer; import java.util.function.Predicate; -import org.graalvm.collections.EconomicMap; -import org.graalvm.compiler.options.OptionDescriptor; -import org.graalvm.compiler.options.OptionKey; -import org.graalvm.compiler.options.OptionType; -import org.graalvm.compiler.options.OptionsParser; -import org.graalvm.nativeimage.Platform; -import org.graalvm.nativeimage.Platforms; - -import com.oracle.svm.core.SubstrateOptions; -import com.oracle.svm.core.SubstrateUtil; -import com.oracle.svm.core.util.InterruptImageBuilding; -import com.oracle.svm.core.util.VMError; -import com.oracle.svm.util.ClassUtil; +import static com.oracle.svm.core.util.VMError.shouldNotReachHere; /** * This class contains methods for parsing options and matching them against @@ -58,283 +55,17 @@ */ public class SubstrateOptionsParser { - public static final String HOSTED_OPTION_PREFIX = "-H:"; - public static final String RUNTIME_OPTION_PREFIX = "-R:"; - public static final int PRINT_OPTION_INDENTATION = 2; - public static final int PRINT_OPTION_WIDTH = 45; - public static final int PRINT_OPTION_WRAP_WIDTH = 120; - - /** - * The result of {@link SubstrateOptionsParser#parseOption}. - */ - static final class OptionParseResult { - private final EnumSet printFlags; - private final Set optionNameFilter; - private final String error; - private final OptionKey optionKey; - private static final String EXTRA_HELP_OPTIONS_WILDCARD = "*"; - - private OptionParseResult(EnumSet printFlags, String error, Set optionNameFilter, OptionKey optionKey) { - this.printFlags = printFlags; - this.error = error; - this.optionNameFilter = optionNameFilter; - this.optionKey = optionKey; - } - - private OptionParseResult(EnumSet printFlags, String error, OptionKey optionKey) { - this(printFlags, error, new HashSet<>(), optionKey); - } - - static OptionParseResult error(String message) { - return new OptionParseResult(EnumSet.noneOf(OptionType.class), message, null); - } - - static OptionParseResult correct(OptionKey optionKey) { - return new OptionParseResult(EnumSet.noneOf(OptionType.class), null, optionKey); - } - - static OptionParseResult printFlags(EnumSet selectedOptionTypes) { - return new OptionParseResult(selectedOptionTypes, null, null); - } - - static OptionParseResult printFlagsWithExtraHelp(Set optionNameFilter) { - Set optionNames = optionNameFilter; - if (optionNames.contains(EXTRA_HELP_OPTIONS_WILDCARD)) { - optionNames = new HashSet<>(); - optionNames.add(EXTRA_HELP_OPTIONS_WILDCARD); - } - return new OptionParseResult(EnumSet.noneOf(OptionType.class), null, optionNames, null); - } - - boolean printFlags() { - return !printFlags.isEmpty(); - } - - boolean printFlagsWithExtraHelp() { - return !optionNameFilter.isEmpty(); - } - - public boolean isValid() { - boolean result = optionKey != null; - assert result == (printFlags.isEmpty() && optionNameFilter.isEmpty() && error == null); - return result; - } - - public String getError() { - return error; - } - - public OptionKey getOptionKey() { - return optionKey; - } - - private boolean matchesFlags(OptionDescriptor d, boolean svmOption) { - if (!printFlags.isEmpty()) { - boolean showAll = printFlags.equals(EnumSet.allOf(OptionType.class)); - return showAll || svmOption && printFlags.contains(d.getOptionType()); - } - if (!optionNameFilter.isEmpty()) { - if (optionNameFilter.contains(EXTRA_HELP_OPTIONS_WILDCARD) && !d.getExtraHelp().isEmpty()) { - return true; - } - return optionNameFilter.contains(d.getName()); - } - return false; - } - - boolean matchesFlagsRuntime(OptionDescriptor d) { - return matchesFlags(d, d.getOptionKey() instanceof RuntimeOptionKey); - } - - boolean matchesFlagsHosted(OptionDescriptor d) { - OptionKey key = d.getOptionKey(); - return matchesFlags(d, key instanceof RuntimeOptionKey || key instanceof HostedOptionKey); - } - } - - /** - * Constants denoting supported boolean option formats. - */ - public enum BooleanOptionFormat { - NAME_VALUE("="), - PLUS_MINUS("+/-"); - - BooleanOptionFormat(String help) { - this.help = help; - } - - private final String help; - - @Override - public String toString() { - return help; - } - } + public static final String HOSTED_OPTION_PREFIX = CommonOptionParser.HOSTED_OPTION_PREFIX; + public static final String RUNTIME_OPTION_PREFIX = CommonOptionParser.RUNTIME_OPTION_PREFIX; static OptionParseResult parseOption(EconomicMap options, Predicate> isHosted, String option, EconomicMap, Object> valuesMap, String optionPrefix, BooleanOptionFormat booleanOptionFormat) { - if (option.length() == 0) { - return OptionParseResult.error("Option name must be specified"); - } - - String optionName; - Object value = null; - String valueString = null; - - char first = option.charAt(0); - int eqIndex = option.indexOf('='); - if (first == '+' || first == '-') { - if (eqIndex != -1) { - return OptionParseResult.error("Cannot mix +/- with = format: '" + optionPrefix + option + "'"); - } - optionName = option.substring(1); - if (booleanOptionFormat == BooleanOptionFormat.NAME_VALUE) { - return OptionParseResult.error("Option " + LocatableOption.from(optionName) + " must use = format, not +/- prefix"); - } - value = (first == '+'); - } else { - if (eqIndex == -1) { - optionName = option; - valueString = null; - } else { - optionName = option.substring(0, eqIndex); - valueString = option.substring(eqIndex + 1); - } - } - - LocatableOption current = LocatableOption.from(optionName); - OptionDescriptor desc = options.get(current.name); - if (desc == null && value != null) { - if (eqIndex != -1) { - optionName = option.substring(1, eqIndex); - current = LocatableOption.from(optionName); - desc = options.get(current.name); - } - } - - optionName = current.name; - - if (desc == null) { - List matches = new ArrayList<>(); - OptionsParser.collectFuzzyMatches(options.getValues(), optionName, matches); - StringBuilder msg = new StringBuilder("Could not find option ").append(current); - if (!matches.isEmpty()) { - msg.append(". Did you mean one of these:"); - for (OptionDescriptor match : matches) { - msg.append(' ').append(match.getName()); - } - } - msg.append(". Use ").append(optionPrefix).append(SubstrateOptions.PrintFlags.getName()).append("= to list all available options."); - return OptionParseResult.error(msg.toString()); - } - - OptionKey optionKey = desc.getOptionKey(); - boolean hostedOption = isHosted.test(optionKey); - Class optionValueType = getMultiOptionValueElementType(optionKey); - Class optionType = hostedOption && optionValueType != null ? optionValueType : desc.getOptionValueType(); - - if (value == null) { - if (optionType == Boolean.class && booleanOptionFormat == BooleanOptionFormat.PLUS_MINUS) { - return OptionParseResult.error("Boolean option " + current + " must have +/- prefix"); - } - if (valueString == null) { - return OptionParseResult.error("Missing value for option " + current); - } - try { - value = parseValue(optionType, current, valueString); - if (value instanceof OptionParseResult) { - return (OptionParseResult) value; - } - } catch (NumberFormatException ex) { - return OptionParseResult.error("Invalid value for option " + current + ": '" + valueString + "' is not a valid number"); - } - } else { - if (optionType != Boolean.class) { - return OptionParseResult.error("Non-boolean option " + current + " can not use +/- prefix. Use '" + current.name + "=' format"); - } - } - - optionKey.update(valuesMap, hostedOption ? LocatableOption.value(value, current.origin) : value); - - if (SubstrateOptions.PrintFlags.getName().equals(optionName)) { - String optionValue = (String) value; - EnumSet selectedOptionTypes; - if (optionValue.isEmpty()) { - selectedOptionTypes = EnumSet.allOf(OptionType.class); - } else { - selectedOptionTypes = EnumSet.noneOf(OptionType.class); - String enumString = null; - try { - String[] enumStrings = SubstrateUtil.split(optionValue, ","); - - for (String string : enumStrings) { - enumString = string; - selectedOptionTypes.add(OptionType.valueOf(enumString)); - } - } catch (IllegalArgumentException e) { - StringBuilder sb = new StringBuilder(); - boolean firstValue = true; - for (OptionType ot : OptionType.values()) { - if (firstValue) { - firstValue = false; - } else { - sb.append(", "); - } - sb.append(ot.name()); - } - String possibleValues = sb.toString(); - return OptionParseResult.error("Invalid value for option " + current + ". '" + enumString + "' is not one of: " + possibleValues); - } - } - return OptionParseResult.printFlags(selectedOptionTypes); - } - if (SubstrateOptions.PrintFlagsWithExtraHelp.getName().equals(optionName)) { - String optionValue = (String) value; - String[] optionNames = SubstrateUtil.split(optionValue, ","); - HashSet selectedOptionNames = new HashSet<>(Arrays.asList(optionNames)); - return OptionParseResult.printFlagsWithExtraHelp(selectedOptionNames); - } - - return OptionParseResult.correct(optionKey); - } - - private static Class getMultiOptionValueElementType(OptionKey optionKey) { - Object defaultValue = optionKey.getDefaultValue(); - if (defaultValue instanceof MultiOptionValue) { - return ((MultiOptionValue) defaultValue).getValueType(); - } - return null; - } - - @SuppressWarnings("unchecked") - static Object parseValue(Class optionType, LocatableOption option, String valueString) throws NumberFormatException { - Object value; - if (optionType == Integer.class) { - long longValue = parseLong(valueString); - if ((int) longValue != longValue) { - return OptionParseResult.error("Wrong value for option " + option + ": '" + valueString + "' is not a valid number"); - } - value = (int) longValue; - } else if (optionType == Long.class) { - value = parseLong(valueString); - } else if (optionType == String.class) { - value = valueString; - } else if (optionType == Double.class) { - value = parseDouble(valueString); - } else if (optionType == Boolean.class) { - if (valueString.equals("true")) { - value = true; - } else if (valueString.equals("false")) { - value = false; - } else { - return OptionParseResult.error("Boolean option " + option + " must have value 'true' or 'false'"); - } - } else if (optionType.isEnum()) { - value = Enum.valueOf(optionType.asSubclass(Enum.class), valueString); - } else { - throw VMError.shouldNotReachHere(option + " uses unsupported option value class: " + ClassUtil.getUnqualifiedName(optionType)); + try { + return CommonOptionParser.parseOption(options, isHosted, option, valuesMap, optionPrefix, booleanOptionFormat); + } catch (UnsupportedOptionClassException e) { + VMError.shouldNotReachHere(e.getMessage()); + return null; } - return value; } /** @@ -359,7 +90,10 @@ public static boolean parseHostedOption(String optionPrefix, EconomicMap optionParseResult.matchesFlags(d, () -> { + OptionKey key = d.getOptionKey(); + return key instanceof RuntimeOptionKey || key instanceof HostedOptionKey; + }), options, optionPrefix, out, optionParseResult.printFlagsWithExtraHelp()); throw new InterruptImageBuilding(""); } if (!optionParseResult.isValid()) { @@ -383,46 +117,12 @@ public static boolean parseHostedOption(String optionPrefix, EconomicMap optionDescriptors, Consumer optionDescriptorConsumer) { + CommonOptionParser.collectOptions(optionDescriptors, optionDescriptorConsumer); } public static void printOption(Consumer println, String option, String description, int indentation, int optionWidth, int wrapWidth) { - String indent = spaces(indentation); - String desc = description != null ? description : ""; - desc = wrapWidth > 0 ? wrap(desc, wrapWidth) : desc; - String nl = System.lineSeparator(); - String[] descLines = SubstrateUtil.split(desc, nl); - if (option.length() >= optionWidth && description != null) { - println.accept(indent + option + nl + indent + spaces(optionWidth) + descLines[0]); - } else { - println.accept(indent + option + spaces(optionWidth - option.length()) + descLines[0]); - } - for (int i = 1; i < descLines.length; i++) { - println.accept(indent + spaces(optionWidth) + descLines[i]); - } + CommonOptionParser.printOption(println, option, description, indentation, optionWidth, wrapWidth); } /** @@ -441,151 +141,15 @@ public static void printOption(Consumer println, String option, String d * between space and execution efficiency is acceptable. */ static void printFlags(Predicate filter, EconomicMap options, String prefix, PrintStream out, boolean verbose) { - List sortedDescriptors = new ArrayList<>(); - for (OptionDescriptor option : options.getValues()) { - if (filter.test(option)) { - sortedDescriptors.add(option); - } - } - sortedDescriptors.sort(Comparator.comparing(OptionDescriptor::getName)); - - for (OptionDescriptor descriptor : sortedDescriptors) { - String helpMsg = verbose && !descriptor.getExtraHelp().isEmpty() ? "" : descriptor.getHelp(); - int helpLen = helpMsg.length(); - if (helpLen > 0 && helpMsg.charAt(helpLen - 1) != '.') { - helpMsg += '.'; - } - boolean stringifiedArrayValue = false; - Object defaultValue = descriptor.getOptionKey().getDefaultValue(); - if (defaultValue != null && defaultValue.getClass().isArray()) { - Object[] defaultValues = (Object[]) defaultValue; - if (defaultValues.length == 1) { - defaultValue = defaultValues[0]; - } else { - List stringList = new ArrayList<>(); - String optionPrefix = prefix + descriptor.getName() + "="; - for (Object rawValue : defaultValues) { - String value; - if (rawValue instanceof String) { - value = '"' + String.valueOf(rawValue) + '"'; - } else { - value = String.valueOf(rawValue); - } - stringList.add(optionPrefix + value); - } - if (helpLen != 0) { - helpMsg += ' '; - } - helpMsg += "Default: "; - if (stringList.isEmpty()) { - helpMsg += "None"; - } else { - helpMsg += String.join(" ", stringList); - } - stringifiedArrayValue = true; - } - } - String verboseHelp = ""; - if (verbose) { - verboseHelp = System.lineSeparator() + descriptor.getHelp() + System.lineSeparator() + String.join(System.lineSeparator(), descriptor.getExtraHelp()); - } else if (!descriptor.getExtraHelp().isEmpty()) { - verboseHelp = " [Extra help available]"; - } - int wrapWidth = verbose ? 0 : PRINT_OPTION_WRAP_WIDTH; - if (descriptor.getOptionValueType() == Boolean.class) { - Boolean val = (Boolean) defaultValue; - if (helpLen != 0) { - helpMsg += ' '; - } - if (val != null) { - if (val) { - helpMsg += "Default: + (enabled)."; - } else { - helpMsg += "Default: - (disabled)."; - } - } - printOption(out, prefix + "\u00b1" + descriptor.getName(), helpMsg + verboseHelp, wrapWidth); - } else { - if (defaultValue == null) { - if (helpLen != 0) { - helpMsg += ' '; - } - helpMsg += "Default: None"; - } - helpMsg += verboseHelp; - if (stringifiedArrayValue || defaultValue == null) { - printOption(out, prefix + descriptor.getName() + "=...", helpMsg, wrapWidth); - } else { - if (defaultValue instanceof String) { - defaultValue = '"' + String.valueOf(defaultValue) + '"'; - } - printOption(out, prefix + descriptor.getName() + "=" + defaultValue, helpMsg, wrapWidth); - } - } - } + CommonOptionParser.printFlags(filter, options, prefix, out, verbose); } public static long parseLong(String v) { - String valueString = v.trim().toLowerCase(); - long scale = 1; - if (valueString.endsWith("k")) { - scale = 1024L; - } else if (valueString.endsWith("m")) { - scale = 1024L * 1024L; - } else if (valueString.endsWith("g")) { - scale = 1024L * 1024L * 1024L; - } else if (valueString.endsWith("t")) { - scale = 1024L * 1024L * 1024L * 1024L; - } - - if (scale != 1) { - /* Remove trailing scale character. */ - valueString = valueString.substring(0, valueString.length() - 1); - } - - return Long.parseLong(valueString) * scale; + return CommonOptionParser.parseLong(v); } - /** - * Parses the provide string to a double number, avoiding the JDK dependencies (which pull in a - * lot of classes, including the regular expression library). Only simple numbers are supported, - * without fancy exponent styles. - */ public static double parseDouble(String v) { - String valueString = v.trim(); - - int dotPos = valueString.indexOf('.'); - if (dotPos == -1) { - return parseLong(valueString); - } - - String beforeDot = valueString.substring(0, dotPos); - String afterDot = valueString.substring(dotPos + 1); - - double sign = 1; - if (beforeDot.startsWith("-")) { - sign = -1; - beforeDot = beforeDot.substring(1); - } else if (beforeDot.startsWith("+")) { - beforeDot = beforeDot.substring(1); - } - - if (beforeDot.startsWith("-") || beforeDot.startsWith("+") || afterDot.startsWith("-") || afterDot.startsWith("+") || - (beforeDot.length() == 0 && afterDot.length() == 0)) { - throw new NumberFormatException(v); - } - - double integral = 0; - if (beforeDot.length() > 0) { - integral = Long.parseLong(beforeDot); - } - - double fraction = 0; - if (afterDot.length() > 0) { - fraction = Long.parseLong(afterDot) * Math.pow(10, -afterDot.length()); - } - - return sign * (integral + fraction); + return CommonOptionParser.parseDouble(v); } /** diff --git a/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/NativeImage.java b/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/NativeImage.java index 1373baa0d0c9..c7c7e5b08aa8 100644 --- a/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/NativeImage.java +++ b/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/NativeImage.java @@ -64,6 +64,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import com.oracle.svm.common.option.CommonOptions; import org.graalvm.compiler.options.OptionKey; import org.graalvm.compiler.serviceprovider.JavaVersionUtil; import org.graalvm.nativeimage.Platform; @@ -188,8 +189,8 @@ void addFallbackBuildArgs(@SuppressWarnings("unused") List buildArgs) { public static final String oH = "-H:"; static final String oR = "-R:"; - final String enablePrintFlags = SubstrateOptions.PrintFlags.getName(); - final String enablePrintFlagsWithExtraHelp = SubstrateOptions.PrintFlagsWithExtraHelp.getName(); + final String enablePrintFlags = CommonOptions.PrintFlags.getName(); + final String enablePrintFlagsWithExtraHelp = CommonOptions.PrintFlagsWithExtraHelp.getName(); private static String oH(OptionKey option) { return oH + option.getName() + "="; diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/ConfigurationParserUtils.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/ConfigurationParserUtils.java index f5e2a1b5d56f..193c309a935a 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/ConfigurationParserUtils.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/ConfigurationParserUtils.java @@ -24,7 +24,7 @@ */ package com.oracle.svm.hosted.config; -import static com.oracle.svm.core.SubstrateOptions.PrintFlags; +import static com.oracle.svm.common.option.CommonOptions.PrintFlags; import java.io.IOException; import java.io.InputStreamReader; diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/option/HostedOptionParser.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/option/HostedOptionParser.java index 657da655ed43..e9a795dc44a3 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/option/HostedOptionParser.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/option/HostedOptionParser.java @@ -24,7 +24,7 @@ */ package com.oracle.svm.hosted.option; -import static com.oracle.svm.core.option.SubstrateOptionsParser.BooleanOptionFormat.PLUS_MINUS; +import static com.oracle.svm.common.option.CommonOptionParser.BooleanOptionFormat.PLUS_MINUS; import static com.oracle.svm.core.util.VMError.shouldNotReachHere; import java.util.ArrayList; @@ -59,30 +59,28 @@ public HostedOptionParser(ClassLoader imageClassLoader) { } public static void collectOptions(ServiceLoader optionDescriptors, EconomicMap allHostedOptions, - EconomicMap allRuntimeOptions) { - for (OptionDescriptors optionDescriptor : optionDescriptors) { - for (OptionDescriptor descriptor : optionDescriptor) { - String name = descriptor.getName(); - - if (descriptor.getDeclaringClass().getAnnotation(Platforms.class) != null) { - throw UserError.abort("Options must not be declared in a class that has a @%s annotation: option %s declared in %s", - Platforms.class.getSimpleName(), name, descriptor.getDeclaringClass().getTypeName()); - } + EconomicMap allRuntimeOptions) { + SubstrateOptionsParser.collectOptions(optionDescriptors, descriptor -> { + String name = descriptor.getName(); + + if (descriptor.getDeclaringClass().getAnnotation(Platforms.class) != null) { + throw UserError.abort("Options must not be declared in a class that has a @%s annotation: option %s declared in %s", + Platforms.class.getSimpleName(), name, descriptor.getDeclaringClass().getTypeName()); + } - if (!(descriptor.getOptionKey() instanceof RuntimeOptionKey)) { - OptionDescriptor existing = allHostedOptions.put(name, descriptor); - if (existing != null) { - throw shouldNotReachHere("Option name \"" + name + "\" has multiple definitions: " + existing.getLocation() + " and " + descriptor.getLocation()); - } + if (!(descriptor.getOptionKey() instanceof RuntimeOptionKey)) { + OptionDescriptor existing = allHostedOptions.put(name, descriptor); + if (existing != null) { + throw shouldNotReachHere("Option name \"" + name + "\" has multiple definitions: " + existing.getLocation() + " and " + descriptor.getLocation()); } - if (!(descriptor.getOptionKey() instanceof HostedOptionKey)) { - OptionDescriptor existing = allRuntimeOptions.put(name, descriptor); - if (existing != null) { - throw shouldNotReachHere("Option name \"" + name + "\" has multiple definitions: " + existing.getLocation() + " and " + descriptor.getLocation()); - } + } + if (!(descriptor.getOptionKey() instanceof HostedOptionKey)) { + OptionDescriptor existing = allRuntimeOptions.put(name, descriptor); + if (existing != null) { + throw shouldNotReachHere("Option name \"" + name + "\" has multiple definitions: " + existing.getLocation() + " and " + descriptor.getLocation()); } } - } + }); } public List parse(List args) { diff --git a/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/StringUtil.java b/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/StringUtil.java new file mode 100644 index 000000000000..602484d32ab5 --- /dev/null +++ b/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/StringUtil.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2021, Alibaba Group Holding Limited. 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.util; + +import java.util.ArrayList; + +public class StringUtil { + + /** + * Similar to {@link String#split(String)} but with a fixed separator string instead of a + * regular expression. This avoids making regular expression code reachable. + */ + public static String[] split(String value, String separator) { + return split(value, separator, 0); + } + + /** + * Similar to {@link String#split(String, int)} but with a fixed separator string instead of a + * regular expression. This avoids making regular expression code reachable. + */ + public static String[] split(String value, String separator, int limit) { + int offset = 0; + int next; + ArrayList list = null; + while ((next = value.indexOf(separator, offset)) != -1) { + if (list == null) { + list = new ArrayList<>(); + } + boolean limited = limit > 0; + if (!limited || list.size() < limit - 1) { + list.add(value.substring(offset, next)); + offset = next + separator.length(); + } else { + break; + } + } + + if (offset == 0) { + /* No match found. */ + return new String[]{value}; + } + + /* Add remaining segment. */ + list.add(value.substring(offset)); + + return list.toArray(new String[list.size()]); + } +}