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