1/*
2 * Copyright (C) 2015-2016 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
38namespace 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
92FunctionOverrides& FunctionOverrides::overrides()
93{
94 static LazyNeverDestroyed<FunctionOverrides> overrides;
95 static std::once_flag initializeListFlag;
96 std::call_once(initializeListFlag, [] {
97 const char* overridesFileName = Options::functionOverrides();
98 overrides.construct(overridesFileName);
99 });
100 return overrides;
101}
102
103FunctionOverrides::FunctionOverrides(const char* overridesFileName)
104{
105 parseOverridesInFile(overridesFileName);
106}
107
108void FunctionOverrides::reinstallOverrides()
109{
110 FunctionOverrides& overrides = FunctionOverrides::overrides();
111 const char* overridesFileName = Options::functionOverrides();
112 overrides.clear();
113 overrides.parseOverridesInFile(overridesFileName);
114}
115
116static void initializeOverrideInfo(const SourceCode& origCode, const String& newBody, FunctionOverrides::OverrideInfo& info)
117{
118 String origProviderStr = origCode.provider()->source().toString();
119 unsigned origStart = origCode.startOffset();
120 unsigned origFunctionStart = origProviderStr.reverseFind("function", origStart);
121 unsigned origBraceStart = origProviderStr.find("{", origStart);
122 unsigned headerLength = origBraceStart - origFunctionStart;
123 String origHeader = origProviderStr.substring(origFunctionStart, headerLength);
124
125 String newProviderStr;
126 newProviderStr.append(origHeader);
127 newProviderStr.append(newBody);
128
129 Ref<SourceProvider> newProvider = StringSourceProvider::create(newProviderStr, SourceOrigin { "<overridden>" }, URL({ }, "<overridden>"));
130
131 info.firstLine = 1;
132 info.lineCount = 1; // Faking it. This doesn't really matter for now.
133 info.startColumn = 1;
134 info.endColumn = 1; // Faking it. This doesn't really matter for now.
135 info.parametersStartOffset = newProviderStr.find("(");
136 info.typeProfilingStartOffset = newProviderStr.find("{");
137 info.typeProfilingEndOffset = newProviderStr.length() - 1;
138
139 info.sourceCode =
140 SourceCode(WTFMove(newProvider), info.parametersStartOffset, info.typeProfilingEndOffset + 1, 1, 1);
141}
142
143bool FunctionOverrides::initializeOverrideFor(const SourceCode& origCode, FunctionOverrides::OverrideInfo& result)
144{
145 ASSERT(Options::functionOverrides());
146 FunctionOverrides& overrides = FunctionOverrides::overrides();
147
148 String sourceString = origCode.view().toString();
149 size_t sourceBodyStart = sourceString.find('{');
150 if (sourceBodyStart == notFound)
151 return false;
152 String sourceBodyString = sourceString.substring(sourceBodyStart);
153
154 auto it = overrides.m_entries.find(sourceBodyString);
155 if (it == overrides.m_entries.end())
156 return false;
157
158 initializeOverrideInfo(origCode, it->value, result);
159 return true;
160}
161
162#define SYNTAX_ERROR "SYNTAX ERROR"
163#define IO_ERROR "IO ERROR"
164#define FAIL_WITH_ERROR(error, errorMessageInBrackets) \
165 do { \
166 dataLog("functionOverrides ", error, ": "); \
167 dataLog errorMessageInBrackets; \
168 exit(EXIT_FAILURE); \
169 } while (false)
170
171static bool hasDisallowedCharacters(const char* str, size_t length)
172{
173 while (length--) {
174 char c = *str++;
175 // '{' is also disallowed, but we don't need to check for it because
176 // parseClause() searches for '{' as the end of the start delimiter.
177 // As a result, the parsed delimiter string will never include '{'.
178 if (c == '}' || isASCIISpace(c))
179 return true;
180 }
181 return false;
182}
183
184static String parseClause(const char* keyword, size_t keywordLength, FILE* file, const char* line, char* buffer, size_t bufferSize)
185{
186 const char* keywordPos = strstr(line, keyword);
187 if (!keywordPos)
188 FAIL_WITH_ERROR(SYNTAX_ERROR, ("Expecting '", keyword, "' clause:\n", line, "\n"));
189 if (keywordPos != line)
190 FAIL_WITH_ERROR(SYNTAX_ERROR, ("Cannot have any characters before '", keyword, "':\n", line, "\n"));
191 if (line[keywordLength] != ' ')
192 FAIL_WITH_ERROR(SYNTAX_ERROR, ("'", keyword, "' must be followed by a ' ':\n", line, "\n"));
193
194 const char* delimiterStart = &line[keywordLength + 1];
195 const char* delimiterEnd = strstr(delimiterStart, "{");
196 if (!delimiterEnd)
197 FAIL_WITH_ERROR(SYNTAX_ERROR, ("Missing { after '", keyword, "' clause start delimiter:\n", line, "\n"));
198
199 size_t delimiterLength = delimiterEnd - delimiterStart;
200 String delimiter(delimiterStart, delimiterLength);
201
202 if (hasDisallowedCharacters(delimiterStart, delimiterLength))
203 FAIL_WITH_ERROR(SYNTAX_ERROR, ("Delimiter '", delimiter, "' cannot have '{', '}', or whitespace:\n", line, "\n"));
204
205 String terminatorString;
206 terminatorString.append('}');
207 terminatorString.append(delimiter);
208
209 CString terminatorCString = terminatorString.ascii();
210 const char* terminator = terminatorCString.data();
211 line = delimiterEnd; // Start from the {.
212
213 StringBuilder builder;
214 do {
215 const char* p = strstr(line, terminator);
216 if (p) {
217 if (p[strlen(terminator)] != '\n')
218 FAIL_WITH_ERROR(SYNTAX_ERROR, ("Unexpected characters after '", keyword, "' clause end delimiter '", delimiter, "':\n", line, "\n"));
219
220 builder.append(line, p - line + 1);
221 return builder.toString();
222 }
223 builder.append(line);
224
225 } while ((line = fgets(buffer, bufferSize, file)));
226
227 FAIL_WITH_ERROR(SYNTAX_ERROR, ("'", keyword, "' clause end delimiter '", delimiter, "' not found:\n", builder.toString(), "\n", "Are you missing a '}' before the delimiter?\n"));
228}
229
230void FunctionOverrides::parseOverridesInFile(const char* fileName)
231{
232 if (!fileName)
233 return;
234
235 FILE* file = fopen(fileName, "r");
236 if (!file)
237 FAIL_WITH_ERROR(IO_ERROR, ("Failed to open file ", fileName, ". Did you add the file-read-data entitlement to WebProcess.sb?\n"));
238
239 char* line;
240 char buffer[BUFSIZ];
241 while ((line = fgets(buffer, sizeof(buffer), file))) {
242 if (strstr(line, "//") == line)
243 continue;
244
245 if (line[0] == '\n' || line[0] == '\0')
246 continue;
247
248 size_t keywordLength;
249
250 keywordLength = sizeof("override") - 1;
251 String keyStr = parseClause("override", keywordLength, file, line, buffer, sizeof(buffer));
252
253 line = fgets(buffer, sizeof(buffer), file);
254
255 keywordLength = sizeof("with") - 1;
256 String valueStr = parseClause("with", keywordLength, file, line, buffer, sizeof(buffer));
257
258 m_entries.add(keyStr, valueStr);
259 }
260
261 int result = fclose(file);
262 if (result)
263 dataLogF("Failed to close file %s: %s\n", fileName, strerror(errno));
264}
265
266} // namespace JSC
267
268