xref: /aosp_15_r20/external/jazzer-api/src/main/java/com/code_intelligence/jazzer/driver/OptParser.java (revision 33edd6723662ea34453766bfdca85dbfdd5342b8)
1 /*
2  * Copyright 2022 Code Intelligence GmbH
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.code_intelligence.jazzer.driver;
18 
19 import static java.lang.System.exit;
20 
21 import com.code_intelligence.jazzer.utils.Log;
22 import java.io.File;
23 import java.util.ArrayList;
24 import java.util.Arrays;
25 import java.util.Collections;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.Objects;
29 import java.util.TreeMap;
30 import java.util.function.Supplier;
31 import java.util.stream.Collectors;
32 import java.util.stream.Stream;
33 
34 final class OptParser {
35   private static final String[] HELP_HEADER = new String[] {
36       "A coverage-guided, in-process fuzzer for the JVM",
37       "",
38       "Usage:",
39       String.format(
40           "  java -cp jazzer.jar[%cclasspath_entries] com.code_intelligence.jazzer.Jazzer --target_class=<target class> [args...]",
41           File.separatorChar),
42       String.format(
43           "  java -cp jazzer.jar[%cclasspath_entries] com.code_intelligence.jazzer.Jazzer --autofuzz=<method reference> [args...]",
44           File.separatorChar),
45       "",
46       "In addition to the options listed below, Jazzer also accepts all",
47       "libFuzzer options described at:",
48       "  https://llvm.org/docs/LibFuzzer.html#options",
49       "",
50       "Options:",
51   };
52   private static final String OPTIONS_PREFIX = "jazzer.";
53 
54   // All supported arguments are added to this set by the individual *Setting methods.
55   private static final Map<String, OptDetails> knownArgs = new TreeMap<>();
56 
getHelpText()57   static String getHelpText() {
58     return Stream
59         .concat(Arrays.stream(HELP_HEADER),
60             knownArgs.values().stream().filter(Objects::nonNull).map(OptDetails::toString))
61         .collect(Collectors.joining("\n\n"));
62   }
63 
ignoreSetting(String name)64   static void ignoreSetting(String name) {
65     knownArgs.put(name, null);
66   }
67 
stringSetting(String name, String defaultValue, String description)68   static String stringSetting(String name, String defaultValue, String description) {
69     knownArgs.put(name, OptDetails.create(name, "string", defaultValue, description));
70     return System.getProperty(OPTIONS_PREFIX + name, defaultValue);
71   }
72 
stringListSetting(String name, String description)73   static List<String> stringListSetting(String name, String description) {
74     return lazyStringListSetting(name, description).get();
75   }
76 
stringListSetting(String name, char separator, String description)77   static List<String> stringListSetting(String name, char separator, String description) {
78     return lazyStringListSetting(name, separator, description).get();
79   }
80 
lazyStringListSetting(String name, String description)81   static Supplier<List<String>> lazyStringListSetting(String name, String description) {
82     return lazyStringListSetting(name, File.pathSeparatorChar, description);
83   }
84 
lazyStringListSetting( String name, char separator, String description)85   static Supplier<List<String>> lazyStringListSetting(
86       String name, char separator, String description) {
87     knownArgs.put(name,
88         OptDetails.create(
89             name, String.format("list separated by '%c'", separator), "", description));
90     return () -> {
91       String value = System.getProperty(OPTIONS_PREFIX + name);
92       if (value == null || value.isEmpty()) {
93         return Collections.emptyList();
94       }
95       return splitOnUnescapedSeparator(value, separator);
96     };
97   }
98 
boolSetting(String name, boolean defaultValue, String description)99   static boolean boolSetting(String name, boolean defaultValue, String description) {
100     knownArgs.put(
101         name, OptDetails.create(name, "boolean", Boolean.toString(defaultValue), description));
102     String value = System.getProperty(OPTIONS_PREFIX + name);
103     if (value == null) {
104       return defaultValue;
105     }
106     return Boolean.parseBoolean(value);
107   }
108 
uint64Setting(String name, long defaultValue, String description)109   static long uint64Setting(String name, long defaultValue, String description) {
110     knownArgs.put(
111         name, OptDetails.create(name, "uint64", Long.toUnsignedString(defaultValue), description));
112     String value = System.getProperty(OPTIONS_PREFIX + name);
113     if (value == null) {
114       return defaultValue;
115     }
116     return Long.parseUnsignedLong(value, 10);
117   }
118 
failOnUnknownArgument()119   static void failOnUnknownArgument() {
120     System.getProperties()
121         .keySet()
122         .stream()
123         .map(key -> (String) key)
124         .filter(key -> key.startsWith("jazzer."))
125         .map(key -> key.substring("jazzer.".length()))
126         .filter(key -> !key.startsWith("internal."))
127         .filter(key -> !knownArgs.containsKey(key))
128         .findFirst()
129         .ifPresent(unknownArg -> {
130           Log.error(String.format(
131               "Unknown argument '--%1$s' or property 'jazzer.%1$s' (list all available arguments with --help)",
132               unknownArg));
133           exit(1);
134         });
135   }
136 
137   /**
138    * Split value into non-empty takens separated by separator. Backslashes can be used to escape
139    * separators (or backslashes).
140    *
141    * @param value the string to split
142    * @param separator a single character to split on (backslash is not allowed)
143    * @return an immutable list of tokens obtained by splitting value on separator
144    */
splitOnUnescapedSeparator(String value, char separator)145   static List<String> splitOnUnescapedSeparator(String value, char separator) {
146     if (separator == '\\') {
147       throw new IllegalArgumentException("separator '\\' is not supported");
148     }
149     ArrayList<String> tokens = new ArrayList<>();
150     StringBuilder currentToken = new StringBuilder();
151     boolean inEscapeState = false;
152     for (int pos = 0; pos < value.length(); pos++) {
153       char c = value.charAt(pos);
154       if (inEscapeState) {
155         currentToken.append(c);
156         inEscapeState = false;
157       } else if (c == '\\') {
158         inEscapeState = true;
159       } else if (c == separator) {
160         // Do not emit empty tokens between consecutive separators.
161         if (currentToken.length() > 0) {
162           tokens.add(currentToken.toString());
163         }
164         currentToken.setLength(0);
165       } else {
166         currentToken.append(c);
167       }
168     }
169     if (currentToken.length() > 0) {
170       tokens.add(currentToken.toString());
171     }
172     return Collections.unmodifiableList(tokens);
173   }
174 
175   private static final class OptDetails {
176     final String name;
177     final String type;
178     final String defaultValue;
179     final String description;
180 
OptDetails(String name, String type, String defaultValue, String description)181     private OptDetails(String name, String type, String defaultValue, String description) {
182       this.name = name;
183       this.type = type;
184       this.defaultValue = defaultValue;
185       this.description = description;
186     }
187 
create(String name, String type, String defaultValue, String description)188     static OptDetails create(String name, String type, String defaultValue, String description) {
189       if (description == null) {
190         return null;
191       }
192       return new OptDetails(checkNotNullOrEmpty(name, "name"), checkNotNullOrEmpty(type, "type"),
193           defaultValue, checkNotNullOrEmpty(description, "description"));
194     }
195 
196     @Override
toString()197     public String toString() {
198       return String.format(
199           "--%s (%s, default: \"%s\")%n     %s", name, type, defaultValue, description);
200     }
201 
checkNotNullOrEmpty(String arg, String name)202     private static String checkNotNullOrEmpty(String arg, String name) {
203       if (arg == null) {
204         throw new NullPointerException(name + " must not be null");
205       }
206       if (arg.isEmpty()) {
207         throw new NullPointerException(name + " must not be empty");
208       }
209       return arg;
210     }
211   }
212 }
213