From 97fe066f60099e2ec216f409ad0adde9227c4ff0 Mon Sep 17 00:00:00 2001 From: Evgenii Stulnikov Date: Tue, 10 Feb 2026 03:38:10 +0300 Subject: [PATCH 1/2] normalize xpath strings and improve error trace context tracking --- formula/error/errorFormatter.js | 127 +++++++++++++++++++++++++------- formula/evaluation.js | 36 ++++++--- 2 files changed, 128 insertions(+), 35 deletions(-) diff --git a/formula/error/errorFormatter.js b/formula/error/errorFormatter.js index 1e32e0fe..d181360a 100644 --- a/formula/error/errorFormatter.js +++ b/formula/error/errorFormatter.js @@ -15,6 +15,10 @@ const FRAME_TYPES = { FUNCTION: 'function', }; +function normalizeXpath(xpath) { + return typeof xpath === 'string' ? xpath.trim() : xpath; +} + const SPECIAL_OPS = { BOUNCE: 'bounce', REQUIRE: 'require', @@ -104,7 +108,7 @@ function collectFunctionsAfterGetter(frames, getterIndex) { function buildAAFrame(frame, nextFrame, traceLine, defaultXpath) { const aaFrame = { type: FRAME_TYPES.AA, aa: frame.aa }; - aaFrame.xpath = frame.xpath ?? defaultXpath; + aaFrame.xpath = normalizeXpath(frame.xpath) ?? normalizeXpath(defaultXpath); aaFrame.line = nextFrame?.call_line ?? traceLine; if (aaFrame.xpath === undefined) delete aaFrame.xpath; @@ -117,10 +121,16 @@ function buildGetterFrame(frame, getterFuncName, functionsAfterGetter, traceLine const getterFrame = { type: FRAME_TYPES.GETTER, aa: frame.aa, - xpath: '/getters', name: getterFuncName ?? frame.name, }; + if (frame.xpath !== undefined) { + getterFrame.xpath = normalizeXpath(frame.xpath); + } else { + const firstFunc = functionsAfterGetter.find(f => f?.def_xpath !== undefined); + if (firstFunc?.def_xpath !== undefined) getterFrame.xpath = normalizeXpath(firstFunc.def_xpath); + } + if (functionsAfterGetter.length > 1) { getterFrame.line = functionsAfterGetter[1]?.call_line ?? traceLine; } else { @@ -140,12 +150,16 @@ function buildFunctionFrame(frame, nextFrame, traceLine) { }; if (frame.def_xpath !== undefined) { - funcFrame.xpath = frame.def_xpath; + funcFrame.xpath = normalizeXpath(frame.def_xpath); } const nextIsFunc = nextFrame?.type === FRAME_TYPES.FUNCTION; const hasNextCallLine = nextIsFunc && nextFrame.call_line !== undefined; - funcFrame.line = hasNextCallLine ? nextFrame.call_line : traceLine; + if (hasNextCallLine) { + funcFrame.line = nextFrame.call_line; + } else { + funcFrame.line = traceLine; + } if (funcFrame.line === undefined) delete funcFrame.line; @@ -185,8 +199,9 @@ function buildTraceFromFrames(frames, traceLine, defaultXpath) { const funcFrame = { type: FRAME_TYPES.FUNCTION, name: lastFunc.name || '', - xpath: '/getters', }; + if (lastFunc?.def_xpath !== undefined) funcFrame.xpath = normalizeXpath(lastFunc.def_xpath); + else if (frame?.xpath !== undefined) funcFrame.xpath = normalizeXpath(frame.xpath); if (traceLine !== undefined) funcFrame.line = traceLine; result.push(funcFrame); } @@ -233,13 +248,22 @@ function handleEnterFunc(event, state) { if (event.name && event.formula) funcFormulas.set(event.name, event.formula); if (event.formula) funcFormulaStack.push(event.formula); + let def_line = event.def_line; + if (def_line === undefined && typeof event.formula === 'string' && typeof event.name === 'string') { + const needle = '$' + event.name; + const lines = event.formula.split('\n'); + const idx = lines.findIndex(l => l.includes(needle)); + if (idx !== -1) def_line = idx + 1; + } + framesStack.push({ type: FRAME_TYPES.FUNCTION, name: event.name || '', aa: event.aa, - def_xpath: event.xpath, + def_xpath: normalizeXpath(event.xpath), + def_line, call_line: event.call_line, - call_xpath: event.call_xpath, + call_xpath: normalizeXpath(event.call_xpath), }); } @@ -268,7 +292,7 @@ function handleEnterAA(event, state) { state.ownerAA = event.aa; state.ownerFormula = event.formula; - framesStack.push({ type: FRAME_TYPES.AA, aa: event.aa, xpath: event.xpath }); + framesStack.push({ type: FRAME_TYPES.AA, aa: event.aa, xpath: normalizeXpath(event.xpath) }); if (!aaFormulas.has(event.aa)) { aaFormulas.set(event.aa, new Set()); @@ -293,7 +317,8 @@ function handleEnterGetters(event, state) { name: event.getter || event.name, caller_aa: event.caller_aa, call_line: event.call_line, - call_xpath: event.call_xpath, + call_xpath: normalizeXpath(event.call_xpath), + xpath: normalizeXpath(event.xpath), }); } @@ -334,36 +359,74 @@ function processTraceEvent(event, state) { TRACE_HANDLERS[event.system]?.(event, state); } -function recordSnapshot(traceLine, state) { - const { snapshotsByLine, framesStack, funcFormulas, funcFormulaStack } = state; - if (snapshotsByLine[traceLine] !== undefined) { - return; +function selectBestSnapshot(snapshots, criteria) { + if (!Array.isArray(snapshots) || snapshots.length === 0) return undefined; + + let candidates = snapshots; + + if (criteria?.preferFatal) { + const fatal = candidates.filter(s => s.isFatal); + if (fatal.length) candidates = fatal; } + if (criteria?.aa !== undefined) { + const sameAA = candidates.filter(s => s.aa === criteria.aa); + if (sameAA.length) candidates = sameAA; + } + + if (criteria?.xpath !== undefined) { + const sameXpath = candidates.filter(s => s.xpath === criteria.xpath); + if (sameXpath.length) candidates = sameXpath; + } + + return candidates.at(-1); +} + +function recordSnapshot(traceLine, traceEvent, state) { + const { snapshotsByLine, framesStack, funcFormulas, funcFormulaStack } = state; + if (traceLine === undefined) return; + let snapFormula = state.ownerFormula || state.lastFormula; - if (funcFormulaStack.length) { snapFormula = funcFormulaStack.at(-1); } else if (state.namedFuncAtLastLine && funcFormulas.has(state.namedFuncAtLastLine)) { snapFormula = funcFormulas.get(state.namedFuncAtLastLine); } - snapshotsByLine[traceLine] = { + if (!Array.isArray(snapshotsByLine[traceLine])) { + snapshotsByLine[traceLine] = []; + } + + const snapshot = { gettersAA: state.gettersAAAtLastLine, formula: snapFormula, frames: framesStack.slice(), + aa: traceEvent?.aa, + xpath: traceEvent?.xpath, + isFatal: traceEvent?.system === 'fatal error', }; + + const list = snapshotsByLine[traceLine]; + const last = list.at(-1); + const lastTop = last?.frames?.at(-1); + const snapTop = snapshot.frames.at(-1); + const isSameTop = !!lastTop && !!snapTop && lastTop.type === snapTop.type && lastTop.name === snapTop.name && lastTop.aa === snapTop.aa; + const isSame = last && last.formula === snapshot.formula && isSameTop && last.isFatal === snapshot.isFatal; + if (!isSame) list.push(snapshot); } function processTraceEvents(trace, state) { for (const traceEvent of trace) { processTraceEvent(traceEvent, state); if (traceEvent.line !== undefined) { + if (traceEvent.system === 'fatal error') { + state.fatalError = { line: traceEvent.line, aa: traceEvent.aa, xpath: traceEvent.xpath }; + } state.lastTraceLine = traceEvent.line; state.gettersAAAtLastLine = state.lastGettersAA; state.namedFuncAtLastLine = state.lastNamedFunc; - recordSnapshot(state.lastTraceLine, state); + recordSnapshot(state.lastTraceLine, traceEvent, state); } } } @@ -388,9 +451,12 @@ function resolveErrorLine(errJson, state) { } function resolveTargetFormula(line, state) { - const { snapshotsByLine, aaPath, gettersAA, aaFormulas, lastFormula } = state; + const { snapshotsByLine, aaPath, gettersAA, aaFormulas, lastFormula, targetSnapshot } = state; - let effectiveFormula = snapshotsByLine?.[line]?.formula || lastFormula; + let effectiveFormula = targetSnapshot?.formula || lastFormula; + if (!effectiveFormula && snapshotsByLine?.[line]) { + effectiveFormula = selectBestSnapshot(snapshotsByLine[line])?.formula || effectiveFormula; + } if (aaFormulas && line !== undefined) { if (hasLineInFormula(effectiveFormula, line)) { @@ -437,6 +503,7 @@ function createInitialState() { namedFuncAtLastLine: undefined, ownerAA: undefined, ownerFormula: undefined, + fatalError: undefined, funcFormulas: new Map(), funcFormulaStack: [], framesStack: [], @@ -450,12 +517,16 @@ function buildContext(errJson) { processTraceEvents(trace, state); const { line, allLinesFromArr } = resolveErrorLine(errJson, state); - const targetSnap = state.snapshotsByLine[line]; + const desired = state.fatalError && state.fatalError.line === line + ? { ...state.fatalError, preferFatal: true } + : { aa: state.lastAA, xpath: errJson?.xpath }; + const targetSnap = selectBestSnapshot(state.snapshotsByLine[line], desired); const gettersAAAtTarget = targetSnap?.gettersAA || state.gettersAAAtLastLine; let lastFormulaAtTarget = resolveTargetFormula(line, { ...state, gettersAA: gettersAAAtTarget, + targetSnapshot: targetSnap, }); const getterFormula = gettersAAAtTarget && state.getters.get(gettersAAAtTarget); @@ -472,6 +543,8 @@ function buildContext(errJson) { gettersAA: gettersAAAtTarget, snapshotsByLine: state.snapshotsByLine, aaFormulas: state.aaFormulas, + targetSnapshot: targetSnap, + fatalError: state.fatalError, }; } @@ -484,7 +557,7 @@ function processNestedError(nestedError, line) { } function buildCodeLines(ctx) { - const { allLinesFromArr, line, lastFormula, snapshotsByLine, aaPath, gettersAA, aaFormulas } = ctx; + const { allLinesFromArr, line, lastFormula, snapshotsByLine, aaPath, gettersAA, aaFormulas, targetSnapshot } = ctx; const formulaState = { snapshotsByLine, @@ -493,7 +566,7 @@ function buildCodeLines(ctx) { aaFormulas, lastFormula, }; - const effectiveFormula = resolveTargetFormula(line, formulaState); + const effectiveFormula = resolveTargetFormula(line, { ...formulaState, targetSnapshot }); const hasLines = allLinesFromArr.length > 0; const hasLine = line !== undefined; @@ -505,8 +578,9 @@ function buildCodeLines(ctx) { }); } -function extractFramesForTrace(snapshotsByLine, traceLine) { - const snapshot = snapshotsByLine?.[traceLine]; +function extractFramesForTrace(snapshotsByLine, traceLine, criteria) { + const snapshots = snapshotsByLine?.[traceLine]; + const snapshot = selectBestSnapshot(snapshots, criteria); return Array.isArray(snapshot?.frames) ? snapshot.frames : undefined; } @@ -525,7 +599,7 @@ function clearContextForSpecialOps(result, errJson) { function formatError(errJson) { const ctx = buildContext(errJson); - const { line, dontShowFormat, snapshotsByLine } = ctx; + const { line, dontShowFormat, snapshotsByLine, fatalError } = ctx; const message = getErrorMessage(errJson.error); const nestedError = typeof errJson?.error === 'object' ? errJson.error : null; @@ -548,7 +622,10 @@ function formatError(errJson) { const result = { message, formattedContext, codeLines }; clearContextForSpecialOps(result, errJson); - const framesForTrace = extractFramesForTrace(snapshotsByLine, traceLine); + const traceCriteria = fatalError && fatalError.line === traceLine + ? { ...fatalError, preferFatal: true } + : { aa: undefined, xpath: undefined }; + const framesForTrace = extractFramesForTrace(snapshotsByLine, traceLine, traceCriteria); if (framesForTrace?.length) { result.trace = buildTraceFromFrames(framesForTrace, traceLine, errJson.xpath); } diff --git a/formula/evaluation.js b/formula/evaluation.js index 03924a28..9faeb06b 100644 --- a/formula/evaluation.js +++ b/formula/evaluation.js @@ -88,6 +88,8 @@ exports.evaluate = function (opts, astTrace, xpath, callback) { if (typeof xpath !== 'string') { xpath = ''; } + var current_xpath = xpath; + var current_xpath_stack = []; astTrace.push({system: 'enter to aa', aa: address, formula, bGetters: opts.bGetters, xpath}); @@ -150,7 +152,7 @@ exports.evaluate = function (opts, astTrace, xpath, callback) { } if (arr.line !== undefined) { - astTrace.push({ line: arr.line }); + astTrace.push({ line: arr.line, aa: address, xpath: current_xpath }); } var op = arr[0]; switch (op) { @@ -1227,7 +1229,10 @@ exports.evaluate = function (opts, astTrace, xpath, callback) { throw Error("arg name cannot be the same as func name in evaluation"); if (_.intersection(args, scopeVarNames).length > 0) return setFatalError("some args of " + var_name + " would shadow some local vars", { arr }, false, cb); - assignField(locals, var_name, new Func(args, body, scopeVarNames, formula, xpath)); + var f = new Func(args, body, scopeVarNames, formula, xpath); + if (arr.line !== undefined) + f.def_line = arr.line; + assignField(locals, var_name, f); return cb(true); } evaluate(rhs, function (res) { @@ -2322,7 +2327,7 @@ exports.evaluate = function (opts, astTrace, xpath, callback) { if (err) return setFatalError(err, { arr }, false, res_cb); - astTrace.push({system: 'exit from getters', aa: funcInfo.remote.remote_aa, caller_aa: address, call_line: arr.line, call_xpath: xpath}); + astTrace.push({system: 'exit from getters', aa: funcInfo.remote.remote_aa, xpath: '/getters', caller_aa: address, call_line: arr.line, call_xpath: xpath}); res_cb(r); }); }; @@ -2504,7 +2509,7 @@ exports.evaluate = function (opts, astTrace, xpath, callback) { callGetter(conn, remote_aa, func_name, args, stateVars, objValidationState, astTrace, xpath, { caller_aa: address, call_line: arr.line, call_xpath: xpath }, (err, res) => { if (err) return setFatalError(err, { arr }, false, cb); - astTrace.push({system: 'exit from getters', aa: remote_aa, caller_aa: address, call_line: arr.line, call_xpath: xpath}); + astTrace.push({system: 'exit from getters', aa: remote_aa, xpath: '/getters', caller_aa: address, call_line: arr.line, call_xpath: xpath}); cb(res); }); }); @@ -2905,6 +2910,9 @@ exports.evaluate = function (opts, astTrace, xpath, callback) { const frameAA = callInfo && callInfo.aa ? callInfo.aa : address; const call_line = callInfo ? callInfo.call_line : undefined; const call_xpath = callInfo ? callInfo.call_xpath : undefined; + current_xpath_stack.push(current_xpath); + if (typeof func.xpath === 'string') + current_xpath = func.xpath; astTrace.push({ system: 'enter to func', formula: func.formula || formula, @@ -2913,6 +2921,7 @@ exports.evaluate = function (opts, astTrace, xpath, callback) { aa: frameAA, call_line, call_xpath, + def_line: func.def_line, }); if (early_return !== undefined) throw Error("function called after a return"); @@ -2940,6 +2949,7 @@ exports.evaluate = function (opts, astTrace, xpath, callback) { // restore assignObject(locals, saved_locals); early_return = undefined; + current_xpath = current_xpath_stack.pop(); if (fatal_error) return cb(false); @@ -2957,7 +2967,12 @@ exports.evaluate = function (opts, astTrace, xpath, callback) { var scopeVarNames = Object.keys(locals); if (_.intersection(args, scopeVarNames).length > 0) return setFatalError("some args of anonymous function would shadow some local vars", { arr }, false, cb); - cb({ local: new Func(args, body, scopeVarNames, formula, xpath) }); + var f = new Func(args, body, scopeVarNames, formula, xpath); + if (func_expr.line !== undefined) + f.def_line = func_expr.line; + else if (arr.line !== undefined) + f.def_line = arr.line; + cb({ local: f }); } else if (func_expr[0] === 'local_var') { var var_name = func_expr[1]; @@ -3043,14 +3058,15 @@ exports.evaluate = function (opts, astTrace, xpath, callback) { function setFatalError(err, context, cb_arg, cb){ try { + if (context && context.arr && context.arr.line !== undefined) + astTrace.push({ system: 'fatal error', line: context.arr.line, aa: address, xpath: current_xpath }); const errorData = { error: err, context: context || {}, trace: astTrace, xpath, trigger }; - fatal_error = {formattedError: formatError(errorData)}; + fatal_error = {formattedError: formatError(errorData)}; } catch(formatError) { console.error('unhandled error, use old format', err, formatError); fatal_error = err; } - console.log(err); (cb_arg !== undefined) ? cb(cb_arg) : cb(err); astTrace = []; } @@ -3156,7 +3172,7 @@ function callGetter(conn, aa_address, getter, args, stateVars, objValidationStat const call_line = callerInfo && callerInfo.call_line; const call_xpath = callerInfo && callerInfo.call_xpath; - addAstTrace({ system: 'enter to getters', aa: aa_address, formula: f, caller_aa, call_line, call_xpath }); + addAstTrace({ system: 'enter to getters', aa: aa_address, xpath: '/getters', formula: f, caller_aa, call_line, call_xpath }); var opts = { conn: conn, @@ -3170,7 +3186,7 @@ function callGetter(conn, aa_address, getter, args, stateVars, objValidationStat objValidationState: objValidationState, address: aa_address }; - exports.evaluate(opts, astTrace, xpath, function (err, res) { + exports.evaluate(opts, astTrace, '/getters', function (err, res) { if (res === null) return cb(err.formattedError || "formula " + f + " failed: " + err); if (!locals[getter]) @@ -3200,7 +3216,7 @@ function callGetter(conn, aa_address, getter, args, stateVars, objValidationStat objValidationState: objValidationState, address: aa_address }; - exports.evaluate(call_opts, astTrace, xpath, function (err, res) { + exports.evaluate(call_opts, astTrace, '/getters', function (err, res) { if (res === null) { addAstTrace({ system: 'error in getter', aa: aa_address, formula: opts.formula, getter: getter }); return cb(err.formattedError || "formula " + call_formula + " failed: " + err); From 8874aa01353d7e997277a7ed1b62c8b1c48efce1 Mon Sep 17 00:00:00 2001 From: Evgenii Stulnikov Date: Fri, 13 Feb 2026 00:16:44 +0300 Subject: [PATCH 2/2] refactor: remove unnecessary xpath tracking and simplify error context handling --- formula/error/errorFormatter.js | 139 +++++++++++--------------------- formula/evaluation.js | 25 ++---- 2 files changed, 54 insertions(+), 110 deletions(-) diff --git a/formula/error/errorFormatter.js b/formula/error/errorFormatter.js index d181360a..7f54a162 100644 --- a/formula/error/errorFormatter.js +++ b/formula/error/errorFormatter.js @@ -15,21 +15,16 @@ const FRAME_TYPES = { FUNCTION: 'function', }; -function normalizeXpath(xpath) { - return typeof xpath === 'string' ? xpath.trim() : xpath; -} - const SPECIAL_OPS = { BOUNCE: 'bounce', REQUIRE: 'require', COMPARISON: 'comparison', }; - function getFormulaByLine(formula, line) { if (!formula) return ''; const lines = formula.split('\n').map(l => l.trim()); - return lines[line - 1] || formula; + return lines[line - 1] || ''; } function hasLineInFormula(formula, line) { @@ -38,15 +33,12 @@ function hasLineInFormula(formula, line) { } function pickBestFormulaForLine(formulas, line, currentFormula) { - if (!Array.isArray(formulas) || !formulas.length) { - return currentFormula; - } + if (!Array.isArray(formulas) || !formulas.length) return undefined; const withLine = formulas.filter(f => hasLineInFormula(f, line)); - const candidates = withLine.length ? withLine : formulas; + if (!withLine.length) return undefined; - const best = candidates.includes(currentFormula) ? currentFormula : candidates[0]; - return best || currentFormula; + return withLine.includes(currentFormula) ? currentFormula : withLine[0]; } function getErrorMessage(error) { @@ -67,7 +59,8 @@ function unwrapAstNode(node) { } function formatComparisonContext(context) { - const { left = {}, right = {}, op = '' } = context; + const { left, right, op } = context; + if (!left || !right || !op) return ''; const leftCode = renderOp(unwrapAstNode(left.var_name)); const rightCode = renderOp(unwrapAstNode(right.var_name)); @@ -105,14 +98,13 @@ function collectFunctionsAfterGetter(frames, getterIndex) { return { functionsAfterGetter, getterFuncName }; } -function buildAAFrame(frame, nextFrame, traceLine, defaultXpath) { +function buildAAFrame(frame, nextFrame, traceLine) { const aaFrame = { type: FRAME_TYPES.AA, aa: frame.aa }; - aaFrame.xpath = normalizeXpath(frame.xpath) ?? normalizeXpath(defaultXpath); - aaFrame.line = nextFrame?.call_line ?? traceLine; + if (frame.xpath !== undefined) aaFrame.xpath = frame.xpath; - if (aaFrame.xpath === undefined) delete aaFrame.xpath; - if (aaFrame.line === undefined) delete aaFrame.line; + const line = nextFrame?.call_line ?? traceLine; + if (line !== undefined) aaFrame.line = line; return aaFrame; } @@ -121,25 +113,20 @@ function buildGetterFrame(frame, getterFuncName, functionsAfterGetter, traceLine const getterFrame = { type: FRAME_TYPES.GETTER, aa: frame.aa, - name: getterFuncName ?? frame.name, }; - if (frame.xpath !== undefined) { - getterFrame.xpath = normalizeXpath(frame.xpath); - } else { - const firstFunc = functionsAfterGetter.find(f => f?.def_xpath !== undefined); - if (firstFunc?.def_xpath !== undefined) getterFrame.xpath = normalizeXpath(firstFunc.def_xpath); - } + const name = getterFuncName ?? frame.name; + if (name !== undefined) getterFrame.name = name; + if (frame.xpath !== undefined) getterFrame.xpath = frame.xpath; if (functionsAfterGetter.length > 1) { - getterFrame.line = functionsAfterGetter[1]?.call_line ?? traceLine; + const line = functionsAfterGetter[1]?.call_line ?? traceLine; + if (line !== undefined) getterFrame.line = line; } else { - getterFrame.line = traceLine ?? frame.call_line; + const line = traceLine ?? frame.call_line; + if (line !== undefined) getterFrame.line = line; } - if (getterFrame.name === undefined) delete getterFrame.name; - if (getterFrame.line === undefined) delete getterFrame.line; - return getterFrame; } @@ -149,24 +136,18 @@ function buildFunctionFrame(frame, nextFrame, traceLine) { name: frame.name || '', }; - if (frame.def_xpath !== undefined) { - funcFrame.xpath = normalizeXpath(frame.def_xpath); - } + if (frame.def_xpath !== undefined) funcFrame.xpath = frame.def_xpath; - const nextIsFunc = nextFrame?.type === FRAME_TYPES.FUNCTION; - const hasNextCallLine = nextIsFunc && nextFrame.call_line !== undefined; - if (hasNextCallLine) { + if (nextFrame?.type === FRAME_TYPES.FUNCTION && nextFrame.call_line !== undefined) { funcFrame.line = nextFrame.call_line; - } else { + } else if (traceLine !== undefined) { funcFrame.line = traceLine; } - if (funcFrame.line === undefined) delete funcFrame.line; - return funcFrame; } -function buildTraceFromFrames(frames, traceLine, defaultXpath) { +function buildTraceFromFrames(frames, traceLine) { if (!Array.isArray(frames) || !frames.length) return null; const getterIndex = findGetterIndex(frames); @@ -186,7 +167,7 @@ function buildTraceFromFrames(frames, traceLine, defaultXpath) { case FRAME_TYPES.AA: if (added.aa) break; added.aa = true; - result.push(buildAAFrame(frame, frames[i + 1], traceLine, defaultXpath)); + result.push(buildAAFrame(frame, frames[i + 1], traceLine)); break; case FRAME_TYPES.GETTER: @@ -200,8 +181,7 @@ function buildTraceFromFrames(frames, traceLine, defaultXpath) { type: FRAME_TYPES.FUNCTION, name: lastFunc.name || '', }; - if (lastFunc?.def_xpath !== undefined) funcFrame.xpath = normalizeXpath(lastFunc.def_xpath); - else if (frame?.xpath !== undefined) funcFrame.xpath = normalizeXpath(frame.xpath); + if (lastFunc.def_xpath !== undefined) funcFrame.xpath = lastFunc.def_xpath; if (traceLine !== undefined) funcFrame.line = traceLine; result.push(funcFrame); } @@ -243,27 +223,16 @@ function collectLinesFromArr(arr, lines = new Set()) { function handleEnterFunc(event, state) { const { framesStack, funcFormulas, funcFormulaStack } = state; - state.inFunc = true; if (event.name) state.lastNamedFunc = event.name; if (event.name && event.formula) funcFormulas.set(event.name, event.formula); if (event.formula) funcFormulaStack.push(event.formula); - let def_line = event.def_line; - if (def_line === undefined && typeof event.formula === 'string' && typeof event.name === 'string') { - const needle = '$' + event.name; - const lines = event.formula.split('\n'); - const idx = lines.findIndex(l => l.includes(needle)); - if (idx !== -1) def_line = idx + 1; - } - framesStack.push({ type: FRAME_TYPES.FUNCTION, name: event.name || '', aa: event.aa, - def_xpath: normalizeXpath(event.xpath), - def_line, + def_xpath: event.xpath, call_line: event.call_line, - call_xpath: normalizeXpath(event.call_xpath), }); } @@ -278,7 +247,6 @@ function removeLastFrameByType(framesStack, type, matchAA) { } function handleExitFunc(_, state) { - state.inFunc = false; state.lastNamedFunc = undefined; if (state.funcFormulaStack.length) state.funcFormulaStack.pop(); removeLastFrameByType(state.framesStack, FRAME_TYPES.FUNCTION); @@ -292,7 +260,7 @@ function handleEnterAA(event, state) { state.ownerAA = event.aa; state.ownerFormula = event.formula; - framesStack.push({ type: FRAME_TYPES.AA, aa: event.aa, xpath: normalizeXpath(event.xpath) }); + framesStack.push({ type: FRAME_TYPES.AA, aa: event.aa, xpath: event.xpath }); if (!aaFormulas.has(event.aa)) { aaFormulas.set(event.aa, new Set()); @@ -315,10 +283,8 @@ function handleEnterGetters(event, state) { type: FRAME_TYPES.GETTER, aa: event.aa, name: event.getter || event.name, - caller_aa: event.caller_aa, call_line: event.call_line, - call_xpath: normalizeXpath(event.call_xpath), - xpath: normalizeXpath(event.xpath), + xpath: event.xpath, }); } @@ -445,44 +411,36 @@ function resolveErrorLine(errJson, state) { } if (line === undefined) line = state.lastTraceLine; - if (state.inFunc) state.lastFormula = state.getters.get(state.lastAA) || state.lastFormula; return { line, allLinesFromArr }; } function resolveTargetFormula(line, state) { - const { snapshotsByLine, aaPath, gettersAA, aaFormulas, lastFormula, targetSnapshot } = state; + const { snapshotsByLine, gettersAA, aaFormulas, lastFormula, targetSnapshot } = state; let effectiveFormula = targetSnapshot?.formula || lastFormula; if (!effectiveFormula && snapshotsByLine?.[line]) { - effectiveFormula = selectBestSnapshot(snapshotsByLine[line])?.formula || effectiveFormula; + effectiveFormula = selectBestSnapshot(snapshotsByLine[line])?.formula; } - if (aaFormulas && line !== undefined) { - if (hasLineInFormula(effectiveFormula, line)) { - return effectiveFormula; - } + if (!aaFormulas || line === undefined) return effectiveFormula; - const targetAA = gettersAA || aaPath?.[0]; - if (targetAA && aaFormulas.has(targetAA)) { - const formulasSet = aaFormulas.get(targetAA); - const formulas = Array.isArray(formulasSet) ? formulasSet : Array.from(formulasSet); - const found = pickBestFormulaForLine(formulas, line, effectiveFormula); - if (hasLineInFormula(found, line)) { - return found; - } - } + if (hasLineInFormula(effectiveFormula, line)) return effectiveFormula; - for (const [, formulasSet] of aaFormulas) { - const formulas = Array.isArray(formulasSet) ? formulasSet : Array.from(formulasSet); - const found = formulas.find(f => hasLineInFormula(f, line)); - if (found) { - return found; - } - } + if (gettersAA && aaFormulas.has(gettersAA)) { + const formulasSet = aaFormulas.get(gettersAA); + const formulas = Array.isArray(formulasSet) ? formulasSet : Array.from(formulasSet); + const found = pickBestFormulaForLine(formulas, line, effectiveFormula); + if (hasLineInFormula(found, line)) return found; + } + + for (const [, formulasSet] of aaFormulas) { + const formulas = Array.isArray(formulasSet) ? formulasSet : Array.from(formulasSet); + const found = formulas.find(f => hasLineInFormula(f, line)); + if (found) return found; } - return effectiveFormula; + return undefined; } function createInitialState() { @@ -494,7 +452,6 @@ function createInitialState() { lastAA: '', lastFormula: '', lastTraceLine: undefined, - inFunc: false, dontShowFormat: false, lastGettersAA: undefined, gettersAAAtLastLine: undefined, @@ -535,7 +492,6 @@ function buildContext(errJson) { } return { - aaPath: state.aaPath, lastFormula: lastFormulaAtTarget, line, allLinesFromArr, @@ -557,16 +513,15 @@ function processNestedError(nestedError, line) { } function buildCodeLines(ctx) { - const { allLinesFromArr, line, lastFormula, snapshotsByLine, aaPath, gettersAA, aaFormulas, targetSnapshot } = ctx; + const { allLinesFromArr, line, lastFormula, snapshotsByLine, gettersAA, aaFormulas, targetSnapshot } = ctx; - const formulaState = { + const effectiveFormula = resolveTargetFormula(line, { snapshotsByLine, - aaPath, gettersAA, aaFormulas, lastFormula, - }; - const effectiveFormula = resolveTargetFormula(line, { ...formulaState, targetSnapshot }); + targetSnapshot, + }); const hasLines = allLinesFromArr.length > 0; const hasLine = line !== undefined; @@ -627,7 +582,7 @@ function formatError(errJson) { : { aa: undefined, xpath: undefined }; const framesForTrace = extractFramesForTrace(snapshotsByLine, traceLine, traceCriteria); if (framesForTrace?.length) { - result.trace = buildTraceFromFrames(framesForTrace, traceLine, errJson.xpath); + result.trace = buildTraceFromFrames(framesForTrace, traceLine); } return result; diff --git a/formula/evaluation.js b/formula/evaluation.js index 9faeb06b..1ed0e86f 100644 --- a/formula/evaluation.js +++ b/formula/evaluation.js @@ -1230,8 +1230,6 @@ exports.evaluate = function (opts, astTrace, xpath, callback) { if (_.intersection(args, scopeVarNames).length > 0) return setFatalError("some args of " + var_name + " would shadow some local vars", { arr }, false, cb); var f = new Func(args, body, scopeVarNames, formula, xpath); - if (arr.line !== undefined) - f.def_line = arr.line; assignField(locals, var_name, f); return cb(true); } @@ -2317,17 +2315,17 @@ exports.evaluate = function (opts, astTrace, xpath, callback) { var func = funcInfo.local; var args = getArgs(func.args.length); caller = function (res_cb) { - callFunction(func, args, undefined, { aa: address, call_line: arr.line, call_xpath: xpath }, res_cb); + callFunction(func, args, undefined, { aa: address, call_line: arr.line }, res_cb); }; } else if (funcInfo.remote) { var fargs = (func) => getArgs(func.args.length); caller = function (res_cb) { - callGetter(conn, funcInfo.remote.remote_aa, funcInfo.remote.func_name, fargs, stateVars, objValidationState, astTrace, xpath, { caller_aa: address, call_line: arr.line, call_xpath: xpath }, (err, r) => { + callGetter(conn, funcInfo.remote.remote_aa, funcInfo.remote.func_name, fargs, stateVars, objValidationState, astTrace, xpath, { call_line: arr.line }, (err, r) => { if (err) return setFatalError(err, { arr }, false, res_cb); - astTrace.push({system: 'exit from getters', aa: funcInfo.remote.remote_aa, xpath: '/getters', caller_aa: address, call_line: arr.line, call_xpath: xpath}); + astTrace.push({system: 'exit from getters', aa: funcInfo.remote.remote_aa, xpath: '/getters'}); res_cb(r); }); }; @@ -2470,7 +2468,7 @@ exports.evaluate = function (opts, astTrace, xpath, callback) { return cb(false); if (err) return setFatalError("arguments failed: " + err, { arr }, false, cb); - callFunction(func, args, func_name, { aa: address, call_line: arr.line, call_xpath: xpath }, cb); + callFunction(func, args, func_name, { aa: address, call_line: arr.line }, cb); } ); break; @@ -2506,10 +2504,10 @@ exports.evaluate = function (opts, astTrace, xpath, callback) { return cb(false); if (err) return setFatalError(err, { arr }, false, cb); - callGetter(conn, remote_aa, func_name, args, stateVars, objValidationState, astTrace, xpath, { caller_aa: address, call_line: arr.line, call_xpath: xpath }, (err, res) => { + callGetter(conn, remote_aa, func_name, args, stateVars, objValidationState, astTrace, xpath, { call_line: arr.line }, (err, res) => { if (err) return setFatalError(err, { arr }, false, cb); - astTrace.push({system: 'exit from getters', aa: remote_aa, xpath: '/getters', caller_aa: address, call_line: arr.line, call_xpath: xpath}); + astTrace.push({system: 'exit from getters', aa: remote_aa, xpath: '/getters'}); cb(res); }); }); @@ -2909,7 +2907,6 @@ exports.evaluate = function (opts, astTrace, xpath, callback) { function callFunction(func, args, func_name, callInfo, cb) { const frameAA = callInfo && callInfo.aa ? callInfo.aa : address; const call_line = callInfo ? callInfo.call_line : undefined; - const call_xpath = callInfo ? callInfo.call_xpath : undefined; current_xpath_stack.push(current_xpath); if (typeof func.xpath === 'string') current_xpath = func.xpath; @@ -2920,8 +2917,6 @@ exports.evaluate = function (opts, astTrace, xpath, callback) { xpath: func.xpath || '', aa: frameAA, call_line, - call_xpath, - def_line: func.def_line, }); if (early_return !== undefined) throw Error("function called after a return"); @@ -2968,10 +2963,6 @@ exports.evaluate = function (opts, astTrace, xpath, callback) { if (_.intersection(args, scopeVarNames).length > 0) return setFatalError("some args of anonymous function would shadow some local vars", { arr }, false, cb); var f = new Func(args, body, scopeVarNames, formula, xpath); - if (func_expr.line !== undefined) - f.def_line = func_expr.line; - else if (arr.line !== undefined) - f.def_line = arr.line; cb({ local: f }); } else if (func_expr[0] === 'local_var') { @@ -3168,11 +3159,9 @@ function callGetter(conn, aa_address, getter, args, stateVars, objValidationStat // rewrite storage size with the storage size of the AA being called objValidationState.storage_size = storage_size; var f = getFormula(arrBaseDefinition[1].getters); - const caller_aa = callerInfo && callerInfo.caller_aa; const call_line = callerInfo && callerInfo.call_line; - const call_xpath = callerInfo && callerInfo.call_xpath; - addAstTrace({ system: 'enter to getters', aa: aa_address, xpath: '/getters', formula: f, caller_aa, call_line, call_xpath }); + addAstTrace({ system: 'enter to getters', aa: aa_address, xpath: '/getters', formula: f, call_line }); var opts = { conn: conn,