Skip to content

Commit

Permalink
Add HermesInternal.getFunctionLocation
Browse files Browse the repository at this point in the history
Summary: Adds a new internal method, `HermesInternal.getFunctionLocation(f)`, which returns an object describing the source location (debug info / bytecode offset) of the function `f`.

Reviewed By: jbower-fb

Differential Revision: D18856943

fbshipit-source-id: d520c63274c4b537b38dfdfe13ba42c9fee2d2bb
  • Loading branch information
motiz88 authored and facebook-github-bot committed Dec 10, 2019
1 parent 32c775d commit 7364f96
Show file tree
Hide file tree
Showing 5 changed files with 253 additions and 2 deletions.
4 changes: 3 additions & 1 deletion include/hermes/VM/NativeFunctions.def
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/

// NATIVE_FUNCTION_VERSION = 5. Updated Nov 22, 2019
// NATIVE_FUNCTION_VERSION = 6. Updated Dec 6, 2019
// Bump this version in SerializeHeader.h whenever we change this file.

#ifndef NATIVE_FUNCTION
Expand Down Expand Up @@ -170,6 +170,8 @@ NATIVE_FUNCTION(hermesInternalToString)
NATIVE_FUNCTION(hermesInternalTTIReached)
NATIVE_FUNCTION(hermesInternalTTRCReached)

NATIVE_FUNCTION(hermesInternalGetFunctionLocation)

#ifdef HERMES_ENABLE_DEBUGGER
NATIVE_FUNCTION(isDebuggerAttached)
NATIVE_FUNCTION(shouldPauseOnThrow)
Expand Down
7 changes: 7 additions & 0 deletions include/hermes/VM/PredefinedStrings.def
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,13 @@ STR(copyRestArgs, "copyRestArgs")
STR(arraySpread, "arraySpread")
STR(exportAll, "exportAll")
STR(exponentiationOperator, "exponentiationOperator")
STR(getFunctionLocation, "getFunctionLocation")
STR(isNative, "isNative")
STR(lineNumber, "lineNumber")
STR(columnNumber, "columnNumber")
STR(cjsModuleOffset, "cjsModuleOffset")
STR(virtualOffset, "virtualOffset")
STR(fileName, "fileName")

STR(require, "require")
STR(requireFast, "requireFast")
Expand Down
2 changes: 1 addition & 1 deletion include/hermes/VM/SerializeHeader.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ constexpr uint32_t SD_MAGIC = 0xad082463;
constexpr uint32_t SD_HEADER_VERSION = 1;

/// Bump this version number up whenever NativeFunctions.def is changed.
constexpr uint32_t NATIVE_FUNCTION_VERSION = 5;
constexpr uint32_t NATIVE_FUNCTION_VERSION = 6;

/// Serialize data header. Used to sanity check serialize data and make sure
/// that serializer and deserializer are consistent.
Expand Down
138 changes: 138 additions & 0 deletions lib/VM/JSLib/HermesInternal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -672,6 +672,142 @@ hermesInternalToString(void *, Runtime *runtime, NativeArgs args) {
}
#endif // HERMESVM_USE_JS_LIBRARY_IMPLEMENTATION

/// \return the code block associated with \p callableHandle if it is a
/// (possibly bound) JS function, or nullptr otherwise.
static const CodeBlock *getLeafCodeBlock(
Handle<Callable> callableHandle,
Runtime *runtime) {
const Callable *callable = callableHandle.get();
while (auto *bound = dyn_vmcast<BoundFunction>(callable)) {
callable = bound->getTarget(runtime);
}
if (auto *asFunction = dyn_vmcast<const JSFunction>(callable)) {
return asFunction->getCodeBlock();
}
return nullptr;
}

/// \return the file name associated with \p codeBlock, if any.
/// This mirrors the way we print file names for code blocks in JSError.
static CallResult<HermesValue> getCodeBlockFileName(
Runtime *runtime,
const CodeBlock *codeBlock,
OptValue<hbc::DebugSourceLocation> location) {
RuntimeModule *runtimeModule = codeBlock->getRuntimeModule();
if (location) {
auto debugInfo = runtimeModule->getBytecode()->getDebugInfo();
return StringPrimitive::createEfficient(
runtime, debugInfo->getFilenameByID(location->filenameId));
} else {
llvm::StringRef sourceURL = runtimeModule->getSourceURL();
if (!sourceURL.empty()) {
return StringPrimitive::createEfficient(runtime, sourceURL);
}
}
return HermesValue::encodeUndefinedValue();
}

