/*
 * Copyright (C) 2016-2024 Apple Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "config.h"
#include "StackFrame.h"

#include "CodeBlock.h"
#include "DebuggerPrimitives.h"
#include "FunctionExecutable.h"
#include "JSCellInlines.h"
#include "JSFunctionInlines.h"
#include <wtf/text/MakeString.h>

namespace JSC {

StackFrame::StackFrame(VM& vm, JSCell* owner, JSCell* callee)
    : m_frameData(JSFrameData {
        WriteBarrier<JSCell>(vm, owner, callee),
        WriteBarrier<CodeBlock>(),
        BytecodeIndex()
    })
{
}

StackFrame::StackFrame(VM& vm, JSCell* owner, JSCell* callee, CodeBlock* codeBlock, BytecodeIndex bytecodeIndex)
    : m_frameData(JSFrameData {
        WriteBarrier<JSCell>(vm, owner, callee),
        WriteBarrier<CodeBlock>(vm, owner, codeBlock),
        bytecodeIndex
    })
{
}

StackFrame::StackFrame(VM& vm, JSCell* owner, JSCell* callee, CodeBlock* codeBlock, BytecodeIndex bytecodeIndex, bool isAsyncFrame)
    : m_frameData(JSFrameData {
        WriteBarrier<JSCell>(vm, owner, callee),
        WriteBarrier<CodeBlock>(vm, owner, codeBlock),
        bytecodeIndex,
        isAsyncFrame
    })
{
}

StackFrame::StackFrame(VM& vm, JSCell* owner, CodeBlock* codeBlock, BytecodeIndex bytecodeIndex)
    : m_frameData(JSFrameData {
        WriteBarrier<JSCell>(),
        WriteBarrier<CodeBlock>(vm, owner, codeBlock),
        bytecodeIndex
    })
{
}

StackFrame::StackFrame(Wasm::IndexOrName indexOrName)
    : m_frameData(WasmFrameData { WTF::move(indexOrName), 0 })
{
}

StackFrame::StackFrame(Wasm::IndexOrName indexOrName, size_t functionIndex)
    : m_frameData(WasmFrameData { WTF::move(indexOrName), functionIndex })
{
}

StackFrame::StackFrame(VM& vm, JSCell* owner, JSCell* callee, bool isAsyncFrame)
    : m_frameData(JSFrameData {
        WriteBarrier<JSCell>(vm, owner, callee),
        WriteBarrier<CodeBlock>(),
        BytecodeIndex(),
        isAsyncFrame
    })
{
}

bool StackFrame::hasBytecodeIndex() const
{
    if (auto* jsFrame = std::get_if<JSFrameData>(&m_frameData))
        return !!jsFrame->bytecodeIndex;
    return false;
}

BytecodeIndex StackFrame::bytecodeIndex() const
{
    ASSERT(hasBytecodeIndex());
    return std::get<JSFrameData>(m_frameData).bytecodeIndex;
}

template<typename Visitor>
void StackFrame::visitAggregate(Visitor& visitor)
{
    WTF::switchOn(m_frameData,
        [&visitor](const JSFrameData& jsFrame) {
            if (jsFrame.callee)
                visitor.append(jsFrame.callee);
            if (jsFrame.codeBlock)
                visitor.append(jsFrame.codeBlock);
        },
        [](const WasmFrameData&) { }
    );
}
template void StackFrame::visitAggregate(AbstractSlotVisitor&);
template void StackFrame::visitAggregate(SlotVisitor&);

bool StackFrame::isMarked(VM& vm) const
{
    return WTF::switchOn(m_frameData,
        [&vm](const JSFrameData& jsFrame) {
            return (!jsFrame.callee || vm.heap.isMarked(jsFrame.callee.get())) && (!jsFrame.codeBlock || vm.heap.isMarked(jsFrame.codeBlock.get()));
        },
        [](const WasmFrameData&) { return true; }
    );
}

SourceID StackFrame::sourceID() const
{
    if (auto* jsFrame = std::get_if<JSFrameData>(&m_frameData)) {
        if (!jsFrame->codeBlock)
            return noSourceID;
        return jsFrame->codeBlock->ownerExecutable()->sourceID();
    }
    return noSourceID;
}

static String processSourceURL(VM& vm, const JSC::StackFrame& frame, const String& sourceURL)
{
    if (vm.clientData && (!protocolIsInHTTPFamily(sourceURL) && !protocolIs(sourceURL, "blob"_s))) {
        String overrideURL = vm.clientData->overrideSourceURL(frame, sourceURL);
        if (!overrideURL.isNull())
            return overrideURL;
    }

    if (!sourceURL.isNull())
        return sourceURL;
    return emptyString();
}

String StackFrame::sourceURL(VM& vm) const
{
    return WTF::switchOn(m_frameData,
        [&vm, this](const JSFrameData& jsFrame) -> String {
            if (isAsyncFrameWithoutCodeBlock()) {
                ASSERT(jsFrame.callee);
                ASSERT(!jsFrame.codeBlock);
                JSFunction* calleeFn = jsDynamicCast<JSFunction*>(jsFrame.callee.get());
                return processSourceURL(vm, *this, calleeFn->jsExecutable()->sourceURL());
            }

            if (!jsFrame.codeBlock)
                return "[native code]"_s;
            return processSourceURL(vm, *this, jsFrame.codeBlock->ownerExecutable()->sourceURL());
        },
        [](const WasmFrameData& wasmFrame) -> String {
            auto moduleName = wasmFrame.functionIndexOrName.moduleName();
            if (moduleName.empty())
                return makeString("wasm-function["_s, wasmFrame.functionIndex, ']');
            return makeString(moduleName, ":wasm-function["_s, wasmFrame.functionIndex, ']');
        }
    );
}

String StackFrame::sourceURLStripped(VM& vm) const
{
    return WTF::switchOn(m_frameData,
        [&vm, this](const JSFrameData& jsFrame) -> String {
            if (isAsyncFrameWithoutCodeBlock()) {
                ASSERT(jsFrame.callee);
                ASSERT(!jsFrame.codeBlock);
                JSFunction* calleeFn = jsDynamicCast<JSFunction*>(jsFrame.callee.get());
                return processSourceURL(vm, *this, calleeFn->jsExecutable()->sourceURLStripped());
            }

            if (!jsFrame.codeBlock)
                return "[native code]"_s;
            return processSourceURL(vm, *this, jsFrame.codeBlock->ownerExecutable()->sourceURLStripped());
        },
        [](const WasmFrameData& wasmFrame) -> String {
            auto moduleName = wasmFrame.functionIndexOrName.moduleName();
            if (moduleName.empty())
                return makeString("wasm-function["_s, wasmFrame.functionIndex, ']');
            return makeString(moduleName, ":wasm-function["_s, wasmFrame.functionIndex, ']');
        }
    );
}

String StackFrame::functionName(VM& vm) const
{
    return WTF::switchOn(m_frameData,
        [&vm](const JSFrameData& jsFrame) -> String {
            if (jsFrame.codeBlock) {
                switch (jsFrame.codeBlock->codeType()) {
                case EvalCode:
                    return "eval code"_s;
                case ModuleCode:
                    return "module code"_s;
                case GlobalCode:
                    return "global code"_s;
                case FunctionCode:
                    break;
                }
            }
            String name;
            if (jsFrame.callee && jsFrame.callee->isObject())
                name = getCalculatedDisplayName(vm, jsCast<JSObject*>(jsFrame.callee.get())).impl();
            else if (jsFrame.codeBlock) {
                if (auto* executable = jsDynamicCast<FunctionExecutable*>(jsFrame.codeBlock->ownerExecutable()))
                    name = executable->ecmaName().impl();
            }

            if (name.isNull())
                return emptyString();

            if (jsFrame.m_isAsyncFrame)
                return makeString("async "_s, name);

            return name;
        },
        [](const WasmFrameData& wasmFrame) -> String {
            if (wasmFrame.functionIndexOrName.isEmpty() || !wasmFrame.functionIndexOrName.nameSection())
                return "wasm-stub"_s;
            if (wasmFrame.functionIndexOrName.isIndex())
                return WTF::toString(wasmFrame.functionIndexOrName.index());
            return WTF::toString(wasmFrame.functionIndexOrName.name()->span());
        }
    );
}

LineColumn StackFrame::computeLineAndColumn() const
{
    if (auto* jsFrame = std::get_if<JSFrameData>(&m_frameData)) {
        if (!jsFrame->codeBlock)
            return { };
        auto lineColumn = jsFrame->codeBlock->lineColumnForBytecodeIndex(jsFrame->bytecodeIndex);

        ScriptExecutable* executable = jsFrame->codeBlock->ownerExecutable();
        if (std::optional<int> overrideLineNumber = executable->overrideLineNumber(jsFrame->codeBlock->vm()))
            lineColumn.line = overrideLineNumber.value();

        return lineColumn;
    }
    return { };
}

String StackFrame::toString(VM& vm) const
{
    String functionName = this->functionName(vm);
    String sourceURL = this->sourceURLStripped(vm);

    if (sourceURL.isEmpty() || !hasLineAndColumnInfo())
        return makeString(functionName, '@', sourceURL);

    auto lineColumn = computeLineAndColumn();
    return makeString(functionName, '@', sourceURL, ':', lineColumn.line, ':', lineColumn.column);
}

} // namespace JSC
