mirror of
https://github.com/darlinghq/darling-JavaScriptCore.git
synced 2025-04-16 05:49:58 +00:00
540 lines
15 KiB
C++
540 lines
15 KiB
C++
/*
|
|
* Copyright (C) 2017 Apple Inc. All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
|
|
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
|
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
|
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include "config.h"
|
|
#include "ConfigFile.h"
|
|
|
|
#include "Options.h"
|
|
#include <limits.h>
|
|
#include <mutex>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <wtf/ASCIICType.h>
|
|
#include <wtf/DataLog.h>
|
|
#include <wtf/text/StringBuilder.h>
|
|
|
|
#if HAVE(REGEX_H)
|
|
#include <regex.h>
|
|
#endif
|
|
|
|
#if OS(UNIX)
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
namespace JSC {
|
|
|
|
static const size_t s_processNameMax = 128;
|
|
char ConfigFile::s_processName[s_processNameMax + 1] = { 0 };
|
|
char ConfigFile::s_parentProcessName[s_processNameMax + 1] = { 0 };
|
|
|
|
class ConfigFileScanner {
|
|
public:
|
|
ConfigFileScanner(const char* filename)
|
|
: m_filename(filename)
|
|
, m_lineNumber(0)
|
|
{
|
|
m_srcPtr = &m_buffer[0];
|
|
m_bufferEnd = &m_buffer[0];
|
|
}
|
|
|
|
bool start()
|
|
{
|
|
m_file = fopen(m_filename, "r");
|
|
if (!m_file) {
|
|
dataLogF("Failed to open file JSC Config file '%s'.\n", m_filename);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
unsigned lineNumber()
|
|
{
|
|
return m_lineNumber;
|
|
}
|
|
|
|
const char* currentBuffer()
|
|
{
|
|
if (!m_srcPtr || m_srcPtr == m_bufferEnd)
|
|
return "";
|
|
|
|
return m_srcPtr;
|
|
}
|
|
|
|
bool atFileEnd()
|
|
{
|
|
if (!fillBufferIfNeeded())
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool tryConsume(char c)
|
|
{
|
|
if (!fillBufferIfNeeded())
|
|
return false;
|
|
|
|
if (c == *m_srcPtr) {
|
|
m_srcPtr++;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
template <size_t length>
|
|
bool tryConsume(const char (&token) [length])
|
|
{
|
|
if (!fillBufferIfNeeded())
|
|
return false;
|
|
|
|
size_t tokenLength = length - 1;
|
|
if (!strncmp(m_srcPtr, token, tokenLength)) {
|
|
m_srcPtr += tokenLength;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
char* tryConsumeString()
|
|
{
|
|
if (!fillBufferIfNeeded())
|
|
return nullptr;
|
|
|
|
if (*m_srcPtr != '"')
|
|
return nullptr;
|
|
|
|
char* stringStart = ++m_srcPtr;
|
|
|
|
char* stringEnd = strchr(m_srcPtr, '"');
|
|
if (stringEnd) {
|
|
*stringEnd = '\0';
|
|
m_srcPtr = stringEnd + 1;
|
|
return stringStart;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
char* tryConsumeRegExPattern(bool& ignoreCase)
|
|
{
|
|
if (!fillBufferIfNeeded())
|
|
return nullptr;
|
|
|
|
if (*m_srcPtr != '/')
|
|
return nullptr;
|
|
|
|
char* stringStart = m_srcPtr + 1;
|
|
|
|
char* stringEnd = strchr(stringStart, '/');
|
|
if (stringEnd) {
|
|
*stringEnd = '\0';
|
|
m_srcPtr = stringEnd + 1;
|
|
if (*m_srcPtr == 'i') {
|
|
ignoreCase = true;
|
|
m_srcPtr++;
|
|
} else
|
|
ignoreCase = false;
|
|
|
|
return stringStart;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
char* tryConsumeUpto(bool& foundChar, char c)
|
|
{
|
|
if (!fillBufferIfNeeded())
|
|
return nullptr;
|
|
|
|
char* start = m_srcPtr;
|
|
foundChar = false;
|
|
|
|
char* cPosition = strchr(m_srcPtr, c);
|
|
if (cPosition) {
|
|
*cPosition = '\0';
|
|
m_srcPtr = cPosition + 1;
|
|
foundChar = true;
|
|
} else
|
|
m_srcPtr = m_bufferEnd;
|
|
|
|
return start;
|
|
}
|
|
|
|
private:
|
|
bool fillBufferIfNeeded()
|
|
{
|
|
if (!m_srcPtr)
|
|
return false;
|
|
|
|
while (true) {
|
|
while (m_srcPtr != m_bufferEnd && isASCIISpace(*m_srcPtr))
|
|
m_srcPtr++;
|
|
|
|
if (m_srcPtr != m_bufferEnd)
|
|
break;
|
|
|
|
if (!fillBuffer())
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool fillBuffer()
|
|
{
|
|
do {
|
|
m_srcPtr = fgets(m_buffer, sizeof(m_buffer), m_file);
|
|
if (!m_srcPtr) {
|
|
fclose(m_file);
|
|
return false;
|
|
}
|
|
|
|
m_lineNumber++;
|
|
|
|
m_bufferEnd = strchr(m_srcPtr, '#');
|
|
|
|
if (m_bufferEnd)
|
|
*m_bufferEnd = '\0';
|
|
else {
|
|
m_bufferEnd = m_srcPtr + strlen(m_srcPtr);
|
|
if (m_bufferEnd > m_srcPtr && m_bufferEnd[-1] == '\n') {
|
|
m_bufferEnd--;
|
|
*m_bufferEnd = '\0';
|
|
}
|
|
}
|
|
} while (m_bufferEnd == m_srcPtr);
|
|
|
|
return true;
|
|
}
|
|
|
|
const char* m_filename;
|
|
unsigned m_lineNumber;
|
|
FILE* m_file;
|
|
char m_buffer[BUFSIZ];
|
|
char* m_srcPtr;
|
|
char* m_bufferEnd;
|
|
};
|
|
|
|
ConfigFile::ConfigFile(const char* filename)
|
|
{
|
|
if (!filename)
|
|
m_filename[0] = '\0';
|
|
else {
|
|
strncpy(m_filename, filename, s_maxPathLength);
|
|
m_filename[s_maxPathLength] = '\0';
|
|
}
|
|
|
|
m_configDirectory[0] = '\0';
|
|
}
|
|
|
|
void ConfigFile::setProcessName(const char* processName)
|
|
{
|
|
strncpy(s_processName, processName, s_processNameMax);
|
|
}
|
|
|
|
void ConfigFile::setParentProcessName(const char* parentProcessName)
|
|
{
|
|
strncpy(s_parentProcessName, parentProcessName, s_processNameMax);
|
|
}
|
|
|
|
void ConfigFile::parse()
|
|
{
|
|
enum StatementNesting { TopLevelStatment, NestedStatement, NestedStatementFailedCriteria };
|
|
enum ParseResult { ParseOK, ParseError, NestedStatementDone };
|
|
|
|
canonicalizePaths();
|
|
|
|
ConfigFileScanner scanner(m_filename);
|
|
|
|
if (!scanner.start())
|
|
return;
|
|
|
|
char logPathname[s_maxPathLength + 1] = { 0 };
|
|
|
|
StringBuilder jscOptionsBuilder;
|
|
|
|
auto parseLogFile = [&](StatementNesting statementNesting) {
|
|
char* filename = nullptr;
|
|
if (scanner.tryConsume('=') && (filename = scanner.tryConsumeString())) {
|
|
if (statementNesting != NestedStatementFailedCriteria) {
|
|
if (filename[0] != '/') {
|
|
int spaceRequired = snprintf(logPathname, s_maxPathLength + 1, "%s/%s", m_configDirectory, filename);
|
|
if (static_cast<unsigned>(spaceRequired) > s_maxPathLength)
|
|
return ParseError;
|
|
} else
|
|
strncpy(logPathname, filename, s_maxPathLength);
|
|
}
|
|
|
|
return ParseOK;
|
|
}
|
|
|
|
return ParseError;
|
|
};
|
|
|
|
auto parseJSCOptions = [&](StatementNesting statementNesting) {
|
|
if (scanner.tryConsume('{')) {
|
|
StringBuilder builder;
|
|
|
|
bool foundClosingBrace = false;
|
|
char* currentLine = nullptr;
|
|
|
|
while ((currentLine = scanner.tryConsumeUpto(foundClosingBrace, '}'))) {
|
|
char* p = currentLine;
|
|
|
|
do {
|
|
if (foundClosingBrace && !*p)
|
|
break;
|
|
|
|
char* optionNameStart = p;
|
|
|
|
while (*p && !isASCIISpace(*p) && *p != '=')
|
|
p++;
|
|
|
|
builder.appendCharacters(optionNameStart, p - optionNameStart);
|
|
|
|
while (*p && isASCIISpace(*p) && *p != '=')
|
|
p++;
|
|
|
|
if (!*p)
|
|
return ParseError;
|
|
p++; // Advance past the '='
|
|
|
|
builder.append('=');
|
|
|
|
while (*p && isASCIISpace(*p))
|
|
p++;
|
|
|
|
if (!*p)
|
|
return ParseError;
|
|
|
|
char* optionValueStart = p;
|
|
|
|
while (*p && !isASCIISpace(*p))
|
|
p++;
|
|
|
|
builder.appendCharacters(optionValueStart, p - optionValueStart);
|
|
builder.append('\n');
|
|
|
|
while (*p && isASCIISpace(*p))
|
|
p++;
|
|
} while (*p);
|
|
|
|
if (foundClosingBrace)
|
|
break;
|
|
}
|
|
|
|
if (statementNesting != NestedStatementFailedCriteria)
|
|
jscOptionsBuilder.append(builder);
|
|
|
|
return ParseOK;
|
|
}
|
|
|
|
return ParseError;
|
|
};
|
|
|
|
auto parseNestedStatement = [&](StatementNesting statementNesting) {
|
|
if (scanner.tryConsume("jscOptions"))
|
|
return parseJSCOptions(statementNesting);
|
|
|
|
if (scanner.tryConsume("logFile"))
|
|
return parseLogFile(statementNesting);
|
|
|
|
if (scanner.tryConsume('}'))
|
|
return NestedStatementDone;
|
|
|
|
return ParseError;
|
|
};
|
|
|
|
auto parsePredicate = [&](bool& predicateMatches, const char* matchValue) {
|
|
if (scanner.tryConsume("==")) {
|
|
char* predicateValue = nullptr;
|
|
if ((predicateValue = scanner.tryConsumeString()) && matchValue) {
|
|
predicateMatches = !strcmp(predicateValue, matchValue);
|
|
return true;
|
|
}
|
|
}
|
|
#if HAVE(REGEX_H)
|
|
else if (scanner.tryConsume("=~")) {
|
|
char* predicateRegExString = nullptr;
|
|
bool ignoreCase { false };
|
|
if ((predicateRegExString = scanner.tryConsumeRegExPattern(ignoreCase)) && matchValue) {
|
|
regex_t predicateRegEx;
|
|
int regexFlags = REG_EXTENDED;
|
|
if (ignoreCase)
|
|
regexFlags |= REG_ICASE;
|
|
if (regcomp(&predicateRegEx, predicateRegExString, regexFlags))
|
|
return false;
|
|
|
|
predicateMatches = !regexec(&predicateRegEx, matchValue, 0, nullptr, 0);
|
|
return true;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return false;
|
|
};
|
|
|
|
auto parseConditionalBlock = [&](StatementNesting statementNesting) {
|
|
if (statementNesting == NestedStatement) {
|
|
StatementNesting subNesting = NestedStatement;
|
|
|
|
while (true) {
|
|
bool predicateMatches;
|
|
const char* actualValue = nullptr;
|
|
|
|
if (scanner.tryConsume("processName"))
|
|
actualValue = s_processName;
|
|
else if (scanner.tryConsume("parentProcessName"))
|
|
actualValue = s_parentProcessName;
|
|
else if (scanner.tryConsume("build"))
|
|
#ifndef NDEBUG
|
|
actualValue = "Debug";
|
|
#else
|
|
actualValue = "Release";
|
|
#endif
|
|
else
|
|
return ParseError;
|
|
|
|
if (parsePredicate(predicateMatches, actualValue)) {
|
|
if (!predicateMatches)
|
|
subNesting = NestedStatementFailedCriteria;
|
|
|
|
if (!scanner.tryConsume("&&"))
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!scanner.tryConsume('{'))
|
|
return ParseError;
|
|
|
|
ParseResult parseResult = ParseOK;
|
|
while (parseResult == ParseOK && !scanner.atFileEnd())
|
|
parseResult = parseNestedStatement(subNesting);
|
|
|
|
if (parseResult == NestedStatementDone)
|
|
return ParseOK;
|
|
}
|
|
|
|
return ParseError;
|
|
};
|
|
|
|
auto parseStatement = [&](StatementNesting statementNesting) {
|
|
if (scanner.tryConsume("jscOptions"))
|
|
return parseJSCOptions(statementNesting);
|
|
|
|
if (scanner.tryConsume("logFile"))
|
|
return parseLogFile(statementNesting);
|
|
|
|
if (statementNesting == TopLevelStatment)
|
|
return parseConditionalBlock(NestedStatement);
|
|
|
|
return ParseError;
|
|
};
|
|
|
|
ParseResult parseResult = ParseOK;
|
|
|
|
while (parseResult == ParseOK && !scanner.atFileEnd())
|
|
parseResult = parseStatement(TopLevelStatment);
|
|
|
|
if (parseResult == ParseOK) {
|
|
if (strlen(logPathname))
|
|
WTF::setDataFile(logPathname);
|
|
|
|
if (!jscOptionsBuilder.isEmpty()) {
|
|
Options::enableRestrictedOptions(true);
|
|
Options::setOptions(jscOptionsBuilder.toString().utf8().data());
|
|
}
|
|
} else
|
|
WTF::dataLogF("Error in JSC Config file on or near line %u, parsing '%s'\n", scanner.lineNumber(), scanner.currentBuffer());
|
|
}
|
|
|
|
void ConfigFile::canonicalizePaths()
|
|
{
|
|
if (!m_filename[0])
|
|
return;
|
|
|
|
#if OS(UNIX) || OS(DARWIN)
|
|
if (m_filename[0] != '/') {
|
|
// Relative path
|
|
char filenameBuffer[s_maxPathLength + 1];
|
|
|
|
if (getcwd(filenameBuffer, sizeof(filenameBuffer))) {
|
|
size_t pathnameLength = strlen(filenameBuffer);
|
|
bool shouldAddPathSeparator = filenameBuffer[pathnameLength - 1] != '/';
|
|
if (sizeof(filenameBuffer) - 1 >= pathnameLength + shouldAddPathSeparator) {
|
|
if (shouldAddPathSeparator)
|
|
strncat(filenameBuffer, "/", 2); // Room for '/' plus NUL
|
|
#if COMPILER(GCC)
|
|
#if GCC_VERSION_AT_LEAST(8, 0, 0)
|
|
IGNORE_WARNINGS_BEGIN("stringop-truncation")
|
|
#endif
|
|
#endif
|
|
strncat(filenameBuffer, m_filename, sizeof(filenameBuffer) - strlen(filenameBuffer) - 1);
|
|
#if COMPILER(GCC)
|
|
#if GCC_VERSION_AT_LEAST(8, 0, 0)
|
|
IGNORE_WARNINGS_END
|
|
#endif
|
|
#endif
|
|
strncpy(m_filename, filenameBuffer, s_maxPathLength);
|
|
m_filename[s_maxPathLength] = '\0';
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
char* lastPathSeperator = strrchr(m_filename, '/');
|
|
|
|
if (lastPathSeperator) {
|
|
unsigned dirnameLength = lastPathSeperator - &m_filename[0];
|
|
strncpy(m_configDirectory, m_filename, dirnameLength);
|
|
m_configDirectory[dirnameLength] = '\0';
|
|
} else {
|
|
m_configDirectory[0] = '/';
|
|
m_configDirectory[1] = '\0';
|
|
}
|
|
}
|
|
|
|
void processConfigFile(const char* configFilename, const char* processName, const char* parentProcessName)
|
|
{
|
|
static std::once_flag processConfigFileOnceFlag;
|
|
|
|
if (!configFilename || !strlen(configFilename))
|
|
return;
|
|
|
|
std::call_once(processConfigFileOnceFlag, [&]{
|
|
if (configFilename) {
|
|
ConfigFile configFile(configFilename);
|
|
configFile.setProcessName(processName);
|
|
if (parentProcessName)
|
|
configFile.setParentProcessName(parentProcessName);
|
|
configFile.parse();
|
|
}
|
|
});
|
|
}
|
|
|
|
} // namespace JSC
|