/// \code
/// HermesInternal.getFunctionLocation function (func) {}
/// \endcode
/// Returns an object describing the source location of func.
/// The following properties may be present:
/// * fileName (string)
/// * lineNumber (number) - 1 based
/// * columnNumber (number) - 1 based
/// * cjsModuleOffset (number) - 0 based
/// * virtualOffset (number) - 0 based
/// * isNative (boolean)
/// TypeError if func is not a function.
CallResult<HermesValue>
hermesInternalGetFunctionLocation(void *, Runtime *runtime, NativeArgs args) {
GCScope gcScope(runtime);

auto callable = args.dyncastArg<Callable>(0);
if (!callable) {
return runtime->raiseTypeError(
"Argument to HermesInternal.getFunctionLocation must be callable");
}
auto resultHandle = toHandle(runtime, JSObject::create(runtime));
MutableHandle<> tmpHandle{runtime};

auto codeBlock = getLeafCodeBlock(callable, runtime);
bool isNative = !codeBlock;
auto res = JSObject::defineOwnProperty(
resultHandle,
runtime,
Predefined::getSymbolID(Predefined::isNative),
DefinePropertyFlags::getDefaultNewPropertyFlags(),
runtime->getBoolValue(isNative));
assert(res != ExecutionStatus::EXCEPTION && "Failed to set isNative");
(void)res;

if (codeBlock) {
OptValue<hbc::DebugSourceLocation> location =
codeBlock->getSourceLocation();
if (location) {
tmpHandle = HermesValue::encodeNumberValue(location->line);
res = JSObject::defineOwnProperty(
resultHandle,
runtime,
Predefined::getSymbolID(Predefined::lineNumber),
DefinePropertyFlags::getDefaultNewPropertyFlags(),
tmpHandle);
assert(res != ExecutionStatus::EXCEPTION && "Failed to set lineNumber");
(void)res;

tmpHandle = HermesValue::encodeNumberValue(location->column);
res = JSObject::defineOwnProperty(
resultHandle,
runtime,
Predefined::getSymbolID(Predefined::columnNumber),
DefinePropertyFlags::getDefaultNewPropertyFlags(),
tmpHandle);
assert(res != ExecutionStatus::EXCEPTION && "Failed to set columnNumber");
(void)res;
} else {
tmpHandle = HermesValue::encodeNumberValue(
codeBlock->getRuntimeModule()->getBytecode()->getCJSModuleOffset());
res = JSObject::defineOwnProperty(
resultHandle,
runtime,
Predefined::getSymbolID(Predefined::cjsModuleOffset),
DefinePropertyFlags::getDefaultNewPropertyFlags(),
tmpHandle);
assert(
res != ExecutionStatus::EXCEPTION && "Failed to set cjsModuleOffset");
(void)res;

tmpHandle = HermesValue::encodeNumberValue(codeBlock->getVirtualOffset());
res = JSObject::defineOwnProperty(
resultHandle,
runtime,
Predefined::getSymbolID(Predefined::virtualOffset),
DefinePropertyFlags::getDefaultNewPropertyFlags(),
tmpHandle);
assert(
res != ExecutionStatus::EXCEPTION && "Failed to set virtualOffset");
(void)res;
}

auto fileNameRes = getCodeBlockFileName(runtime, codeBlock, location);
if (LLVM_UNLIKELY(fileNameRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
tmpHandle = *fileNameRes;
res = JSObject::defineOwnProperty(
resultHandle,
runtime,
Predefined::getSymbolID(Predefined::fileName),
DefinePropertyFlags::getDefaultNewPropertyFlags(),
tmpHandle);
assert(res != ExecutionStatus::EXCEPTION && "Failed to set fileName");
(void)res;
}
JSObject::preventExtensions(*resultHandle);
return resultHandle.getHermesValue();
}

Handle<JSObject> createHermesInternalObject(
Runtime *runtime,
const JSLibFlags &flags) {
Expand Down Expand Up @@ -736,6 +872,8 @@ Handle<JSObject> createHermesInternalObject(
P::getRuntimeProperties, hermesInternalGetRuntimeProperties);
defineInternMethod(P::ttiReached, hermesInternalTTIReached);
defineInternMethod(P::ttrcReached, hermesInternalTTRCReached);
defineInternMethod(P::getFunctionLocation, hermesInternalGetFunctionLocation);

#ifdef HERMESVM_USE_JS_LIBRARY_IMPLEMENTATION
defineInternMethodAndSymbol("executeCall", hermesInternalExecuteCall);
defineInternMethodAndSymbol("getSubstitution", hermesInternalGetSubstitution);
Expand Down
104 changes: 104 additions & 0 deletions test/hermes/hermes-internal-get-function-location.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

// RUN: %hermes -g %s \
// RUN: | %FileCheck --match-full-lines %s -check-prefix JS
// RUN: %hermes -O -emit-binary -output-source-map -out %t.hbc %s && %hermes %t.hbc \
// RUN: | %FileCheck --match-full-lines %s -check-prefix BC

"use strict";

function fn1() {}
print(loc(fn1).fileName);
// JS: {{.+}}/hermes-internal-get-function-location.js
// BC: undefined
print(loc(fn1).lineNumber);
// JS: [[@LINE-5]]
// BC: undefined
print(loc(fn1).columnNumber);
// JS-NEXT: 16
// BC-NEXT: undefined
print(loc(fn1).cjsModuleOffset);
// JS-NEXT: undefined
// BC-NEXT: 0
print(loc(fn1).virtualOffset);
// JS-NEXT: undefined
// BC-NEXT: [[FN1OFFSET:[0-9]+]]
print(loc(fn1).isNative);
// JS-NEXT: false
// BC-NEXT: false

const fn1Bound = fn1.bind(null);
print(loc(fn1Bound).fileName);
// JS-NEXT: {{.+}}/hermes-internal-get-function-location.js
// BC-NEXT: undefined
print(loc(fn1Bound).lineNumber);
// JS: [[@LINE-25]]
// BC-NEXT: undefined
print(loc(fn1Bound).columnNumber);
// JS-NEXT: 16
// BC-NEXT: undefined
print(loc(fn1Bound).cjsModuleOffset);
// JS-NEXT: undefined
// BC-NEXT: 0
print(loc(fn1Bound).virtualOffset);
// JS-NEXT: undefined
// BC-NEXT: [[FN1OFFSET]]
print(loc(fn1Bound).isNative);
// JS-NEXT: false
// BC-NEXT: false

print(loc(Object).fileName);
// JS-NEXT: undefined
// BC-NEXT: undefined
print(loc(Object).lineNumber);
// JS-NEXT: undefined
// BC-NEXT: undefined
print(loc(Object).columnNumber);
// JS-NEXT: undefined
// BC-NEXT: undefined
print(loc(Object).cjsModuleOffset);
// JS-NEXT: undefined
// BC-NEXT: undefined
print(loc(Object).virtualOffset);
// JS-NEXT: undefined
// BC-NEXT: undefined
print(loc(Object).isNative)
// JS-NEXT: true
// BC-NEXT: true

const fn2 = eval('(x => x()) \n//# sourceURL=dummy');
print(loc(fn2).fileName);
// JS-NEXT: dummy
// BC-NEXT: dummy
print(loc(fn2).lineNumber);
// JS-NEXT: 1
// BC-NEXT: 1
print(loc(fn2).columnNumber);
// JS-NEXT: 2
// BC-NEXT: 2
print(loc(fn2).cjsModuleOffset);
// JS-NEXT: undefined
// BC-NEXT: undefined
print(loc(fn2).virtualOffset);
// JS-NEXT: undefined
// BC-NEXT: undefined
print(loc(fn2).isNative);
// JS-NEXT: false
// BC-NEXT: false

try { loc(); } catch(e) { print(e.message); }
// JS-NEXT: Argument to HermesInternal.getFunctionLocation must be callable
// BC-NEXT: Argument to HermesInternal.getFunctionLocation must be callable
try { loc({}); } catch(e) { print(e.message); }
// JS-NEXT: Argument to HermesInternal.getFunctionLocation must be callable
// BC-NEXT: Argument to HermesInternal.getFunctionLocation must be callable
try { loc(Math.PI); } catch(e) { print(e.message); }
// JS-NEXT: Argument to HermesInternal.getFunctionLocation must be callable
// BC-NEXT: Argument to HermesInternal.getFunctionLocation must be callable

function loc(f) { return HermesInternal.getFunctionLocation(f); }

0 comments on commit 7364f96

Please sign in to comment.