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
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
92struct FunctionOverridesAssertScope {
93 FunctionOverridesAssertScope() { RELEASE_ASSERT(g_jscConfig.restrictedOptionsEnabled); }
94 ~FunctionOverridesAssertScope() { RELEASE_ASSERT(g_jscConfig.restrictedOptionsEnabled); }
95};
96
97FunctionOverrides& 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
110FunctionOverrides::FunctionOverrides(const char* overridesFileName)
111{
112 FunctionOverridesAssertScope assertScope;
113 parseOverridesInFile(holdLock(m_lock), overridesFileName);
114}
115
116void 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
126static 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 Ref<SourceProvider> newProvider = StringSourceProvider::create(newProviderStr, SourceOrigin { "<overridden>" }, URL({ }, "<overridden>"));
141
142 info.firstLine = 1;
143 info.lineCount = 1; // Faking it. This doesn't really matter for now.
144 info.startColumn = 1;
145 info.endColumn = 1; // Faking it. This doesn't really matter for now.
146 info.parametersStartOffset = newProviderStr.find("(");
147 info.typeProfilingStartOffset = newProviderStr.find("{");
148 info.typeProfilingEndOffset = newProviderStr.length() - 1;
149
150 info.sourceCode =
151 SourceCode(WTFMove(newProvider), info.parametersStartOffset, info.typeProfilingEndOffset + 1, 1, 1);
152}
153
154bool FunctionOverrides::initializeOverrideFor(const SourceCode& origCode, FunctionOverrides::OverrideInfo& result)
155{
156 FunctionOverridesAssertScope assertScope;
157 RELEASE_ASSERT(Options::functionOverrides());
158 FunctionOverrides& overrides = FunctionOverrides::overrides();
159
160 String sourceString = origCode.view().toString();
161 size_t sourceBodyStart = sourceString.find('{');
162 if (sourceBodyStart == notFound)
163 return false;
164 String sourceBodyString = sourceString.substring(sourceBodyStart);
165
166 String newBody;
167 {
168 auto locker = holdLock(overrides.m_lock);
169 auto it = overrides.m_entries.find(sourceBodyString.isolatedCopy());
170 if (it == overrides.m_entries.end())
171 return false;
172 newBody = it->value.isolatedCopy();
173 }
174
175 initializeOverrideInfo(origCode, newBody, result);
176 RELEASE_ASSERT(Options::functionOverrides());
177 return true;
178}
179
180#define SYNTAX_ERROR "SYNTAX ERROR"
181#define IO_ERROR "IO ERROR"
182#define FAIL_WITH_ERROR(error, errorMessageInBrackets) \
183 do { \
184 dataLog("functionOverrides ", error, ": "); \
185 dataLog errorMessageInBrackets; \
186 exit(EXIT_FAILURE); \
187 } while (false)
188
189static bool hasDisallowedCharacters(const char* str, size_t length)
190{
191 while (length--) {
192 char c = *str++;
193 // '{' is also disallowed, but we don't need to check for it because
194 // parseClause() searches for '{' as the end of the start delimiter.
195 // As a result, the parsed delimiter string will never include '{'.
196 if (c == '}' || isASCIISpace(c))
197 return true;
198 }
199 return false;
200}
201
202static String parseClause(const char* keyword, size_t keywordLength, FILE* file, const char* line, char* buffer, size_t bufferSize)
203{
204 FunctionOverridesAssertScope assertScope;
205 const char* keywordPos = strstr(line, keyword);
206 if (!keywordPos)
207 FAIL_WITH_ERROR(SYNTAX_ERROR, ("Expecting '", keyword, "' clause:\n", line, "\n"));
208 if (keywordPos != line)
209 FAIL_WITH_ERROR(SYNTAX_ERROR, ("Cannot have any characters before '", keyword, "':\n", line, "\n"));
210 if (line[keywordLength] != ' ')
211 FAIL_WITH_ERROR(SYNTAX_ERROR, ("'", keyword, "' must be followed by a ' ':\n", line, "\n"));
212
213 const char* delimiterStart = &line[keywordLength + 1];
214 const char* delimiterEnd = strstr(delimiterStart, "{");
215 if (!delimiterEnd)
216 FAIL_WITH_ERROR(SYNTAX_ERROR, ("Missing { after '", keyword, "' clause start delimiter:\n", line, "\n"));
217
218 size_t delimiterLength = delimiterEnd - delimiterStart;
219 String delimiter(delimiterStart, delimiterLength);
220
221 if (hasDisallowedCharacters(delimiterStart, delimiterLength))
222 FAIL_WITH_ERROR(SYNTAX_ERROR, ("Delimiter '", delimiter, "' cannot have '{', '}', or whitespace:\n", line, "\n"));
223
224 String terminatorString;
225 terminatorString.append('}');
226 terminatorString.append(delimiter);
227
228 CString terminatorCString = terminatorString.ascii();
229 const char* terminator = terminatorCString.data();
230 line = delimiterEnd; // Start from the {.
231
232 StringBuilder builder;
233 do {
234 const char* p = strstr(line, terminator);
235 if (p) {
236 if (p[strlen(terminator)] != '\n')
237 FAIL_WITH_ERROR(SYNTAX_ERROR, ("Unexpected characters after '", keyword, "' clause end delimiter '", delimiter, "':\n", line, "\n"));
238
239 builder.appendCharacters(line, p - line + 1);
240 return builder.toString();
241 }
242 builder.append(line);
243
244 } while ((line = fgets(buffer, bufferSize, file)));
245
246 FAIL_WITH_ERROR(SYNTAX_ERROR, ("'", keyword, "' clause end delimiter '", delimiter, "' not found:\n", builder.toString(), "\n", "Are you missing a '}' before the delimiter?\n"));
247}
248
249void FunctionOverrides::parseOverridesInFile(const AbstractLocker&, const char* fileName)
250{
251 FunctionOverridesAssertScope assertScope;
252 if (!fileName)
253 return;
254
255 FILE* file = fopen(fileName, "r");
256 if (!file)
257 FAIL_WITH_ERROR(IO_ERROR, ("Failed to open file ", fileName, ". Did you add the file-read-data entitlement to WebProcess.sb?\n"));
258
259 char* line;
260 char buffer[BUFSIZ];
261 while ((line = fgets(buffer, sizeof(buffer), file))) {
262 if (strstr(line, "//") == line)
263 continue;
264
265 if (line[0] == '\n' || line[0] == '\0')
266 continue;
267
268 size_t keywordLength;
269
270 keywordLength = sizeof("override") - 1;
271 String keyStr = parseClause("override", keywordLength, file, line, buffer, sizeof(buffer));
272
273 line = fgets(buffer, sizeof(buffer), file);
274
275 keywordLength = sizeof("with") - 1;
276 String valueStr = parseClause("with", keywordLength, file, line, buffer, sizeof(buffer));
277
278 m_entries.add(keyStr, valueStr);
279 }
280
281 int result = fclose(file);
282 if (result)
283 dataLogF("Failed to close file %s: %s\n", fileName, strerror(errno));
284}
285
286} // namespace JSC
287
288