Skip to content

Commit

Permalink
Add ability to mask URLs in Error.stack.
Browse files Browse the repository at this point in the history
https://bugs.webkit.org/show_bug.cgi?id=242278
rdar://81991245

Reviewed by Darin Adler and Yusuke Suzuki.

Have StackFrame's toString consult a new overrideSourceURL() method on VM::ClientData
that is implemented in WebCore's JSVMClientData that uses the new maskedURLForBindingsIfNeeded()
to mask certain URLs from the Error.stack string returned to JavaScript.

* Source/JavaScriptCore/inspector/ScriptCallStackFactory.cpp:
(Inspector::extractSourceInformationFromException): Pass vm to getLineColumnAndSource().
(Inspector::createScriptCallStackFromException): Pass vm to sourceURL().
* Source/JavaScriptCore/runtime/Error.cpp:
(JSC::getLineColumnAndSource): Add vm parameter and pass it to sourceURL().
(JSC::addErrorInfo): Pass vm to getLineColumnAndSource().
* Source/JavaScriptCore/runtime/Error.h:
* Source/JavaScriptCore/runtime/ErrorInstance.cpp:
(JSC::ErrorInstance::computeErrorInfo): Pass vm to getLineColumnAndSource().
* Source/JavaScriptCore/runtime/StackFrame.cpp:
(JSC::StackFrame::sourceURL const): Add vm parameter and call overrideSourceURL() on clientData.
(JSC::StackFrame::functionName const): Add some newlines.
(JSC::StackFrame::toString const): Pass vm to sourceURL().
* Source/JavaScriptCore/runtime/StackFrame.h:
(JSC::StackFrame::codeBlock const): Added.
* Source/JavaScriptCore/runtime/VM.h:
(JSC::VM::ClientData::overrideSourceURL const): Added.
* Source/WebCore/bindings/js/WebCoreJSClientData.cpp:
(WebCore::JSVMClientData::overrideSourceURL const): Added. Use maskedURLForBindingsIfNeeded() to mask sourceURLs.
* Source/WebCore/bindings/js/WebCoreJSClientData.h:
* Tools/TestWebKitAPI/Tests/WebKitCocoa/WKWebViewConfiguration.mm:
(TEST(WebKit, ConfigurationMaskedURLSchemes)): Added user script tests with Error.stack.

Canonical link: https://commits.webkit.org/252253@main
  • Loading branch information
