FunctionOverrides.cpp
1 /* 2 * Copyright (C) 2015-2019 Apple Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY 14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR 17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26 #include "config.h" 27 #include "FunctionOverrides.h" 28 29 #include "Options.h" 30 #include <stdio.h> 31 #include <string.h> 32 #include <wtf/DataLog.h> 33 #include <wtf/NeverDestroyed.h> 34 #include <wtf/text/CString.h> 35 #include <wtf/text/StringBuilder.h> 36 #include <wtf/text/StringHash.h> 37 38 namespace JSC { 39 40 /* 41 The overrides file defines function bodies that we will want to override with 42 a replacement for debugging purposes. The overrides file may contain 43 'override' and 'with' clauses like these: 44 45 // Example 1: function foo1(a) 46 override !@#$%{ print("In foo1"); }!@#$% 47 with abc{ 48 print("I am overridden"); 49 }abc 50 51 // Example 2: function foo2(a) 52 override %%%{ 53 print("foo2's body has a string with }%% in it."); 54 // Because }%% appears in the function body here, we cannot use 55 // %% or % as the delimiter. %%% is ok though. 56 }%%% 57 with %%%{ 58 print("Overridden foo2"); 59 }%%% 60 61 1. Comments are lines starting with //. All comments will be ignored. 62 63 2. An 'override' clause is used to specify the original function body we 64 want to override. The with clause is used to specify the overriding 65 function body. 66 67 An 'override' clause must be followed immediately by a 'with' clause. 68 69 3. An 'override' clause must be of the form: 70 override <delimiter>{...function body...}<delimiter> 71 72 The override keyword must be at the start of the line. 73 74 <delimiter> may be any string of any ASCII characters (except for '{', 75 '}', and whitespace characters) as long as the pattern of "}<delimiter>" 76 does not appear in the function body e.g. the override clause of Example 2 77 above illustrates this. 78 79 The start and end <delimiter> must be identical. 80 81 The space between the override keyword and the start <delimiter> is 82 required. 83 84 All characters between the pair of delimiters will be considered to 85 be part of the function body string. This allows us to also work 86 with script source that are multi-lined i.e. newlines are allowed. 87 88 4. A 'with' clause is identical in form to an 'override' clause except that 89 it uses the 'with' keyword instead of the 'override' keyword. 90 */ 91 92 struct FunctionOverridesAssertScope { 93 FunctionOverridesAssertScope() { RELEASE_ASSERT(g_jscConfig.restrictedOptionsEnabled); } 94 ~FunctionOverridesAssertScope() { RELEASE_ASSERT(g_jscConfig.restrictedOptionsEnabled); } 95 }; 96 97 FunctionOverrides& FunctionOverrides::overrides() 98 { 99 FunctionOverridesAssertScope assertScope; 100 static LazyNeverDestroyed<FunctionOverrides> overrides; 101 static std::once_flag initializeListFlag; 102 std::call_once(initializeListFlag, [] { 103 FunctionOverridesAssertScope assertScope; 104 const char* overridesFileName = Options::functionOverrides(); 105 overrides.construct(overridesFileName); 106 }); 107 return overrides; 108 } 109 110 FunctionOverrides::FunctionOverrides(const char* overridesFileName) 111 { 112 FunctionOverridesAssertScope assertScope; 113 parseOverridesInFile(holdLock(m_lock), overridesFileName); 114 } 115 116 void FunctionOverrides::reinstallOverrides() 117 { 118 FunctionOverridesAssertScope assertScope; 119 FunctionOverrides& overrides = FunctionOverrides::overrides(); 120 auto locker = holdLock(overrides.m_lock); 121 const char* overridesFileName = Options::functionOverrides(); 122 overrides.clear(locker); 123 overrides.parseOverridesInFile(locker, overridesFileName); 124 } 125 126 static void initializeOverrideInfo(const SourceCode& origCode, const String& newBody, FunctionOverrides::OverrideInfo& info) 127 { 128 FunctionOverridesAssertScope assertScope; 129 String origProviderStr = origCode.provider()->source().toString(); 130 unsigned origStart = origCode.startOffset(); 131 unsigned origFunctionStart = origProviderStr.reverseFind("function", origStart); 132 unsigned origBraceStart = origProviderStr.find("{", origStart); 133 unsigned headerLength = origBraceStart - origFunctionStart; 134 String origHeader = origProviderStr.substring(origFunctionStart, headerLength); 135 136 String newProviderStr; 137 newProviderStr.append(origHeader); 138 newProviderStr.append(newBody); 139 140 auto overridden = "<overridden>"_s; 141 URL url({ }, overridden); 142 Ref<SourceProvider> newProvider = StringSourceProvider::create(newProviderStr, SourceOrigin { url }, overridden); 143 144 info.firstLine = 1; 145 info.lineCount = 1; // Faking it. This doesn't really matter for now. 146 info.startColumn = 1; 147 info.endColumn = 1; // Faking it. This doesn't really matter for now. 148 info.parametersStartOffset = newProviderStr.find("("); 149 info.typeProfilingStartOffset = newProviderStr.find("{"); 150 info.typeProfilingEndOffset = newProviderStr.length() - 1; 151 152 info.sourceCode = 153 SourceCode(WTFMove(newProvider), info.parametersStartOffset, info.typeProfilingEndOffset + 1, 1, 1); 154 } 155 156 bool FunctionOverrides::initializeOverrideFor(const SourceCode& origCode, FunctionOverrides::OverrideInfo& result) 157 { 158 FunctionOverridesAssertScope assertScope; 159 RELEASE_ASSERT(Options::functionOverrides()); 160 FunctionOverrides& overrides = FunctionOverrides::overrides(); 161 162 String sourceString = origCode.view().toString(); 163 size_t sourceBodyStart = sourceString.find('{'); 164 if (sourceBodyStart == notFound) 165 return false; 166 String sourceBodyString = sourceString.substring(sourceBodyStart); 167 168 String newBody; 169 { 170 auto locker = holdLock(overrides.m_lock); 171 auto it = overrides.m_entries.find(sourceBodyString.isolatedCopy()); 172 if (it == overrides.m_entries.end()) 173 return false; 174 newBody = it->value.isolatedCopy(); 175 } 176 177 initializeOverrideInfo(origCode, newBody, result); 178 RELEASE_ASSERT(Options::functionOverrides()); 179 return true; 180 } 181 182 #define SYNTAX_ERROR "SYNTAX ERROR" 183 #define IO_ERROR "IO ERROR" 184 #define FAIL_WITH_ERROR(error, errorMessageInBrackets) \ 185 do { \ 186 dataLog("functionOverrides ", error, ": "); \ 187 dataLog errorMessageInBrackets; \ 188 exit(EXIT_FAILURE); \ 189 } while (false) 190 191 static bool hasDisallowedCharacters(const char* str, size_t length) 192 { 193 while (length--) { 194 char c = *str++; 195 // '{' is also disallowed, but we don't need to check for it because 196 // parseClause() searches for '{' as the end of the start delimiter. 197 // As a result, the parsed delimiter string will never include '{'. 198 if (c == '}' || isASCIISpace(c)) 199 return true; 200 } 201 return false; 202 } 203 204 static String parseClause(const char* keyword, size_t keywordLength, FILE* file, const char* line, char* buffer, size_t bufferSize) 205 { 206 FunctionOverridesAssertScope assertScope; 207 const char* keywordPos = strstr(line, keyword); 208 if (!keywordPos) 209 FAIL_WITH_ERROR(SYNTAX_ERROR, ("Expecting '", keyword, "' clause:\n", line, "\n")); 210 if (keywordPos != line) 211 FAIL_WITH_ERROR(SYNTAX_ERROR, ("Cannot have any characters before '", keyword, "':\n", line, "\n")); 212 if (line[keywordLength] != ' ') 213 FAIL_WITH_ERROR(SYNTAX_ERROR, ("'", keyword, "' must be followed by a ' ':\n", line, "\n")); 214 215 const char* delimiterStart = &line[keywordLength + 1]; 216 const char* delimiterEnd = strstr(delimiterStart, "{"); 217 if (!delimiterEnd) 218 FAIL_WITH_ERROR(SYNTAX_ERROR, ("Missing { after '", keyword, "' clause start delimiter:\n", line, "\n")); 219 220 size_t delimiterLength = delimiterEnd - delimiterStart; 221 String delimiter(delimiterStart, delimiterLength); 222 223 if (hasDisallowedCharacters(delimiterStart, delimiterLength)) 224 FAIL_WITH_ERROR(SYNTAX_ERROR, ("Delimiter '", delimiter, "' cannot have '{', '}', or whitespace:\n", line, "\n")); 225 226 String terminatorString; 227 terminatorString.append('}'); 228 terminatorString.append(delimiter); 229 230 CString terminatorCString = terminatorString.ascii(); 231 const char* terminator = terminatorCString.data(); 232 line = delimiterEnd; // Start from the {. 233 234 StringBuilder builder; 235 do { 236 const char* p = strstr(line, terminator); 237 if (p) { 238 if (p[strlen(terminator)] != '\n') 239 FAIL_WITH_ERROR(SYNTAX_ERROR, ("Unexpected characters after '", keyword, "' clause end delimiter '", delimiter, "':\n", line, "\n")); 240 241 builder.appendCharacters(line, p - line + 1); 242 return builder.toString(); 243 } 244 builder.append(line); 245 246 } while ((line = fgets(buffer, bufferSize, file))); 247 248 FAIL_WITH_ERROR(SYNTAX_ERROR, ("'", keyword, "' clause end delimiter '", delimiter, "' not found:\n", builder.toString(), "\n", "Are you missing a '}' before the delimiter?\n")); 249 } 250 251 void FunctionOverrides::parseOverridesInFile(const AbstractLocker&, const char* fileName) 252 { 253 FunctionOverridesAssertScope assertScope; 254 if (!fileName) 255 return; 256 257 FILE* file = fopen(fileName, "r"); 258 if (!file) 259 FAIL_WITH_ERROR(IO_ERROR, ("Failed to open file ", fileName, ". Did you add the file-read-data entitlement to WebProcess.sb?\n")); 260 261 char* line; 262 char buffer[BUFSIZ]; 263 while ((line = fgets(buffer, sizeof(buffer), file))) { 264 if (strstr(line, "//") == line) 265 continue; 266 267 if (line[0] == '\n' || line[0] == '\0') 268 continue; 269 270 size_t keywordLength; 271 272 keywordLength = sizeof("override") - 1; 273 String keyStr = parseClause("override", keywordLength, file, line, buffer, sizeof(buffer)); 274 275 line = fgets(buffer, sizeof(buffer), file); 276 277 keywordLength = sizeof("with") - 1; 278 String valueStr = parseClause("with", keywordLength, file, line, buffer, sizeof(buffer)); 279 280 m_entries.add(keyStr, valueStr); 281 } 282 283 int result = fclose(file); 284 if (result) 285 dataLogF("Failed to close file %s: %s\n", fileName, strerror(errno)); 286 } 287 288 } // namespace JSC 289