Ignore:
Timestamp:
Sep 10, 2015, 12:19:15 PM (10 years ago)
Author:
Yusuke Suzuki
Message:

Consider long module path name case in Windows
https://bugs.webkit.org/show_bug.cgi?id=148917

Reviewed by Alex Christensen.

The local file system module loader in the JSC shell manages the module files by the absolute path.
However, in Windows, _MAX_PATH is defined as 260. So if the path like the current working directory or the path to the module is long,
it will be truncated by the API and it fail to open the file.
In JSC tests in Apple Windows buildbot, since the current working directory is long enough, the tests failed.

This patch introduces the following 3 tweaks.

  1. When retrieving the current working path, we use GetCurrentDirectoryW instead of _getcwd. GetCurrentDirectoryW allows the long path while _getcwd automatically truncate the result by the _MAX_PATH.
  1. Before opening the module file, we prepend "
    ?\" to the path. It converts the local file path to the long UNC path which allows longer path names.
  1. Since Windows ASCII API accepts the characters in the current code page, we use the Unicode APIs like _wfopen instead.

And enable the once disabled module tests in Windows.

Since this functionality is the part of the JSC shell to run the module tests, it is now implemented in jsc.cpp.

  • jsc.cpp:

(stringFromUTF):
(jscSource):
(extractDirectoryName):
(currentWorkingDirectory):
(convertShebangToJSComment):
(fillBufferWithContentsOfFile):
(fetchScriptFromLocalFileSystem):
(fetchModuleFromLocalFileSystem):
(GlobalObject::moduleLoaderFetch):
(functionRun):
(functionLoad):
(functionReadFile):
(functionCheckSyntax):
(functionLoadModule):
(runWithScripts):
(runInteractive):

  • tests/modules.yaml:
