// Copyright (C) 2018 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
// Qt-Security score:significant


#include "qv4module_p.h"

#include <private/qv4context_p.h>
#include <private/qv4identifiertable_p.h>
#include <private/qv4mm_p.h>
#include <private/qv4stackframe_p.h>
#include <private/qv4symbol_p.h>
#include <private/qv4vme_moth_p.h>

#include <QtCore/qscopeguard.h>

using namespace QV4;

DEFINE_OBJECT_VTABLE(Module);

void Heap::Module::init(ExecutionEngine *engine, ExecutableCompilationUnit *moduleUnit)
{
    Object::init();

    // This is a back pointer and there is no need to call addref() on the unit, because the unit
    // owns this object instead.
    unit = moduleUnit;
    self.set(engine, this);

    Function *moduleFunction = unit->runtimeFunctions[unit->unitData()->indexOfRootFunction];

    const uint locals = moduleFunction->compiledFunction->nLocals;
    const size_t requiredMemory = sizeof(QV4::CallContext::Data) - sizeof(Value) + sizeof(Value) * locals;
    scope.set(engine, engine->memoryManager->allocManaged<QV4::CallContext>(requiredMemory, moduleFunction->internalClass));
    scope->init();
    scope->outer.set(engine, engine->rootContext()->d());
    scope->locals.size = locals;
    scope->locals.alloc = locals;
    scope->nArgs = 0;

    // Prepare the temporal dead zone
    scope->setupLocalTemporalDeadZone(moduleFunction->compiledFunction);

    Scope valueScope(engine);

    // It's possible for example to re-export an import, for example:
    //     import * as foo from "./bar.js"
    //     export { foo }
    // Since we don't add imports to the locals, it won't be found typically.
    // Except now we add imports at the end of the internal class in the index
    // space past the locals, so that resolveExport can find it.
    {
        Scoped<QV4::InternalClass> ic(valueScope, scope->internalClass);

        for (uint i = 0; i < unit->unitData()->importEntryTableSize; ++i) {
            const CompiledData::ImportEntry &import = unit->unitData()->importEntryTable()[i];
            ic = ic->addMember(engine->identifierTable->asPropertyKey(unit->runtimeStrings[import.localName]), Attr_NotConfigurable);
        }
        scope->internalClass.set(engine, ic->d());
    }


    Scoped<QV4::Module> This(valueScope, this);
    ScopedString name(valueScope, engine->newString(QStringLiteral("Module")));
    This->insertMember(engine->symbol_toStringTag(), name, Attr_ReadOnly);
    This->setPrototypeUnchecked(nullptr);
}

void Module::evaluate()
{
    if (d()->evaluated)
        return;
    d()->evaluated = true;

    ExecutableCompilationUnit *unit = d()->unit;

    unit->evaluateModuleRequests();

    ExecutionEngine *v4 = engine();
    Function *moduleFunction = unit->runtimeFunctions[unit->unitData()->indexOfRootFunction];
    JSTypesStackFrame frame;
    frame.init(moduleFunction, nullptr, 0);
    frame.setupJSFrame(v4->jsStackTop, Value::undefinedValue(), d()->scope,
                       Value::undefinedValue(), Value::undefinedValue());

    frame.push(v4);
    v4->jsStackTop += frame.requiredJSStackFrameSize();
    auto frameCleanup = qScopeGuard([&frame, v4]() {
        frame.pop(v4);
    });
    Moth::VME::exec(&frame, v4);
}

const Value *Module::resolveExport(PropertyKey id) const
{
    if (d()->unit->isESModule()) {
        if (!id.isString())
            return nullptr;
        Scope scope(engine());
        ScopedString name(scope, id.asStringOrSymbol());
        return d()->unit->resolveExport(name);
    } else {
        InternalClassEntry entry = d()->scope->internalClass->find(id);
        if (entry.isValid())
            return &d()->scope->locals[entry.index];
        return nullptr;
    }
}

ReturnedValue Module::virtualGet(const Managed *m, PropertyKey id, const Value *receiver, bool *hasProperty)
{
    if (id.isSymbol())
        return Object::virtualGet(m, id, receiver, hasProperty);

    const Module *module = static_cast<const Module *>(m);
    const Value *v = module->resolveExport(id);
    if (hasProperty)
        *hasProperty = v != nullptr;
    if (!v)
        return Encode::undefined();
    if (v->isEmpty()) {
        Scope scope(m->engine());
        ScopedValue propName(scope, id.toStringOrSymbol(scope.engine));
        return scope.engine->throwReferenceError(propName);
    }
    return v->asReturnedValue();
}

