1/*
2 * Copyright (c) 2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16#include "importExportAccessValid.h"
17#include "helpers.h"
18#include "ir/expressions/callExpression.h"
19#include "checker/types/ets/etsObjectType.h"
20#include "ir/module/importSpecifier.h"
21#include "ir/module/importNamespaceSpecifier.h"
22#include "ir/module/importDefaultSpecifier.h"
23#include "ir/expressions/identifier.h"
24#include "ir/ets/etsImportDeclaration.h"
25#include "checker/types/signature.h"
26
27namespace ark::es2panda::compiler::ast_verifier {
28
29[[nodiscard]] CheckResult ImportExportAccessValid::operator()(CheckContext &ctx, const ir::AstNode *ast)
30{
31    std::unordered_set<std::string> importedVariables {};
32    if (ast->IsETSImportDeclaration()) {
33        const auto importDecl = ast->AsETSImportDeclaration()->Specifiers();
34        const auto name = [](ir::AstNode *const specifier) {
35            if (specifier->IsImportNamespaceSpecifier()) {
36                return specifier->AsImportNamespaceSpecifier()->Local()->Name();
37            }
38            if (specifier->IsImportSpecifier()) {
39                return specifier->AsImportSpecifier()->Local()->Name();
40            }
41            return specifier->AsImportDefaultSpecifier()->Local()->Name();
42        };
43        for (const auto import : importDecl) {
44            importedVariables.emplace(name(import));
45        }
46    }
47    if (ast->IsCallExpression()) {
48        const auto *callExpr = ast->AsCallExpression();
49        const auto *callee = callExpr->Callee();
50        if (callee != nullptr && callee->IsIdentifier() &&
51            !HandleImportExportIdentifier(importedVariables, callee->AsIdentifier(), callExpr)) {
52            ctx.AddCheckMessage("PROPERTY_NOT_VISIBLE_HERE(NOT_EXPORTED)", *callee, callee->Start());
53            return {CheckDecision::INCORRECT, CheckAction::CONTINUE};
54        }
55    }
56    if (ast->IsIdentifier() && !HandleImportExportIdentifier(importedVariables, ast->AsIdentifier(), nullptr)) {
57        ctx.AddCheckMessage("PROPERTY_NOT_VISIBLE_HERE(NOT_EXPORTED)", *ast, ast->Start());
58        return {CheckDecision::INCORRECT, CheckAction::CONTINUE};
59    }
60    return {CheckDecision::CORRECT, CheckAction::CONTINUE};
61}
62
63bool ImportExportAccessValid::ValidateExport(const varbinder::Variable *var)
64{
65    const auto *decl = var->Declaration();
66    if (decl == nullptr) {
67        return false;
68    }
69    const auto *node = decl->Node();
70    if (node == nullptr) {
71        return false;
72    }
73    return node->IsExported() || node->IsExportedType();
74}
75
76bool ImportExportAccessValid::InvariantImportExportMethod(const std::unordered_set<std::string> &importedVariables,
77                                                          const varbinder::Variable *varCallee,
78                                                          const ir::AstNode *callExpr, util::StringView name)
79{
80    auto *signature = callExpr->AsCallExpression()->Signature();
81    if (signature == nullptr || signature->Owner() == nullptr) {
82        // NOTE(vpukhov): Add a synthetic owner for dynamic signatures
83        ASSERT(callExpr->AsCallExpression()->Callee()->TsType()->HasTypeFlag(checker::TypeFlag::ETS_DYNAMIC_FLAG));
84        return true;
85    }
86
87    if (signature != nullptr && varCallee->Declaration() != nullptr && varCallee->Declaration()->Node() != nullptr &&
88        !IsContainedIn(varCallee->Declaration()->Node(), signature->Owner()->GetDeclNode()) &&
89        varCallee->Declaration()->Node() != signature->Owner()->GetDeclNode()) {
90        if (importedVariables.find(name.Mutf8()) != importedVariables.end() ||
91            importedVariables.find("") != importedVariables.end()) {
92            return ValidateExport(varCallee);
93        }
94        return false;
95    }
96    return true;
97}
98
99bool ImportExportAccessValid::InvariantImportExportVariable(const std::unordered_set<std::string> &importedVariables,
100                                                            const varbinder::Variable *var, const ir::Identifier *ident,
101                                                            util::StringView name)
102{
103    if (!var->HasFlag(varbinder::VariableFlags::LOCAL) && !var->HasFlag(varbinder::VariableFlags::VAR) &&
104        var->HasFlag(varbinder::VariableFlags::INITIALIZED) && var->Declaration() != nullptr &&
105        var->Declaration()->Node() != nullptr && !var->Declaration()->Node()->IsMethodDefinition() &&
106        !var->Declaration()->Node()->IsClassProperty()) {
107        auto varParent = var->Declaration()->Node()->Parent();
108        if (varParent != nullptr && !IsContainedIn(ident->Parent(), varParent) && ident->Parent() != varParent) {
109            if (var->GetScope() != nullptr && var->GetScope()->Parent() != nullptr &&
110                var->GetScope()->Parent()->IsGlobalScope() &&
111                ident->GetTopStatement() == varParent->GetTopStatement()) {
112                return true;
113            }
114            if (importedVariables.find(name.Mutf8()) != importedVariables.end() ||
115                importedVariables.find("") != importedVariables.end()) {
116                return ValidateExport(var);
117            }
118            return false;
119        }
120    }
121    return true;
122}
123
124bool ImportExportAccessValid::HandleImportExportIdentifier(std::unordered_set<std::string> &importedVariables,
125                                                           const ir::Identifier *ident, const ir::AstNode *callExpr)
126{
127    if (ident->IsReference()) {
128        const auto *var = ident->Variable();
129        if (var != nullptr) {
130            if (var->HasFlag(varbinder::VariableFlags::METHOD) && callExpr != nullptr) {
131                return InvariantImportExportMethod(importedVariables, var, callExpr, ident->Name());
132            }
133            return InvariantImportExportVariable(importedVariables, var, ident, ident->Name());
134        }
135    }
136    return true;
137}
138
139}  // namespace ark::es2panda::compiler::ast_verifier
140