File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/JavaScriptCore/jsc.cpp

    r189439 r189583  
    566566}
    567567
    568 static inline String stringFromUTF(const char* utf8)
    569 {
    570     return String::fromUTF8WithLatin1Fallback(utf8, strlen(utf8));
    571 }
    572 
    573 static inline SourceCode jscSource(const char* utf8, const String& filename)
     568template<typename Vector>
     569static inline String stringFromUTF(const Vector& utf8)
     570{
     571    return String::fromUTF8WithLatin1Fallback(utf8.data(), utf8.size());
     572}
     573
     574template<typename Vector>
     575static inline SourceCode jscSource(const Vector& utf8, const String& filename)
    574576{
    575577    String str = stringFromUTF(utf8);
     
    702704
    703705    static JSInternalPromise* moduleLoaderResolve(JSGlobalObject*, ExecState*, JSValue, JSValue);
    704 
    705     static JSInternalPromise* moduleLoaderFetch(JSGlobalObject* globalObject, ExecState* exec, JSValue key)
    706     {
    707         JSInternalPromiseDeferred* deferred = JSInternalPromiseDeferred::create(exec, globalObject);
    708         String moduleKey = key.toString(exec)->value(exec);
    709         if (exec->hadException()) {
    710             JSValue exception = exec->exception();
    711             exec->clearException();
    712             return deferred->reject(exec, exception);
    713         }
    714 
    715         // Here, now we consider moduleKey as the fileName.
    716         Vector<char> utf8;
    717         if (!fillBufferWithContentsOfFile(moduleKey, utf8))
    718             return deferred->reject(exec, createError(exec, makeString("Could not open file '", moduleKey, "'.")));
    719 
    720         return deferred->resolve(exec, jsString(exec, stringFromUTF(utf8.data())));
    721     }
     706    static JSInternalPromise* moduleLoaderFetch(JSGlobalObject*, ExecState*, JSValue);
    722707};
    723708
     
    775760    if (firstSeparatorPosition == lastSeparatorPosition)
    776761        directoryName.queryName = StringImpl::empty();
    777     else
    778         directoryName.queryName = absolutePathToFile.substring(firstSeparatorPosition + 1, lastSeparatorPosition); // Not include the separator.
     762    else {
     763        size_t queryStartPosition = firstSeparatorPosition + 1;
     764        size_t queryLength = lastSeparatorPosition - queryStartPosition; // Not include the last separator.
     765        directoryName.queryName = absolutePathToFile.substring(queryStartPosition, queryLength);
     766    }
    779767    return true;
    780768}
     
    783771{
    784772#if OS(WINDOWS)
    785     // https://msdn.microsoft.com/en-us/library/sf98bd4y.aspx
    786     char* buffer = nullptr;
    787     if (!(buffer = _getcwd(nullptr, 0)))
     773    // https://msdn.microsoft.com/en-us/library/windows/desktop/aa364934.aspx
     774    // https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247.aspx#maxpath
     775    // The _MAX_PATH in Windows is 260. If the path of the current working directory is longer than that, _getcwd truncates the result.
     776    // And other I/O functions taking a path name also truncate it. To avoid this situation,
     777    //
     778    // (1). When opening the file in Windows for modules, we always use the abosolute path and add "\\?\" prefix to the path name.
     779    // (2). When retrieving the current working directory, use GetCurrentDirectory instead of _getcwd.
     780    //
     781    // In the path utility functions inside the JSC shell, we does not handle the UNC and UNCW including the network host name.
     782    DWORD bufferLength = ::GetCurrentDirectoryW(0, nullptr);
     783    if (!bufferLength)
    788784        return false;
    789     String directoryString = String::fromUTF8(buffer);
    790     free(buffer);
     785    // In Windows, wchar_t is the UTF-16LE.
     786    // https://msdn.microsoft.com/en-us/library/dd374081.aspx
     787    // https://msdn.microsoft.com/en-us/library/windows/desktop/ff381407.aspx
     788    auto buffer = std::make_unique<wchar_t[]>(bufferLength);
     789    DWORD lengthNotIncludingNull = ::GetCurrentDirectoryW(bufferLength, buffer.get());
     790    static_assert(sizeof(wchar_t) == sizeof(UChar), "In Windows, both are UTF-16LE");
     791    String directoryString = String(reinterpret_cast<UChar*>(buffer.get()));
     792    // We don't support network path like \\host\share\<path name>.
     793    if (directoryString.startsWith("\\\\"))
     794        return false;
    791795#else
    792796    auto buffer = std::make_unique<char[]>(PATH_MAX);
     
    868872}
    869873
     874static void convertShebangToJSComment(Vector<char>& buffer)
     875{
     876    if (buffer.size() >= 2) {
     877        if (buffer[0] == '#' && buffer[1] == '!')
     878            buffer[0] = buffer[1] = '/';
     879    }
     880}
     881
     882static void fillBufferWithContentsOfFile(FILE* file, Vector<char>& buffer)
     883{
     884    fseek(file, 0, SEEK_END);
     885    size_t bufferCapacity = ftell(file);
     886    fseek(file, 0, SEEK_SET);
     887    buffer.resize(bufferCapacity);
     888    fread(buffer.data(), 1, bufferCapacity, file);
     889}
     890
     891static bool fillBufferWithContentsOfFile(const String& fileName, Vector<char>& buffer)
     892{
     893    FILE* f = fopen(fileName.utf8().data(), "r");
     894    if (!f) {
     895        fprintf(stderr, "Could not open file: %s\n", fileName.utf8().data());
     896        return false;
     897    }
     898
     899    fillBufferWithContentsOfFile(f, buffer);
     900    fclose(f);
     901
     902    return true;
     903}
     904
     905static bool fetchScriptFromLocalFileSystem(const String& fileName, Vector<char>& buffer)
     906{
     907    if (!fillBufferWithContentsOfFile(fileName, buffer))
     908        return false;
     909    convertShebangToJSComment(buffer);
     910    return true;
     911}
     912
     913static bool fetchModuleFromLocalFileSystem(const String& fileName, Vector<char>& buffer)
     914{
     915    // We assume that fileName is always an absolute path.
     916#if OS(WINDOWS)
     917    // https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247.aspx#maxpath
     918    // Use long UNC to pass the long path name to the Windows APIs.
     919    String longUNCPathName = WTF::makeString("\\\\?\\", fileName);
     920    static_assert(sizeof(wchar_t) == sizeof(UChar), "In Windows, both are UTF-16LE");
     921    auto utf16Vector = longUNCPathName.charactersWithNullTermination();
     922    FILE* f = _wfopen(reinterpret_cast<wchar_t*>(utf16Vector.data()), L"rb");
     923#else
     924    FILE* f = fopen(fileName.utf8().data(), "r");
     925#endif
     926    if (!f) {
     927        fprintf(stderr, "Could not open file: %s\n", fileName.utf8().data());
     928        return false;
     929    }
     930
     931    fillBufferWithContentsOfFile(f, buffer);
     932    convertShebangToJSComment(buffer);
     933    fclose(f);
     934
     935    return true;
     936}
     937
     938JSInternalPromise* GlobalObject::moduleLoaderFetch(JSGlobalObject* globalObject, ExecState* exec, JSValue key)
     939{
     940    JSInternalPromiseDeferred* deferred = JSInternalPromiseDeferred::create(exec, globalObject);
     941    String moduleKey = key.toString(exec)->value(exec);
     942    if (exec->hadException()) {
     943        JSValue exception = exec->exception();
     944        exec->clearException();
     945        return deferred->reject(exec, exception);
     946    }
     947
     948    // Here, now we consider moduleKey as the fileName.
     949    Vector<char> utf8;
     950    if (!fetchModuleFromLocalFileSystem(moduleKey, utf8))
     951        return deferred->reject(exec, createError(exec, makeString("Could not open file '", moduleKey, "'.")));
     952
     953    return deferred->resolve(exec, jsString(exec, stringFromUTF(utf8)));
     954}
     955
     956
    870957EncodedJSValue JSC_HOST_CALL functionPrint(ExecState* exec)
    871958{
     
    10771164    String fileName = exec->argument(0).toString(exec)->value(exec);
    10781165    Vector<char> script;
    1079     if (!fillBufferWithContentsOfFile(fileName, script))
     1166    if (!fetchScriptFromLocalFileSystem(fileName, script))
    10801167        return JSValue::encode(exec->vm().throwException(exec, createError(exec, ASCIILiteral("Could not open file."))));
    10811168
     
    10911178    StopWatch stopWatch;
    10921179    stopWatch.start();
    1093     evaluate(globalObject->globalExec(), jscSource(script.data(), fileName), JSValue(), exception);
     1180    evaluate(globalObject->globalExec(), jscSource(script, fileName), JSValue(), exception);
    10941181    stopWatch.stop();
    10951182
     
    11031190
    11041191EncodedJSValue JSC_HOST_CALL functionLoad(ExecState* exec)
     1192{
     1193    String fileName = exec->argument(0).toString(exec)->value(exec);
     1194    Vector<char> script;
     1195    if (!fetchScriptFromLocalFileSystem(fileName, script))
     1196        return JSValue::encode(exec->vm().throwException(exec, createError(exec, ASCIILiteral("Could not open file."))));
     1197
     1198    JSGlobalObject* globalObject = exec->lexicalGlobalObject();
     1199   
     1200    NakedPtr<Exception> evaluationException;
     1201    JSValue result = evaluate(globalObject->globalExec(), jscSource(script, fileName), JSValue(), evaluationException);
     1202    if (evaluationException)
     1203        exec->vm().throwException(exec, evaluationException);
     1204    return JSValue::encode(result);
     1205}
     1206
     1207EncodedJSValue JSC_HOST_CALL functionReadFile(ExecState* exec)
    11051208{
    11061209    String fileName = exec->argument(0).toString(exec)->value(exec);
     
    11091212        return JSValue::encode(exec->vm().throwException(exec, createError(exec, ASCIILiteral("Could not open file."))));
    11101213
    1111     JSGlobalObject* globalObject = exec->lexicalGlobalObject();
    1112    
    1113     NakedPtr<Exception> evaluationException;
    1114     JSValue result = evaluate(globalObject->globalExec(), jscSource(script.data(), fileName), JSValue(), evaluationException);
    1115     if (evaluationException)
    1116         exec->vm().throwException(exec, evaluationException);
    1117     return JSValue::encode(result);
    1118 }
    1119 
    1120 EncodedJSValue JSC_HOST_CALL functionReadFile(ExecState* exec)
     1214    return JSValue::encode(jsString(exec, stringFromUTF(script)));
     1215}
     1216
     1217EncodedJSValue JSC_HOST_CALL functionCheckSyntax(ExecState* exec)
    11211218{
    11221219    String fileName = exec->argument(0).toString(exec)->value(exec);
    11231220    Vector<char> script;
    1124     if (!fillBufferWithContentsOfFile(fileName, script))
    1125         return JSValue::encode(exec->vm().throwException(exec, createError(exec, ASCIILiteral("Could not open file."))));
    1126 
    1127     return JSValue::encode(jsString(exec, stringFromUTF(script.data())));
    1128 }
    1129 
    1130 EncodedJSValue JSC_HOST_CALL functionCheckSyntax(ExecState* exec)
    1131 {
    1132     String fileName = exec->argument(0).toString(exec)->value(exec);
    1133     Vector<char> script;
    1134     if (!fillBufferWithContentsOfFile(fileName, script))
     1221    if (!fetchScriptFromLocalFileSystem(fileName, script))
    11351222        return JSValue::encode(exec->vm().throwException(exec, createError(exec, ASCIILiteral("Could not open file."))));
    11361223
     
    11411228
    11421229    JSValue syntaxException;
    1143     bool validSyntax = checkSyntax(globalObject->globalExec(), jscSource(script.data(), fileName), &syntaxException);
     1230    bool validSyntax = checkSyntax(globalObject->globalExec(), jscSource(script, fileName), &syntaxException);
    11441231    stopWatch.stop();
    11451232
     
    13771464    String fileName = exec->argument(0).toString(exec)->value(exec);
    13781465    Vector<char> script;
    1379     if (!fillBufferWithContentsOfFile(fileName, script))
     1466    if (!fetchScriptFromLocalFileSystem(fileName, script))
    13801467        return JSValue::encode(exec->vm().throwException(exec, createError(exec, ASCIILiteral("Could not open file."))));
    13811468
     
    15291616static bool runWithScripts(GlobalObject* globalObject, const Vector<Script>& scripts, bool dump, bool module)
    15301617{
    1531     const char* script;
    15321618    String fileName;
    15331619    Vector<char> scriptBuffer;
     
    15561642                promise = evaluateModule(globalObject->globalExec(), fileName);
    15571643            else {
    1558                 if (!fillBufferWithContentsOfFile(fileName, scriptBuffer))
     1644                if (!fetchScriptFromLocalFileSystem(fileName, scriptBuffer))
    15591645                    return false; // fail early so we can catch missing files
    1560                 script = scriptBuffer.data();
    15611646            }
    15621647        } else {
    1563             script = scripts[i].argument;
     1648            size_t commandLineLength = strlen(scripts[i].argument);
     1649            scriptBuffer.resize(commandLineLength);
     1650            std::copy(scripts[i].argument, scripts[i].argument + commandLineLength, scriptBuffer.begin());
    15641651            fileName = ASCIILiteral("[Command Line]");
    15651652        }
     
    15691656        if (module) {
    15701657            if (!promise)
    1571                 promise = evaluateModule(globalObject->globalExec(), jscSource(script, fileName));
     1658                promise = evaluateModule(globalObject->globalExec(), jscSource(scriptBuffer, fileName));
    15721659            globalObject->globalExec()->clearException();
    15731660            promise->then(globalObject->globalExec(), nullptr, errorHandler);
     
    15751662        } else {
    15761663            NakedPtr<Exception> evaluationException;
    1577             JSValue returnValue = evaluate(globalObject->globalExec(), jscSource(script, fileName), JSValue(), evaluationException);
     1664            JSValue returnValue = evaluate(globalObject->globalExec(), jscSource(scriptBuffer, fileName), JSValue(), evaluationException);
    15781665            success = success && !evaluationException;
    15791666            if (dump && !evaluationException)
     
    16471734        if (line.isEmpty())
    16481735            break;
    1649         line.append('\0');
    16501736
    16511737        NakedPtr<Exception> evaluationException;
    1652         JSValue returnValue = evaluate(globalObject->globalExec(), jscSource(line.data(), interpreterName), JSValue(), evaluationException);
     1738        JSValue returnValue = evaluate(globalObject->globalExec(), jscSource(line, interpreterName), JSValue(), evaluationException);
    16531739#endif
    16541740        if (evaluationException)
     
    18401926}
    18411927
    1842 static bool fillBufferWithContentsOfFile(const String& fileName, Vector<char>& buffer)
    1843 {
    1844     FILE* f = fopen(fileName.utf8().data(), "r");
    1845     if (!f) {
    1846         fprintf(stderr, "Could not open file: %s\n", fileName.utf8().data());
    1847         return false;
    1848     }
    1849 
    1850     size_t bufferSize = 0;
    1851     size_t bufferCapacity = 1024;
    1852 
    1853     buffer.resize(bufferCapacity);
    1854 
    1855     while (!feof(f) && !ferror(f)) {
    1856         bufferSize += fread(buffer.data() + bufferSize, 1, bufferCapacity - bufferSize, f);
    1857         if (bufferSize == bufferCapacity) { // guarantees space for trailing '\0'
    1858             bufferCapacity *= 2;
    1859             buffer.resize(bufferCapacity);
    1860         }
    1861     }
    1862     fclose(f);
    1863     buffer[bufferSize] = '\0';
    1864 
    1865     if (buffer[0] == '#' && buffer[1] == '!')
    1866         buffer[0] = buffer[1] = '/';
    1867 
    1868     return true;
    1869 }
    1870 
    18711928#if OS(WINDOWS)
    18721929extern "C" __declspec(dllexport) int WINAPI dllLauncherEntryPoint(int argc, const char* argv[])
Note: See TracChangeset for help on using the changeset viewer.