83e4f1ca4decd8a234bc1d040e65901c925b2b3c
[WebKit-https.git] / Source / JavaScriptCore / tools / FunctionOverrides.cpp
1 /*
2  * Copyright (C) 2015 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 <stdio.h>
30 #include <string.h>
31 #include <wtf/DataLog.h>
32 #include <wtf/NeverDestroyed.h>
33 #include <wtf/text/CString.h>
34 #include <wtf/text/StringBuilder.h>
35 #include <wtf/text/StringHash.h>
36
37 namespace JSC {
38
39 /*
40   The overrides file defines function bodies that we will want to override with
41   a replacement for debugging purposes. The overrides file may contain
42   'override' and 'with' clauses like these:
43
44      // Example 1: function foo1(a)
45      override !@#$%{ print("In foo1"); }!@#$%
46      with abc{
47          print("I am overridden");
48      }abc
49
50      // Example 2: function foo2(a)
51      override %%%{
52          print("foo2's body has a string with }%% in it.");
53          // Because }%% appears in the function body here, we cannot use
54          // %% or % as the delimiter. %%% is ok though.
55      }%%%
56      with %%%{
57          print("Overridden foo2");
58      }%%%
59
60   1. Comments are lines starting with //.  All comments will be ignored.
61
62   2. An 'override' clause is used to specify the original function body we
63      want to override. The with clause is used to specify the overriding
64      function body.
65
66      An 'override' clause must be followed immediately by a 'with' clause.
67
68   3. An 'override' clause must be of the form:
69          override <delimiter>{...function body...}<delimiter>
70
71      The override keyword must be at the start of the line.
72
73      <delimiter> may be any string of any ASCII characters (except for '{',
74      '}', and whitespace characters) as long as the pattern of "}<delimiter>"
75      does not appear in the function body e.g. the override clause of Example 2
76      above illustrates this.
77
78      The start and end <delimiter> must be identical.
79
80      The space between the override keyword and the start <delimiter> is
81      required.
82
83      All characters between the pair of delimiters will be considered to
84      be part of the function body string. This allows us to also work
85      with script source that are multi-lined i.e. newlines are allowed.
86      
87   4. A 'with' clause is identical in form to an 'override' clause except that
88      it uses the 'with' keyword instead of the 'override' keyword.
89  */
90
91 FunctionOverrides& FunctionOverrides::overrides()
92 {
93     static LazyNeverDestroyed<FunctionOverrides> overrides;
94     static std::once_flag initializeListFlag;
95     std::call_once(initializeListFlag, [] {
96         const char* overridesFileName = Options::functionOverrides();
97         overrides.construct(overridesFileName);
98     });
99     return overrides;
100 }
101     
102 FunctionOverrides::FunctionOverrides(const char* overridesFileName)
103 {
104     parseOverridesInFile(overridesFileName);
105 }
106
107 static void initializeOverrideInfo(const SourceCode& origCode, String newBody, FunctionOverrides::OverrideInfo& info)
108 {
109     String origProviderStr = origCode.provider()->source();
110     unsigned origBraceStart = origCode.startOffset();
111     unsigned origFunctionStart = origProviderStr.reverseFind("function", origBraceStart);
112     unsigned headerLength = origBraceStart - origFunctionStart;
113     String origHeader = origProviderStr.substring(origFunctionStart, headerLength);
114
115     String newProviderStr;
116     newProviderStr.append(origHeader);
117     newProviderStr.append(newBody);
118
119     RefPtr<SourceProvider> newProvider = StringSourceProvider::create(newProviderStr, "<overridden>");
120
121     info.firstLine = 1;
122     info.lineCount = 1; // Faking it. This doesn't really matter for now.
123     info.startColumn = 1;
124     info.endColumn = 1; // Faking it. This doesn't really matter for now.
125     info.parametersStartOffset = newProviderStr.find("(");
126     info.typeProfilingStartOffset = newProviderStr.find("{");
127     info.typeProfilingEndOffset = newProviderStr.length() - 1;
128
129     info.sourceCode =
130         SourceCode(newProvider, info.typeProfilingStartOffset, info.typeProfilingEndOffset + 1, 1, 1);
131 }
132     
133 bool FunctionOverrides::initializeOverrideFor(const SourceCode& origCode, FunctionOverrides::OverrideInfo& result)
134 {
135     ASSERT(Options::functionOverrides());
136     FunctionOverrides& overrides = FunctionOverrides::overrides();
137
138     auto it = overrides.m_entries.find(origCode.toString());
139     if (it == overrides.m_entries.end())
140         return false;
141
142     initializeOverrideInfo(origCode, it->value, result);
143     return true;
144 }
145
146 #define SYNTAX_ERROR "SYNTAX ERROR"
147 #define IO_ERROR "IO ERROR"
148 #define FAIL_WITH_ERROR(error, errorMessageInBrackets) \
149     do { \
150         dataLog("functionOverrides ", error, ": "); \
151         dataLog errorMessageInBrackets; \
152         exit(EXIT_FAILURE); \
153     } while (false)
154
155 static bool hasDisallowedCharacters(const char* str, size_t length)
156 {
157     while (length--) {
158         char c = *str++;
159         // '{' is also disallowed, but we don't need to check for it because
160         // parseClause() searches for '{' as the end of the start delimiter.
161         // As a result, the parsed delimiter string will never include '{'.
162         if (c == '}' || isASCIISpace(c))
163             return true;
164     }
165     return false;
166 }
167
168 static String parseClause(const char* keyword, size_t keywordLength, FILE* file, const char* line, char* buffer, size_t bufferSize)
169 {
170     const char* keywordPos = strstr(line, keyword);
171     if (!keywordPos)
172         FAIL_WITH_ERROR(SYNTAX_ERROR, ("Expecting '", keyword, "' clause:\n", line, "\n"));
173     if (keywordPos != line)
174         FAIL_WITH_ERROR(SYNTAX_ERROR, ("Cannot have any characters before '", keyword, "':\n", line, "\n"));
175     if (line[keywordLength] != ' ')
176         FAIL_WITH_ERROR(SYNTAX_ERROR, ("'", keyword, "' must be followed by a ' ':\n", line, "\n"));
177
178     const char* delimiterStart = &line[keywordLength + 1];
179     const char* delimiterEnd = strstr(delimiterStart, "{");
180     if (!delimiterEnd)
181         FAIL_WITH_ERROR(SYNTAX_ERROR, ("Missing { after '", keyword, "' clause start delimiter:\n", line, "\n"));
182     
183     size_t delimiterLength = delimiterEnd - delimiterStart;
184     String delimiter(delimiterStart, delimiterLength);
185
186     if (hasDisallowedCharacters(delimiterStart, delimiterLength))
187         FAIL_WITH_ERROR(SYNTAX_ERROR, ("Delimiter '", delimiter, "' cannot have '{', '}', or whitespace:\n", line, "\n"));
188     
189     String terminatorString;
190     terminatorString.append("}");
191     terminatorString.append(delimiter);
192
193     const char* terminator = terminatorString.ascii().data();
194     line = delimiterEnd; // Start from the {.
195
196     StringBuilder builder;
197     do {
198         const char* p = strstr(line, terminator);
199         if (p) {
200             if (p[strlen(terminator)] != '\n')
201                 FAIL_WITH_ERROR(SYNTAX_ERROR, ("Unexpected characters after '", keyword, "' clause end delimiter '", delimiter, "':\n", line, "\n"));
202
203             builder.append(line, p - line + 1);
204             return builder.toString();
205         }
206         builder.append(line);
207
208     } while ((line = fgets(buffer, bufferSize, file)));
209
210     FAIL_WITH_ERROR(SYNTAX_ERROR, ("'", keyword, "' clause end delimiter '", delimiter, "' not found:\n", builder.toString(), "\n", "Are you missing a '}' before the delimiter?\n"));
211 }
212
213 void FunctionOverrides::parseOverridesInFile(const char* fileName)
214 {
215     if (!fileName)
216         return;
217     
218     FILE* file = fopen(fileName, "r");
219     if (!file)
220         FAIL_WITH_ERROR(IO_ERROR, ("Failed to open file ", fileName, ". Did you add the file-read-data entitlement to WebProcess.sb?\n"));
221
222     char* line;
223     char buffer[BUFSIZ];
224     while ((line = fgets(buffer, sizeof(buffer), file))) {
225         if (strstr(line, "//") == line)
226             continue;
227
228         if (line[0] == '\n' || line[0] == '\0')
229             continue;
230
231         size_t keywordLength;
232         
233         keywordLength = sizeof("override") - 1;
234         String keyStr = parseClause("override", keywordLength, file, line, buffer, sizeof(buffer));
235
236         line = fgets(buffer, sizeof(buffer), file);
237
238         keywordLength = sizeof("with") - 1;
239         String valueStr = parseClause("with", keywordLength, file, line, buffer, sizeof(buffer));
240
241         m_entries.add(keyStr, valueStr);
242     }
243     
244     int result = fclose(file);
245     if (result)
246         dataLogF("Failed to close file %s: %s\n", fileName, strerror(errno));
247 }
248     
249 } // namespace JSC
250