Skip to content

Instantly share code, notes, and snippets.

@anba

anba/log.js Secret

Created October 17, 2013 20:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save anba/fc8e71ed88f446663b0b to your computer and use it in GitHub Desktop.
Save anba/fc8e71ed88f446663b0b to your computer and use it in GitHub Desktop.
MOP operation sequence logging
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