xeenon committed Jul 8, 2022
1 parent eb491a8 commit 2aac2a8
Show file tree
Hide file tree
Showing 10 changed files with 71 additions and 17 deletions.
12 changes: 6 additions & 6 deletions Source/JavaScriptCore/inspector/ScriptCallStackFactory.cpp
Expand Up @@ -127,7 +127,7 @@ static bool extractSourceInformationFromException(JSC::JSGlobalObject* globalObj
JSValue lineValue = exceptionObject->getDirect(vm, Identifier::fromString(vm, "line"_s));
JSValue columnValue = exceptionObject->getDirect(vm, Identifier::fromString(vm, "column"_s));
JSValue sourceURLValue = exceptionObject->getDirect(vm, Identifier::fromString(vm, "sourceURL"_s));

bool result = false;
if (lineValue && lineValue.isNumber()
&& sourceURLValue && sourceURLValue.isString()) {
Expand All @@ -138,14 +138,14 @@ static bool extractSourceInformationFromException(JSC::JSGlobalObject* globalObj
} else if (ErrorInstance* error = jsDynamicCast<ErrorInstance*>(exceptionObject)) {
unsigned unsignedLine;
unsigned unsignedColumn;
result = getLineColumnAndSource(error->stackTrace(), unsignedLine, unsignedColumn, *sourceURL);
result = getLineColumnAndSource(vm, error->stackTrace(), unsignedLine, unsignedColumn, *sourceURL);
*lineNumber = static_cast<int>(unsignedLine);
*columnNumber = static_cast<int>(unsignedColumn);
}

if (sourceURL->isEmpty())
*sourceURL = "undefined"_s;

scope.clearException();
return result;
}
Expand All @@ -160,7 +160,7 @@ Ref<ScriptCallStack> createScriptCallStackFromException(JSC::JSGlobalObject* glo
unsigned column;
stackTrace[i].computeLineAndColumn(line, column);
String functionName = stackTrace[i].functionName(vm);
frames.append(ScriptCallFrame(functionName, stackTrace[i].sourceURL(), stackTrace[i].sourceID(), line, column));
frames.append(ScriptCallFrame(functionName, stackTrace[i].sourceURL(vm), stackTrace[i].sourceID(), line, column));
}

// Fallback to getting at least the line and sourceURL from the exception object if it has values and the exceptionStack doesn't.
Expand All @@ -179,7 +179,7 @@ Ref<ScriptCallStack> createScriptCallStackFromException(JSC::JSGlobalObject* glo
// example - it uses firstNonNativeCallFrame). This looks like it splats something else
// over it. That something else is probably already at stackTrace[1].
// https://bugs.webkit.org/show_bug.cgi?id=176663
if (!stackTrace[0].hasLineAndColumnInfo() || stackTrace[0].sourceURL().isEmpty()) {
if (!stackTrace[0].hasLineAndColumnInfo() || stackTrace[0].sourceURL(vm).isEmpty()) {
const ScriptCallFrame& firstCallFrame = frames.first();
if (extractSourceInformationFromException(globalObject, exceptionObject, &lineNumber, &columnNumber, &exceptionSourceURL))
frames[0] = ScriptCallFrame(firstCallFrame.functionName(), exceptionSourceURL, stackTrace[0].sourceID(), lineNumber, columnNumber);
Expand Down
6 changes: 3 additions & 3 deletions Source/JavaScriptCore/runtime/Error.cpp
Expand Up @@ -176,7 +176,7 @@ void getBytecodeIndex(VM& vm, CallFrame* startCallFrame, Vector<StackFrame>* sta
bytecodeIndex = stackTrace->at(stackIndex).bytecodeIndex();
}

bool getLineColumnAndSource(Vector<StackFrame>* stackTrace, unsigned& line, unsigned& column, String& sourceURL)
bool getLineColumnAndSource(VM& vm, Vector<StackFrame>* stackTrace, unsigned& line, unsigned& column, String& sourceURL)
{
line = 0;
column = 0;
Expand All @@ -189,7 +189,7 @@ bool getLineColumnAndSource(Vector<StackFrame>* stackTrace, unsigned& line, unsi
StackFrame& frame = stackTrace->at(i);
if (frame.hasLineAndColumnInfo()) {
frame.computeLineAndColumn(line, column);
sourceURL = frame.sourceURL();
sourceURL = frame.sourceURL(vm);
return true;
}
}
Expand All @@ -206,7 +206,7 @@ bool addErrorInfo(VM& vm, Vector<StackFrame>* stackTrace, JSObject* obj)
unsigned line;
unsigned column;
String sourceURL;
getLineColumnAndSource(stackTrace, line, column, sourceURL);
getLineColumnAndSource(vm, stackTrace, line, column, sourceURL);
obj->putDirect(vm, vm.propertyNames->line, jsNumber(line));
obj->putDirect(vm, vm.propertyNames->column, jsNumber(column));
if (!sourceURL.isEmpty())
Expand Down
2 changes: 1 addition & 1 deletion Source/JavaScriptCore/runtime/Error.h
Expand Up @@ -68,7 +68,7 @@ JS_EXPORT_PRIVATE JSObject* createError(JSGlobalObject*, ErrorTypeWithExtension,

std::unique_ptr<Vector<StackFrame>> getStackTrace(JSGlobalObject*, VM&, JSObject*, bool useCurrentFrame);
void getBytecodeIndex(VM&, CallFrame*, Vector<StackFrame>*, CallFrame*&, BytecodeIndex&);
bool getLineColumnAndSource(Vector<StackFrame>* stackTrace, unsigned& line, unsigned& column, String& sourceURL);
bool getLineColumnAndSource(VM&, Vector<StackFrame>* stackTrace, unsigned& line, unsigned& column, String& sourceURL);
bool addErrorInfo(VM&, Vector<StackFrame>*, JSObject*);
JS_EXPORT_PRIVATE void addErrorInfo(JSGlobalObject*, JSObject*, bool);
JSObject* addErrorInfo(VM&, JSObject* error, int line, const SourceCode&);
Expand Down
2 changes: 1 addition & 1 deletion Source/JavaScriptCore/runtime/ErrorInstance.cpp
Expand Up @@ -236,7 +236,7 @@ void ErrorInstance::computeErrorInfo(VM& vm)
ASSERT(!m_errorInfoMaterialized);

if (m_stackTrace && !m_stackTrace->isEmpty()) {
getLineColumnAndSource(m_stackTrace.get(), m_line, m_column, m_sourceURL);
getLineColumnAndSource(vm, m_stackTrace.get(), m_line, m_column, m_sourceURL);
m_stackString = Interpreter::stackTraceAsString(vm, *m_stackTrace.get());
m_stackTrace = nullptr;
}
Expand Down
16 changes: 12 additions & 4 deletions Source/JavaScriptCore/runtime/StackFrame.cpp
Expand Up @@ -58,16 +58,22 @@ SourceID StackFrame::sourceID() const
return m_codeBlock->ownerExecutable()->sourceID();
}

String StackFrame::sourceURL() const
String StackFrame::sourceURL(VM& vm) const
{
if (m_isWasmFrame)
return "[wasm code]"_s;

if (!m_codeBlock) {
if (!m_codeBlock)
return "[native code]"_s;
}

String sourceURL = m_codeBlock->ownerExecutable()->sourceURL();

if (vm.clientData && !sourceURL.startsWithIgnoringASCIICase("http"_s)) {
String overrideURL = vm.clientData->overrideSourceURL(*this, sourceURL);
if (!overrideURL.isNull())
return overrideURL;
}

if (!sourceURL.isNull())
return sourceURL;
return emptyString();
Expand All @@ -92,11 +98,13 @@ String StackFrame::functionName(VM& vm) const
ASSERT_NOT_REACHED();
}
}

String name;
if (m_callee) {
if (m_callee->isObject())
name = getCalculatedDisplayName(vm, jsCast<JSObject*>(m_callee.get())).impl();
}

return name.isNull() ? emptyString() : name;
}

Expand All @@ -121,7 +129,7 @@ void StackFrame::computeLineAndColumn(unsigned& line, unsigned& column) const
String StackFrame::toString(VM& vm) const
{
String functionName = this->functionName(vm);
String sourceURL = this->sourceURL();
String sourceURL = this->sourceURL(vm);

if (sourceURL.isEmpty() || !hasLineAndColumnInfo())
return makeString(functionName, '@', sourceURL);
Expand Down
5 changes: 3 additions & 2 deletions Source/JavaScriptCore/runtime/StackFrame.h
Expand Up @@ -45,11 +45,12 @@ class StackFrame {
StackFrame(Wasm::IndexOrName);

bool hasLineAndColumnInfo() const { return !!m_codeBlock; }

CodeBlock* codeBlock() const { return m_codeBlock.get(); }

void computeLineAndColumn(unsigned& line, unsigned& column) const;
String functionName(VM&) const;
SourceID sourceID() const;
String sourceURL() const;
String sourceURL(VM&) const;
String toString(VM&) const;

bool hasBytecodeIndex() const { return m_bytecodeIndex && !m_isWasmFrame; }
Expand Down
3 changes: 3 additions & 0 deletions Source/JavaScriptCore/runtime/VM.h
Expand Up @@ -133,6 +133,7 @@ class ShadowChicken;
class SharedJITStubSet;
class SourceProvider;
class SourceProviderCache;
class StackFrame;
class Structure;
class Symbol;
class TypedArrayController;
Expand Down Expand Up @@ -255,6 +256,8 @@ class VM : public ThreadSafeRefCounted<VM>, public DoublyLinkedListNode<VM> {

struct ClientData {
JS_EXPORT_PRIVATE virtual ~ClientData() = 0;

JS_EXPORT_PRIVATE virtual String overrideSourceURL(const StackFrame&, const String& originalSourceURL) const = 0;
};

bool isSharedInstance() { return vmType == APIShared; }
Expand Down
19 changes: 19 additions & 0 deletions Source/WebCore/bindings/js/WebCoreJSClientData.cpp
Expand Up @@ -182,5 +182,24 @@ void JSVMClientData::initNormalWorld(VM* vm, WorkerThreadType type)
vm->m_typedArrayController = adoptRef(new WebCoreTypedArrayController(type == WorkerThreadType::DedicatedWorker || type == WorkerThreadType::Worklet));
}

String JSVMClientData::overrideSourceURL(const JSC::StackFrame& frame, const String& originalSourceURL) const
{
if (originalSourceURL.isEmpty())
return nullString();

auto* codeBlock = frame.codeBlock();
RELEASE_ASSERT(codeBlock);

auto* globalObject = codeBlock->globalObject();
if (!globalObject->inherits<JSDOMWindowBase>())
return nullString();

auto* document = jsCast<const JSDOMWindowBase*>(globalObject)->wrapped().document();
if (!document)
return nullString();

return document->maskedURLForBindingsIfNeeded(URL(originalSourceURL)).string();
}

} // namespace WebCore

2 changes: 2 additions & 0 deletions Source/WebCore/bindings/js/WebCoreJSClientData.h
Expand Up @@ -123,6 +123,8 @@ class JSVMClientData : public JSC::VM::ClientData {
m_worldSet.remove(&world);
}

virtual String overrideSourceURL(const JSC::StackFrame&, const String& originalSourceURL) const;

JSHeapData& heapData() { return *m_heapData; }

WebCoreBuiltinNames& builtinNames() { return m_builtinNames; }
Expand Down
21 changes: 21 additions & 0 deletions Tools/TestWebKitAPI/Tests/WebKitCocoa/WKWebViewConfiguration.mm
Expand Up @@ -30,6 +30,8 @@
#import "TestNavigationDelegate.h"
#import "TestProtocol.h"
#import "TestWKWebView.h"
#import <WebKit/WKUserContentControllerPrivate.h>
#import <WebKit/WKUserScriptPrivate.h>
#import <WebKit/WKWebView.h>
#import <WebKit/WKWebViewConfigurationPrivate.h>
#import <WebKit/WKWebsiteDataStorePrivate.h>
Expand Down Expand Up @@ -206,6 +208,9 @@ HTTPServer server([&] (Connection connection) {
[configuration _setMaskedURLSchemes:[NSSet setWithObjects:@"test-scheme", @"another-scheme", nil]];

auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 100, 100) configuration:configuration.get()]);
auto delegate = adoptNS([TestUIDelegate new]);
[webView setUIDelegate:delegate.get()];

[webView synchronouslyLoadHTMLString:@"<img src=\"test-scheme://foo.com/bar.jpg\"><img src=\"baz.png\">" baseURL:[NSURL URLWithString:@"https://example.com"]];

NSString *imageSource = [webView stringByEvaluatingJavaScript:@"document.querySelectorAll(\"img\")[0].src"];
Expand Down Expand Up @@ -264,4 +269,20 @@ HTTPServer server([&] (Connection connection) {

NSString *htmlSource = [webView stringByEvaluatingJavaScript:@"document.body.innerHTML"];
EXPECT_EQ([htmlSource containsString:@"<iframe src=\"webkit-masked-url://hidden/\"></iframe>"], YES);

[webView synchronouslyLoadHTMLString:@""];

NSURL *scriptURL = [NSURL URLWithString:@"another-scheme://foo.com/bar.js"];
auto userScript = adoptNS([[WKUserScript alloc] _initWithSource:@"alert((new Error).stack)" injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:YES includeMatchPatternStrings:@[] excludeMatchPatternStrings:@[] associatedURL:scriptURL contentWorld:nil deferRunningUntilNotification:NO]);

[[webView configuration].userContentController _addUserScriptImmediately:userScript.get()];

EXPECT_WK_STREQ([delegate waitForAlert], "global code@webkit-masked-url://hidden/:1:17");

scriptURL = [NSURL URLWithString:@"https://example.com/foo.js"];
userScript = adoptNS([[WKUserScript alloc] _initWithSource:@"alert((new Error).stack)" injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:YES includeMatchPatternStrings:@[] excludeMatchPatternStrings:@[] associatedURL:scriptURL contentWorld:nil deferRunningUntilNotification:NO]);

[[webView configuration].userContentController _addUserScriptImmediately:userScript.get()];

EXPECT_WK_STREQ([delegate waitForAlert], "global code@https://example.com/foo.js:1:17");
}

0 comments on commit 2aac2a8

Please sign in to comment.