PropertyAttributes Module::virtualGetOwnProperty(const Managed *m, PropertyKey id, Property *p)
{
    if (id.isSymbol())
        return Object::virtualGetOwnProperty(m, id, p);

    const Module *module = static_cast<const Module *>(m);
    const Value *v = module->resolveExport(id);
    if (!v) {
        if (p)
            p->value = Encode::undefined();
        return Attr_Invalid;
    }
    if (p)
        p->value = v->isEmpty() ? Encode::undefined() : v->asReturnedValue();
    if (v->isEmpty()) {
        Scope scope(m->engine());
        ScopedValue propName(scope, id.toStringOrSymbol(scope.engine));
        scope.engine->throwReferenceError(propName);
    }
    return Attr_Data | Attr_NotConfigurable;
}

bool Module::virtualHasProperty(const Managed *m, PropertyKey id)
{
    if (id.isSymbol())
        return Object::virtualHasProperty(m, id);

    const Module *module = static_cast<const Module *>(m);
    const Value *v = module->resolveExport(id);
    return v != nullptr;
}

bool Module::virtualPreventExtensions(Managed *)
{
    return true;
}

bool Module::virtualDefineOwnProperty(Managed *, PropertyKey, const Property *, PropertyAttributes)
{
    return false;
}

bool Module::virtualPut(Managed *, PropertyKey, const Value &, Value *)
{
    return false;
}

bool Module::virtualDeleteProperty(Managed *m, PropertyKey id)
{
    if (id.isSymbol())
        return Object::virtualDeleteProperty(m, id);
    const Module *module = static_cast<const Module *>(m);
    const Value *v = module->resolveExport(id);
    if (v)
        return false;
    return true;
}

namespace {
struct ModuleNamespaceIterator : ObjectOwnPropertyKeyIterator
{
    QStringList exportedNames;
    int exportIndex = 0;
    ModuleNamespaceIterator(const QStringList &names) : exportedNames(names) {}
    ~ModuleNamespaceIterator() override = default;
    PropertyKey next(const Object *o, Property *pd = nullptr, PropertyAttributes *attrs = nullptr) override;
};
} // namespace

PropertyKey ModuleNamespaceIterator::next(const Object *o, Property *pd, PropertyAttributes *attrs)
{
    const Module *module = static_cast<const Module *>(o);
    if (exportIndex < exportedNames.size()) {
        if (attrs)
            *attrs = Attr_Data;
        Scope scope(module->engine());
        ScopedString exportName(scope, scope.engine->newString(exportedNames.at(exportIndex)));
        exportIndex++;
        if (pd) {
            const Value *v = module->resolveExport(exportName->toPropertyKey());
            if (!v || v->isEmpty())
                scope.engine->throwReferenceError(exportName);
            else
                pd->value = *v;
        }
        return exportName->toPropertyKey();
    }
    return ObjectOwnPropertyKeyIterator::next(o, pd, attrs);
}

OwnPropertyKeyIterator *Module::virtualOwnPropertyKeys(const Object *o, Value *target)
{
    const Module *module = static_cast<const Module *>(o);
    *target = *o;

    QStringList names;
    if (module->d()->unit->isESModule()) {
        names = module->d()->unit->exportedNames();
    } else {
        QV4::Scope scope(module->engine());
        QV4::Scoped<InternalClass> scopeClass(scope, module->d()->scope->internalClass);
        for (uint i = 0, end = scopeClass->d()->size; i < end; ++i) {
            QV4::ScopedValue key(scope, scopeClass->d()->keyAt(i));
            names << key->toQString();
        }
    }

    return new ModuleNamespaceIterator(names);
}

Heap::Object *Module::virtualGetPrototypeOf(const Managed *)
{
    return nullptr;
}

bool Module::virtualSetPrototypeOf(Managed *, const Object *proto)
{
    return proto == nullptr;
}

bool Module::virtualIsExtensible(const Managed *)
{
    return false;
}
