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