From ba85682d22b2f609d99b187ae16ca3a65d30773f Mon Sep 17 00:00:00 2001 From: gdh1995 Date: Thu, 22 Aug 2024 01:25:19 +0800 Subject: [PATCH] simple eval: fix found bugs and update behaviors in MV3 --- lib/simple_eval.ts | 69 +++++++++++++++++++--------------- tests/unit/simple-js-eval.html | 8 ++-- typings/vimium_c.d.ts | 1 + 3 files changed, 45 insertions(+), 33 deletions(-) diff --git a/lib/simple_eval.ts b/lib/simple_eval.ts index fd4e089fb..e8a4a7dc2 100644 --- a/lib/simple_eval.ts +++ b/lib/simple_eval.ts @@ -101,10 +101,11 @@ const HasReflect: boolean = !(Build.BTypes & BrowserType.Chrome) || typeof VApi === "object" && !!(VApi && VApi.z) && VApi.z.v! > BrowserVer.MinEnsured$Reflect$$apply$And$$construct - 1 || typeof Reflect !== "undefined" && !!Reflect && !!Reflect.apply && !!Reflect.construct -const DefaultIsolate = (typeof globalThis !== "undefined" ? globalThis : window) as unknown as Isolate +const DefaultIsolate = (Build.MV3 || typeof globalThis !== "undefined" ? globalThis : window) as unknown as Isolate const DefaultFunction = (DefaultIsolate as Dict)["Function"] as FunctionConstructor -let NativeFunctionCtor: FunctionConstructor | false | null = !Build.NDEBUG && typeof VimiumInjector === "undefined" - ? null : JSON.stringify((typeof browser === "object" && browser as never || chrome +let NativeFunctionCtor: FunctionConstructor | false | null = + Build.MV3 || !Build.NDEBUG && typeof VimiumInjector === "undefined" ? null + : JSON.stringify((typeof browser === "object" && browser as never || chrome ).runtime.getManifest().content_security_policy).includes("'unsafe-eval'") ? null : false let isolate_: Isolate = DefaultIsolate, locals_: StackFrame[] = [], stackDepth_ = 0 let g_exc: { g: Isolate, l: StackFrame[], d: number } | null = null @@ -225,7 +226,7 @@ const splitTokens = (expression_: string): Token[] => { } else if (peek("...")) { tokens_.push(Token(T.ref, "..."), Token(T.comma, ",")); pos_ += 3 } else if (expect(curlyBraces[curlyBraces.length - 1] ? /^[\}`]/ : /^`/)) { - if (!expect(/^(?:[^`\\$]|\\[^]|\$(?!\{))*(?:`|\$\{)/) && !Build.NDEBUG) { + if (!Build.NDEBUG && !expect(/^(?:[^`\\$]|\\[^]|\$(?!\{))*(?:`|\$\{)/)) { throwSyntax("Unexpected template string") } const isBegin = expression_[pos_ - last_.length - 1] === "`", isEnd = last_.endsWith("`") @@ -375,7 +376,7 @@ const parseTree = (tokens_: readonly Token[], inNewFunc: boolean | null | undefi } else { if (stat.a !== "labelled") { /* empty */ } else if (val.o !== O.stat) { stat.l = [(clause as RefOp).v], stat.c = null } - else { stat = val.v; (stat.l || ((stat.l as string[] | null) = [])).push((clause as RefOp).v) } + else { stat = val.v; (stat.l || ((stat.l as string[] | null) = [])).unshift((clause as RefOp).v) } values_.push(Op(O.stat, stat)) } } break @@ -602,7 +603,7 @@ const analyseEscaped = (func: BaseOp): void => { _collect = (op, enter): void => { if (op.o === O.token) { let val = enter as VarNames, i = visited.length - 1, decl: NullableVarList - if (visited[i].r.has(val) || (val as VarNames | VarLiterals) === "...") { return } + if (i < 0 || visited[i].r.has(val) || (val as VarNames | VarLiterals) === "...") { return } for (; (decl = visited[i].d) !== 0 && decl.indexOf(val) < 0; i--) { /* empty */ } visited[decl ? visited.length - 1 : i].r.add(val) } else if (op.o !== O.fn) { @@ -968,8 +969,7 @@ const evalNever = (op: BaseOp): void => throwType(DebugCallee(op.v.f, func.name, i2) + "is not a constructor") } if (func === DefaultFunction) { func = /*#__NOINLINE__*/ innerFunction_ as unknown as typeof func } - return (!(Build.BTypes & BrowserType.Chrome) || Build.MinCVer >= BrowserVer.MinTestedES6Environment - && Build.MinCVer >= BrowserVer.MinEnsuredES6SpreadOperator) && noThis + return (!(Build.BTypes & BrowserType.Chrome) || Build.MinCVer >= BrowserVer.MinTestedES6Environment) && noThis ? !op.v.n ? func(...args) : new func(...args) : !op.v.n ? HasReflect ? Reflect!.apply(func, noThis ? void 0 : y, args) : evalCall.apply.bind<(this: (this: 1, arg: 2) => 3, thisArg: 1, x: [2]) => 3>( @@ -1175,7 +1175,7 @@ const ToString = (op: Op, allowed: number): string => { + (ToString(clause.v[2], allowed) || kUnknown).trim() let x = child ? ToString(child, allowed) : "" doesCollect2 && _collect!(op, 0) - return (label ? label.slice(1).map(i => i + ":\n").join("") + label[0] + ": " : "") + return (label ? label.join(":\n") + ": " : "") + prefix + (clause ? c ? ` (${c})` : " " + kUnknown : "") + (!child ? "" : !x ? child.o !== O.block ? " " + kUnknown + ";" : " { ... }" : (x = x.trimLeft(), prefix && (prefix === "else if" || kTokenEnums[prefix] === T.prefix) @@ -1264,9 +1264,9 @@ const AccessToString = (access: string | number | boolean | null | undefined | o } const FuncToString = function (this: (...args: unknown[]) => unknown): string { - let func: Function2 = this, s: string - return typeof func !== "function" || !func.__fn ? DefaultFunction.prototype.toString.apply(func, arguments) - : (s = ToString(Op(O.fn, func.__fn), 0), /*#__NOINLINE__*/ resetRe_(), s) + let func: Function2 = this, s: string, priv_fn = typeof func === "function" && func.__fn + return !priv_fn ? DefaultFunction.prototype.toString.apply(func, arguments) + : (s = ToString(priv_fn.p || Op(O.fn, priv_fn), 0), /*#__NOINLINE__*/ resetRe_(), s) } const DebugCallee = (funcOp: Op, funcInst: ((...args: unknown[]) => unknown) | string | null | undefined @@ -1340,14 +1340,18 @@ const innerEval_ = function (_functionBody: string): unknown { return func() } +/** + * (...args: [...funcArguments: string[], functionBody: string + * , globals?: Isolate | null | undefined, locals?: VarDict | VarDict[] | null | undefined]) => Result + */ const outerEval_ = function (_functionBody: string): unknown { const func = baseFunctionCtor(parseArgsAndEnv(arguments, DefaultIsolate, []), null, true) return func() } -const doubleEval_ = function (_functionBody: string | object): unknown { +const doubleEval_mv2 = function (_functionBody: string | object): unknown { const info = typeof _functionBody === "object" && _functionBody ? _functionBody as ReturnType - : parseArgsAndEnv(arguments, null, null), hasEnv = !!(info.globals || info.closures) + : parseArgsAndEnv(arguments, null, null), hasEnv = !Build.MV3 && !!(info.globals || info.closures) let func: (() => unknown) | null | undefined if (!Build.MV3 && NativeFunctionCtor === null && !hasEnv) { const ctor = baseFunctionCtor(parseArgsAndEnv(["Function"], DefaultIsolate, []))() as FunctionConstructor @@ -1368,8 +1372,8 @@ const doubleEval_ = function (_functionBody: string | object): unknown { } catch {} if (func) { return func() } } - info.globals ||= info.globals || DefaultIsolate - info.closures = info.closures || [] + info.globals ||= DefaultIsolate + info.closures ||= [] func = baseFunctionCtor(info, null, true) return func() } @@ -1378,33 +1382,38 @@ const exposeStack = (stackArray: StackFrame[] ): { dict: VarDict, consts: readonly string[], name: string, done: boolean }[] => stackArray.slice().reverse().map(frame => ({ dict: frame.v, consts: frame.c, name: frame.n || "", done: !frame.r })) -/** - * (...args: [...args: string[], functionBody: string - * , globals: Isolate | null | undefined, locals: VarDict | VarDict[] | null | undefined]): Result - */ -typeof VApi === "object" && VApi ? VApi!.v = outerEval_ - : (DefaultIsolate as Partial).vimiumEval = outerEval_ -outerEval_.vimiumEval = outerEval_ -outerEval_.doubleEval = doubleEval_ -outerEval_["noNative"] = (): void => { (!Build.MV3 || !Build.NDEBUG) && (NativeFunctionCtor = false) } +if (Build.MV3) { + const browser_ = Build.BTypes&BrowserType.Chrome && (DefaultIsolate as any).chrome || (DefaultIsolate as any).browser + if (browser_?.runtime?.connect && typeof VApi === "object" && VApi) { + VApi!.v = outerEval_ + } else { + (DefaultIsolate as any)[GlobalConsts.kEvalNameInMV3] = outerEval_ + } +} else { + typeof VApi === "object" && VApi ? VApi!.v = outerEval_ + : (DefaultIsolate as Partial).vimiumEval = outerEval_ + outerEval_.vimiumEval = outerEval_ + outerEval_.doubleEval = doubleEval_mv2 + outerEval_["noNative"] = (): void => { NativeFunctionCtor = false } +} outerEval_["getStack"] = (exc?: boolean): unknown => (exc && !g_exc ? null : { stack: exposeStack(exc ? g_exc!.l : locals_), depth: exc ? g_exc!.d : stackDepth_, globals: exc ? g_exc!.g : isolate_, }) outerEval_.tryEval = function (_functionBody: string): ReturnType { - const info = parseArgsAndEnv(arguments, null, null), hasEnv = !!(info.globals || info.closures) + const info = Build.MV3 ? null as never : parseArgsAndEnv(arguments, null, null) + const hasEnv = !Build.MV3 && !!(info.globals || info.closures) try { - const result = doubleEval_(info) - return { ok: (!Build.MV3 || !Build.NDEBUG) && NativeFunctionCtor && !hasEnv ? 1 : 2, result } + const result = Build.MV3 ? outerEval_(...(arguments as ArrayLike as [string])) : doubleEval_mv2(info) + return { ok: !Build.MV3 && NativeFunctionCtor && !hasEnv ? 1 : 2, result } } catch (error) { - const native = (!Build.MV3 || !Build.NDEBUG) && NativeFunctionCtor && !hasEnv + const native = !Build.MV3 && NativeFunctionCtor && !hasEnv Build.NDEBUG || console.log("Vimium C: catch an eval error:", error) return { ok: 0, result: error, stack: native ? null : exposeStack(g_exc ? g_exc!.l : []), type: native ? "native": "eval", globals: native ? null : g_exc ? g_exc!.g : isolate_ } as ReturnType } } -; (outerEval_.tryEval as any).outerEval = outerEval_ if (!(Build.NDEBUG || T.END < 0x80000000 && T.END > 0 )) { alert(`Assert error: wrong kTokenNames`) } if (!(Build.NDEBUG || kOpNames.length === O.token + 1)) { alert(`Assert error: wrong fields in kOpNames`) } diff --git a/tests/unit/simple-js-eval.html b/tests/unit/simple-js-eval.html index 699aab814..3315bbfee 100644 --- a/tests/unit/simple-js-eval.html +++ b/tests/unit/simple-js-eval.html @@ -41,7 +41,9 @@ globalThis.Map=function(){setProto_1(this);this.map_=Object.create(null);this.isSet_=0;};} })(); typeof chrome !== "undefined" || (window.chrome = {}); +typeof browser !== "undefined" || (window.browser = chrome); (chrome.runtime || (chrome.runtime={})).getManifest = function(){return {content_security_policy:{extension_pages:""}}} +chrome.runtime.connect = function(){return -42} @@ -76,7 +78,7 @@ var error = null, value code = normalizeCode(code) try { - value = VApi.v.vimiumEval(code) + value = (VApi.v.tryEval && VApi.v)(code) } catch (e) { error = e } @@ -204,7 +206,7 @@ //#endregion function onLoad() { resultDiv.innerText = "(Inited)"; - VApi.v.noNative(); + VApi.v.noNative && VApi.v.noNative(); // =============================== assert =============================== assert(function () { return {...null, ...123} }) @@ -373,7 +375,7 @@ }) test(` const a = 1 - if a: ab: return 12 + if a: ab: cd: efg: return 12 return { 0: 1, 2: 3 } `) assert(function() { diff --git a/typings/vimium_c.d.ts b/typings/vimium_c.d.ts index 66fcc36d2..836e77076 100644 --- a/typings/vimium_c.d.ts +++ b/typings/vimium_c.d.ts @@ -583,6 +583,7 @@ declare const enum GlobalConsts { ModifierKeyTimeout = 2_900, OptionsPage = "pages/options.html", kLoadEvent = "VimiumC", + kEvalNameInMV3 = "__VimiumC_eval__", KeepAliveTime = 900_000, }