-
-
Save anba/fc8e71ed88f446663b0b to your computer and use it in GitHub Desktop.
MOP operation sequence logging
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
if (!("uncurryThis" in Function.prototype)) { | |
Function.prototype.uncurryThis = function uncurryThis() { | |
return (a0, ...args) => this.call(a0, ...args) | |
}; | |
} | |
if (!("compose" in Function.prototype)) { | |
Function.prototype.compose = function compose(g) { | |
return (...args) => g(this(...args)) | |
}; | |
} | |
// Test function with example data | |
function test() { | |
function compare(expected, actual, input) { | |
if (expected !== actual) { | |
print("Input: " + input); | |
print(" Expected: " + expected); | |
print(" Actual: " + actual); | |
} | |
} | |
compare( | |
"ownKeys,preventExtensions,", | |
log(Object.seal, {}), | |
"log(Object.seal, {})" | |
); | |
compare( | |
"ownKeys,defineProperty:a,defineProperty:b,preventExtensions,", | |
log(Object.seal, {a: 1, b: 2}), | |
"log(Object.seal, {a: 1, b: 2})" | |
); | |
compare( | |
"ownKeys,preventExtensions,ownKeys,preventExtensions,", | |
log(Object.seal.compose(Object.seal), {}), | |
"log(Object.seal.compose(Object.seal), {})" | |
); | |
compare( | |
"ownKeys,defineProperty:a,preventExtensions,ownKeys,defineProperty:a,preventExtensions,", | |
log(Object.seal.compose(Object.seal), {a: 1}), | |
"log(Object.seal.compose(Object.seal), {a: 1})" | |
); | |
compare( | |
"get:length,get:1,deleteProperty:1,set:length,getOwnPropertyDescriptor:length,defineProperty:length,", | |
log([].pop.uncurryThis(), [1, 2]), | |
"log([].pop.uncurryThis(), [1, 2])" | |
); | |
compare( | |
"get:join,get:length,get:0,get:1,", | |
log([].toString.uncurryThis(), [1, 2]), | |
"log([].toString.uncurryThis(), [1, 2])" | |
); | |
compare( | |
"get:length,get:0,get:2,has:0,has:2,set:0,getOwnPropertyDescriptor:0,defineProperty:0,set:2,getOwnPropertyDescriptor:2,defineProperty:2,", | |
log([].reverse.uncurryThis(), [1,2,3]), | |
"log([].reverse.uncurryThis(), [1,2,3])" | |
); | |
} | |
// Function to log MOP operations | |
function log(fn, o = {}, ...args) { | |
"use strict"; | |
function assert(c, msg = "") { | |
if (!c) { | |
throw new Error("assertion failed: " + msg); | |
} | |
} | |
/* 6.1 ECMAScript Language Types */ | |
function IsObject(v) { | |
switch(typeof v) { | |
case "undefined": | |
case "boolean": | |
case "number": | |
case "string": | |
case "symbol": | |
return false; | |
case "object": | |
if (v === null) { | |
return false; | |
} | |
case "function": | |
return true; | |
default: | |
return false; | |
} | |
} | |
/* 6.2.5.2 IsDataDescriptor (Desc) */ | |
function IsDataDescriptor(desc) { | |
if (desc === void 0) { | |
return false; | |
} | |
return "value" in desc || "writable" in desc; | |
} | |
/* 6.2.5.1 IsAccessorDescriptor (Desc) */ | |
function IsAccessorDescriptor(desc) { | |
if (desc === void 0) { | |
return false; | |
} | |
return "get" in desc || "set" in desc; | |
} | |
/* 7.1.9 ToObject */ | |
function ToObject(v) { | |
if (v == null) { | |
throw TypeError("ToObject(null)"); | |
} | |
return Object(v); | |
} | |
/* 7.1.10 ToPropertyKey */ | |
function ToPropertyKey(pk) { | |
if (typeof pk === "symbol") { | |
return pk; | |
} | |
return String(pk); | |
} | |
/* 7.2.2 IsCallable */ | |
function IsCallable(v) { | |
return typeof v === "function"; | |
} | |
/* 7.2.3 SameValue(x, y) */ | |
function SameValue(a, b) { | |
return Object.is(a, b); | |
} | |
/* 7.2.6 IsPropertyKey */ | |
function IsPropertyKey(pk) { | |
return typeof pk === "string" || typeof pk === "symbol"; | |
} | |
/* 7.3.3 CreateOwnDataProperty (O, P, V) */ | |
function CreateOwnDataProperty(o, p, v) { | |
return ObjectDefineOwnProperty(o, p, {value: v, writable: true, enumerable: true, configurable: true}); | |
} | |
/* 7.3.7 HasOwnProperty (O, P) */ | |
function HasOwnProperty(o, p) { | |
return ObjectGetOwnProperty(o, p) !== void 0; | |
} | |
/* 7.3.8 GetMethod (O, P) */ | |
function GetMethod(o, p) { | |
var func = ObjectGet(o, p); | |
if (!(func === void 0 || IsCallable(func))) { | |
throw TypeError(p + "is not callable"); | |
} | |
return func; | |
} | |
// WeakMap: Proxy -> (Target, Handler) | |
var proxies = new WeakMap(); | |
function newProxy(target, handler) { | |
var p = new Proxy(target, handler); | |
proxies.set(p, {target: target, handler: handler}); | |
return p; | |
} | |
/* [[GetPrototypeOf]] */ | |
function ObjectPrototypeOf(o) { | |
return Object.getPrototypeOf(o); | |
} | |
/* [[GetOwnProperty]] */ | |
function ObjectGetOwnProperty(o, p) { | |
return Object.getOwnPropertyDescriptor(o, p); | |
} | |
/* [[DefineOwnProperty]] */ | |
function ObjectDefineOwnProperty(o, p, desc) { | |
try { | |
Object.defineProperty(o, p, desc); | |
return true; | |
} catch (e) { | |
return false; | |
} | |
} | |
/* [[HasProperty]] */ | |
function ObjectHasProperty(o, p) { | |
return p in o; | |
} | |
/* [[Get]] */ | |
function ObjectGet(o, p, receiver) { | |
if (proxies.has(o)) { | |
return ProxyGet(o, p, receiver); | |
} | |
return OrdinaryObjectGet(o, p, receiver); | |
} | |
/* [[Set]] */ | |
function ObjectSet(o, p, v, receiver) { | |
if (proxies.has(o)) { | |
return ProxySet(o, p, v, receiver); | |
} | |
return OrdinaryObjectSet(o, p, v, receiver); | |
} | |
/* [[Delete]] */ | |
function ObjectDelete(o, p) { | |
try { | |
return delete o[p]; | |
} catch (e) { | |
return false; | |
} | |
} | |
/* 9.1.8 [[Get]] (P, Receiver) */ | |
function OrdinaryObjectGet(o, p, receiver) { | |
assert(IsPropertyKey(p)); | |
var desc = ObjectGetOwnProperty(o, p); | |
if (desc === void 0) { | |
var parent = ObjectPrototypeOf(o); | |
if (parent === null) { | |
return void 0; | |
} | |
return ObjectGet(parent, p, receiver); | |
} | |
if (IsDataDescriptor(desc)) { | |
return desc.value; | |
} | |
assert(IsAccessorDescriptor(desc)); | |
var getter = desc.get; | |
if (getter === void 0) { | |
return void 0; | |
} | |
return getter.call(receiver); | |
} | |
/* 9.1.9 [[Set]] (P, V, Receiver) */ | |
function OrdinaryObjectSet(o, p, v, receiver) { | |
assert(IsPropertyKey(p)); | |
var ownDesc = ObjectGetOwnProperty(o, p); | |
if (ownDesc === void 0) { | |
var parent = ObjectPrototypeOf(o); | |
if (parent !== null) { | |
return ObjectSet(parent, p, v, receiver); | |
} else { | |
ownDesc = {value: void 0, writable: true, enumerable: true, configurable: true}; | |
} | |
} | |
if (IsDataDescriptor(ownDesc)) { | |
if (!ownDesc.writable) { | |
return false; | |
} | |
if (!IsObject(receiver)) { | |
return false; | |
} | |
var existingDescriptor = ObjectGetOwnProperty(receiver, p); | |
if (existingDescriptor !== void 0) { | |
var valueDesc = {value: v}; | |
return ObjectDefineOwnProperty(receiver, p, valueDesc); | |
} else { | |
return CreateOwnDataProperty(receiver, p, v); | |
} | |
} | |
if (IsAccessorDescriptor(ownDesc)) { | |
var setter = ownDesc.set; | |
if (setter === void 0) { | |
return false; | |
} | |
setter.call(receiver, v); | |
return true; | |
} | |
assert(false, "unreachable"); | |
} | |
/* 9.3.8 [[Get]] (P, Receiver) */ | |
function ProxyGet(o, p, receiver) { | |
assert(IsPropertyKey(p)); | |
var intern = proxies.get(o); | |
var handler = intern.handler; | |
var target = intern.target; | |
var trap = GetMethod(handler, "get"); | |
if (trap === void 0) { | |
return ObjectGet(target, p, receiver); | |
} | |
var trapResult = trap.call(handler, target, p, receiver); | |
var targetDesc = ObjectGetOwnProperty(target, p); | |
if (targetDesc !== void 0) { | |
if (IsDataDescriptor(targetDesc) && !targetDesc.configurable && !targetDesc.writable) { | |
if (!SameValue(trapResult, targetDesc.value)) { | |
throw TypeError("ProxyGet violation"); | |
} | |
} | |
if (IsAccessorDescriptor(targetDesc) && !targetDesc.configurable && targetDesc.get === void 0) { | |
if (trapResult !== void 0) { | |
throw TypeError("ProxyGet violation"); | |
} | |
} | |
} | |
return trapResult; | |
} | |
/* 9.3.9 [[Set]] (P, V, Receiver) */ | |
function ProxySet(o, p, v, receiver) { | |
assert(IsPropertyKey(p)); | |
var intern = proxies.get(o); | |
var handler = intern.handler; | |
var target = intern.target; | |
var trap = GetMethod(handler, "set"); | |
if (trap === void 0) { | |
return ObjectSet(target, p, v, receiver); | |
} | |
var trapResult = trap.call(handler, target, p, v, receiver); | |
if (!trapResult) { | |
return false; | |
} | |
var targetDesc = ObjectGetOwnProperty(target, p); | |
if (targetDesc !== void 0) { | |
if (IsDataDescriptor(targetDesc) && !targetDesc.configurable && !targetDesc.writable) { | |
if (!SameValue(v, targetDesc.value)) { | |
throw TypeError("ProxySet violation"); | |
} | |
} | |
if (IsAccessorDescriptor(targetDesc) && !targetDesc.configurable) { | |
if (targetDesc.set === void 0) { | |
throw TypeError("ProxySet violation"); | |
} | |
} | |
} | |
return true; | |
} | |
// Provide (partial) Reflect implementation. Only needed here to ensure `receiver` argument in [[Get]] and [[Set]] | |
// operations is preserved. | |
var global = (1, eval)("this"); | |
var Reflect = global.Reflect !== void 0 ? global.Reflect : {}; | |
if (!("defineProperty" in Reflect)) { | |
Reflect.defineProperty = function Reflect$defineProperty(target, propertyKey, attributes) { | |
var obj = ToObject(target); | |
var key = ToPropertyKey(propertyKey); | |
var desc = attributes; | |
return ObjectDefineOwnProperty(obj, key, desc); | |
}; | |
} | |
if (!("deleteProperty" in Reflect)) { | |
Reflect.deleteProperty = function Reflect$deleteProperty(target, propertyKey) { | |
var obj = ToObject(target); | |
var key = ToPropertyKey(propertyKey); | |
return ObjectDelete(obj, key); | |
}; | |
} | |
if (!("get" in Reflect)) { | |
Reflect.get = function Reflect$get(target, propertyKey, receiver) { | |
var obj = ToObject(target); | |
var key = ToPropertyKey(propertyKey); | |
if (arguments.length < 3) receiver = target; | |
return ObjectGet(obj, key, receiver); | |
}; | |
} | |
if (!("getOwnPropertyDescriptor" in Reflect)) { | |
Reflect.getOwnPropertyDescriptor = function Reflect$getOwnPropertyDescriptor(target, propertyKey) { | |
var obj = ToObject(target); | |
var key = ToPropertyKey(propertyKey); | |
return ObjectGetOwnProperty(obj, key); | |
}; | |
} | |
if (!("has" in Reflect)) { | |
Reflect.has = function Reflect$has(target, propertyKey) { | |
var obj = ToObject(target); | |
var key = ToPropertyKey(propertyKey); | |
return ObjectHasProperty(obj, key); | |
}; | |
} | |
if (!("hasOwn" in Reflect)) { | |
Reflect.hasOwn = function Reflect$hasOwn(target, propertyKey) { | |
var obj = ToObject(target); | |
var key = ToPropertyKey(propertyKey); | |
return HasOwnProperty(obj, key); | |
}; | |
} | |
if (!("set" in Reflect)) { | |
Reflect.set = function Reflect$set(target, propertyKey, value, receiver) { | |
var obj = ToObject(target); | |
var key = ToPropertyKey(propertyKey); | |
if (arguments.length < 4) receiver = target; | |
return ObjectSet(obj, key, value, receiver); | |
}; | |
} | |
// Symbol-aware ToString() operation | |
function PropertyKeyToString(pk) { | |
return typeof pk !== "symbol" ? String(pk) : String(Object(pk)); | |
} | |
// Translates old proxy trap names | |
var trapNames = { | |
hasOwn: "getOwnPropertyDescriptor", | |
getOwnPropertyNames: "ownKeys", | |
}; | |
function translateTrapName(name) { | |
name = PropertyKeyToString(name); | |
return name in trapNames ? trapNames[name] : name; | |
} | |
// Double lifting proxy to log MOP operation sequence | |
var out = ""; | |
var h, p = newProxy(o, newProxy(h = { | |
getOwnPropertyDescriptor:(t, pk) => { | |
out += ":" + PropertyKeyToString(pk) + ","; | |
return Reflect.getOwnPropertyDescriptor(t, pk); | |
}, | |
defineProperty:(t, pk, d) => { | |
out += ":" + PropertyKeyToString(pk) + ","; | |
return Reflect.defineProperty(t, pk, d); | |
}, | |
hasOwn:(t, pk) => { | |
out += ":" + PropertyKeyToString(pk) + ","; | |
return Reflect.hasOwn(t, pk); | |
}, | |
has:(t, pk) => { | |
out += ":" + PropertyKeyToString(pk) + ","; | |
return Reflect.has(t, pk); | |
}, | |
get:(t, pk, r) => { | |
out += ":" + PropertyKeyToString(pk) + ","; | |
return Reflect.get(t, pk, r); | |
}, | |
set:(t, pk, v, r) => { | |
out += ":" + PropertyKeyToString(pk) + ","; | |
return Reflect.set(t, pk, v, r); | |
}, | |
deleteProperty:(t, pk) => { | |
out += ":" + PropertyKeyToString(pk) + ","; | |
return Reflect.deleteProperty(t, pk); | |
}, | |
}, { | |
get:(t, pk, r) => { | |
out += translateTrapName(pk) + (pk in h ? "" : ","); | |
return t[pk]; | |
} | |
})); | |
// Call supplied function and return the recorded log | |
try { | |
fn(p, ...args); | |
return out; | |
} catch (e) { | |
if (typeof print === "function") { | |
print("Log: " + out); | |
} | |
throw e; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment