|  | /* | 
|  | * Copyright (C) 2015-2016 Apple Inc. All rights reserved. | 
|  | * | 
|  | * Redistribution and use in source and binary forms, with or without | 
|  | * modification, are permitted provided that the following conditions | 
|  | * are met: | 
|  | * 1. Redistributions of source code must retain the above copyright | 
|  | *    notice, this list of conditions and the following disclaimer. | 
|  | * 2. Redistributions in binary form must reproduce the above copyright | 
|  | *    notice, this list of conditions and the following disclaimer in the | 
|  | *    documentation and/or other materials provided with the distribution. | 
|  | * | 
|  | * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY | 
|  | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | 
|  | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR | 
|  | * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR | 
|  | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, | 
|  | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, | 
|  | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR | 
|  | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY | 
|  | * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | 
|  | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 
|  | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 
|  | */ | 
|  |  | 
|  | #include "config.h" | 
|  | #include "FunctionOverrides.h" | 
|  |  | 
|  | #include "Options.h" | 
|  | #include <stdio.h> | 
|  | #include <string.h> | 
|  | #include <wtf/DataLog.h> | 
|  | #include <wtf/NeverDestroyed.h> | 
|  | #include <wtf/text/CString.h> | 
|  | #include <wtf/text/StringBuilder.h> | 
|  | #include <wtf/text/StringHash.h> | 
|  |  | 
|  | namespace JSC { | 
|  |  | 
|  | /* | 
|  | The overrides file defines function bodies that we will want to override with | 
|  | a replacement for debugging purposes. The overrides file may contain | 
|  | 'override' and 'with' clauses like these: | 
|  |  | 
|  | // Example 1: function foo1(a) | 
|  | override !@#$%{ print("In foo1"); }!@#$% | 
|  | with abc{ | 
|  | print("I am overridden"); | 
|  | }abc | 
|  |  | 
|  | // Example 2: function foo2(a) | 
|  | override %%%{ | 
|  | print("foo2's body has a string with }%% in it."); | 
|  | // Because }%% appears in the function body here, we cannot use | 
|  | // %% or % as the delimiter. %%% is ok though. | 
|  | }%%% | 
|  | with %%%{ | 
|  | print("Overridden foo2"); | 
|  | }%%% | 
|  |  | 
|  | 1. Comments are lines starting with //.  All comments will be ignored. | 
|  |  | 
|  | 2. An 'override' clause is used to specify the original function body we | 
|  | want to override. The with clause is used to specify the overriding | 
|  | function body. | 
|  |  | 
|  | An 'override' clause must be followed immediately by a 'with' clause. | 
|  |  | 
|  | 3. An 'override' clause must be of the form: | 
|  | override <delimiter>{...function body...}<delimiter> | 
|  |  | 
|  | The override keyword must be at the start of the line. | 
|  |  | 
|  | <delimiter> may be any string of any ASCII characters (except for '{', | 
|  | '}', and whitespace characters) as long as the pattern of "}<delimiter>" | 
|  | does not appear in the function body e.g. the override clause of Example 2 | 
|  | above illustrates this. | 
|  |  | 
|  | The start and end <delimiter> must be identical. | 
|  |  | 
|  | The space between the override keyword and the start <delimiter> is | 
|  | required. | 
|  |  | 
|  | All characters between the pair of delimiters will be considered to | 
|  | be part of the function body string. This allows us to also work | 
|  | with script source that are multi-lined i.e. newlines are allowed. | 
|  |  | 
|  | 4. A 'with' clause is identical in form to an 'override' clause except that | 
|  | it uses the 'with' keyword instead of the 'override' keyword. | 
|  | */ | 
|  |  | 
|  | FunctionOverrides& FunctionOverrides::overrides() | 
|  | { | 
|  | static LazyNeverDestroyed<FunctionOverrides> overrides; | 
|  | static std::once_flag initializeListFlag; | 
|  | std::call_once(initializeListFlag, [] { | 
|  | const char* overridesFileName = Options::functionOverrides(); | 
|  | overrides.construct(overridesFileName); | 
|  | }); | 
|  | return overrides; | 
|  | } | 
|  |  | 
|  | FunctionOverrides::FunctionOverrides(const char* overridesFileName) | 
|  | { | 
|  | parseOverridesInFile(overridesFileName); | 
|  | } | 
|  |  | 
|  | void FunctionOverrides::reinstallOverrides() | 
|  | { | 
|  | FunctionOverrides& overrides = FunctionOverrides::overrides(); | 
|  | const char* overridesFileName = Options::functionOverrides(); | 
|  | overrides.clear(); | 
|  | overrides.parseOverridesInFile(overridesFileName); | 
|  | } | 
|  |  | 
|  | static void initializeOverrideInfo(const SourceCode& origCode, const String& newBody, FunctionOverrides::OverrideInfo& info) | 
|  | { | 
|  | String origProviderStr = origCode.provider()->source().toString(); | 
|  | unsigned origStart = origCode.startOffset(); | 
|  | unsigned origFunctionStart = origProviderStr.reverseFind("function", origStart); | 
|  | unsigned origBraceStart = origProviderStr.find("{", origStart); | 
|  | unsigned headerLength = origBraceStart - origFunctionStart; | 
|  | String origHeader = origProviderStr.substring(origFunctionStart, headerLength); | 
|  |  | 
|  | String newProviderStr; | 
|  | newProviderStr.append(origHeader); | 
|  | newProviderStr.append(newBody); | 
|  |  | 
|  | Ref<SourceProvider> newProvider = StringSourceProvider::create(newProviderStr, SourceOrigin { "<overridden>" }, URL({ }, "<overridden>")); | 
|  |  | 
|  | info.firstLine = 1; | 
|  | info.lineCount = 1; // Faking it. This doesn't really matter for now. | 
|  | info.startColumn = 1; | 
|  | info.endColumn = 1; // Faking it. This doesn't really matter for now. | 
|  | info.parametersStartOffset = newProviderStr.find("("); | 
|  | info.typeProfilingStartOffset = newProviderStr.find("{"); | 
|  | info.typeProfilingEndOffset = newProviderStr.length() - 1; | 
|  |  | 
|  | info.sourceCode = | 
|  | SourceCode(WTFMove(newProvider), info.parametersStartOffset, info.typeProfilingEndOffset + 1, 1, 1); | 
|  | } | 
|  |  | 
|  | bool FunctionOverrides::initializeOverrideFor(const SourceCode& origCode, FunctionOverrides::OverrideInfo& result) | 
|  | { | 
|  | ASSERT(Options::functionOverrides()); | 
|  | FunctionOverrides& overrides = FunctionOverrides::overrides(); | 
|  |  | 
|  | String sourceString = origCode.view().toString(); | 
|  | size_t sourceBodyStart = sourceString.find('{'); | 
|  | if (sourceBodyStart == notFound) | 
|  | return false; | 
|  | String sourceBodyString = sourceString.substring(sourceBodyStart); | 
|  |  | 
|  | auto it = overrides.m_entries.find(sourceBodyString); | 
|  | if (it == overrides.m_entries.end()) | 
|  | return false; | 
|  |  | 
|  | initializeOverrideInfo(origCode, it->value, result); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | #define SYNTAX_ERROR "SYNTAX ERROR" | 
|  | #define IO_ERROR "IO ERROR" | 
|  | #define FAIL_WITH_ERROR(error, errorMessageInBrackets) \ | 
|  | do { \ | 
|  | dataLog("functionOverrides ", error, ": "); \ | 
|  | dataLog errorMessageInBrackets; \ | 
|  | exit(EXIT_FAILURE); \ | 
|  | } while (false) | 
|  |  | 
|  | static bool hasDisallowedCharacters(const char* str, size_t length) | 
|  | { | 
|  | while (length--) { | 
|  | char c = *str++; | 
|  | // '{' is also disallowed, but we don't need to check for it because | 
|  | // parseClause() searches for '{' as the end of the start delimiter. | 
|  | // As a result, the parsed delimiter string will never include '{'. | 
|  | if (c == '}' || isASCIISpace(c)) | 
|  | return true; | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | static String parseClause(const char* keyword, size_t keywordLength, FILE* file, const char* line, char* buffer, size_t bufferSize) | 
|  | { | 
|  | const char* keywordPos = strstr(line, keyword); | 
|  | if (!keywordPos) | 
|  | FAIL_WITH_ERROR(SYNTAX_ERROR, ("Expecting '", keyword, "' clause:\n", line, "\n")); | 
|  | if (keywordPos != line) | 
|  | FAIL_WITH_ERROR(SYNTAX_ERROR, ("Cannot have any characters before '", keyword, "':\n", line, "\n")); | 
|  | if (line[keywordLength] != ' ') | 
|  | FAIL_WITH_ERROR(SYNTAX_ERROR, ("'", keyword, "' must be followed by a ' ':\n", line, "\n")); | 
|  |  | 
|  | const char* delimiterStart = &line[keywordLength + 1]; | 
|  | const char* delimiterEnd = strstr(delimiterStart, "{"); | 
|  | if (!delimiterEnd) | 
|  | FAIL_WITH_ERROR(SYNTAX_ERROR, ("Missing { after '", keyword, "' clause start delimiter:\n", line, "\n")); | 
|  |  | 
|  | size_t delimiterLength = delimiterEnd - delimiterStart; | 
|  | String delimiter(delimiterStart, delimiterLength); | 
|  |  | 
|  | if (hasDisallowedCharacters(delimiterStart, delimiterLength)) | 
|  | FAIL_WITH_ERROR(SYNTAX_ERROR, ("Delimiter '", delimiter, "' cannot have '{', '}', or whitespace:\n", line, "\n")); | 
|  |  | 
|  | String terminatorString; | 
|  | terminatorString.append('}'); | 
|  | terminatorString.append(delimiter); | 
|  |  | 
|  | CString terminatorCString = terminatorString.ascii(); | 
|  | const char* terminator = terminatorCString.data(); | 
|  | line = delimiterEnd; // Start from the {. | 
|  |  | 
|  | StringBuilder builder; | 
|  | do { | 
|  | const char* p = strstr(line, terminator); | 
|  | if (p) { | 
|  | if (p[strlen(terminator)] != '\n') | 
|  | FAIL_WITH_ERROR(SYNTAX_ERROR, ("Unexpected characters after '", keyword, "' clause end delimiter '", delimiter, "':\n", line, "\n")); | 
|  |  | 
|  | builder.appendCharacters(line, p - line + 1); | 
|  | return builder.toString(); | 
|  | } | 
|  | builder.append(line); | 
|  |  | 
|  | } while ((line = fgets(buffer, bufferSize, file))); | 
|  |  | 
|  | FAIL_WITH_ERROR(SYNTAX_ERROR, ("'", keyword, "' clause end delimiter '", delimiter, "' not found:\n", builder.toString(), "\n", "Are you missing a '}' before the delimiter?\n")); | 
|  | } | 
|  |  | 
|  | void FunctionOverrides::parseOverridesInFile(const char* fileName) | 
|  | { | 
|  | if (!fileName) | 
|  | return; | 
|  |  | 
|  | FILE* file = fopen(fileName, "r"); | 
|  | if (!file) | 
|  | FAIL_WITH_ERROR(IO_ERROR, ("Failed to open file ", fileName, ". Did you add the file-read-data entitlement to WebProcess.sb?\n")); | 
|  |  | 
|  | char* line; | 
|  | char buffer[BUFSIZ]; | 
|  | while ((line = fgets(buffer, sizeof(buffer), file))) { | 
|  | if (strstr(line, "//") == line) | 
|  | continue; | 
|  |  | 
|  | if (line[0] == '\n' || line[0] == '\0') | 
|  | continue; | 
|  |  | 
|  | size_t keywordLength; | 
|  |  | 
|  | keywordLength = sizeof("override") - 1; | 
|  | String keyStr = parseClause("override", keywordLength, file, line, buffer, sizeof(buffer)); | 
|  |  | 
|  | line = fgets(buffer, sizeof(buffer), file); | 
|  |  | 
|  | keywordLength = sizeof("with") - 1; | 
|  | String valueStr = parseClause("with", keywordLength, file, line, buffer, sizeof(buffer)); | 
|  |  | 
|  | m_entries.add(keyStr, valueStr); | 
|  | } | 
|  |  | 
|  | int result = fclose(file); | 
|  | if (result) | 
|  | dataLogF("Failed to close file %s: %s\n", fileName, strerror(errno)); | 
|  | } | 
|  |  | 
|  | } // namespace JSC | 
|  |  |