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