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 "evaluate/debugInfoDeserialization/methodBuilder.h"
17#include "checker/ETSchecker.h"
18#include "libpandafile/file-inl.h"
19#include "libpandafile/method_data_accessor-inl.h"
20#include "libpandafile/proto_data_accessor-inl.h"
21#include "evaluate/helpers.h"
22
23namespace ark::es2panda::evaluate {
24
25namespace {
26
27std::string GetFieldName(size_t fieldIdx)
28{
29    std::stringstream sstream;
30    sstream << "field" << fieldIdx;
31    return sstream.str();
32}
33
34ArenaVector<ir::TypeNode *> GetFunctionParameters(checker::ETSChecker *checker, panda_file::MethodDataAccessor &mda)
35{
36    const auto &pf = mda.GetPandaFile();
37    ArenaVector<ir::TypeNode *> parameters(checker->Allocator()->Adapter());
38    mda.EnumerateTypesInProto(
39        [checker, &parameters, &pf = std::as_const(pf)](panda_file::Type type, panda_file::File::EntityId classId) {
40            auto *typeNode = helpers::PandaTypeToTypeNode(pf, type, classId, checker);
41            ASSERT(typeNode);
42            parameters.push_back(typeNode);
43        },
44        true);  // true -- skip `this` parameter
45
46    return parameters;
47}
48
49ir::ReturnStatement *CreateTypedReturnStatement(checker::ETSChecker *checker, ir::TypeNode *type)
50{
51    ASSERT(type);
52
53    if (type->IsETSPrimitiveType() && type->AsETSPrimitiveType()->GetPrimitiveType() == ir::PrimitiveType::VOID) {
54        return checker->AllocNode<ir::ReturnStatement>();
55    }
56
57    // Hack for correct validation. This function call won't be executed in compiled code,
58    // as the whole class declaration only mimics the real code loaded into runtime.
59
60    auto *allocator = checker->Allocator();
61    auto *apiClass = checker->AllocNode<ir::Identifier>(helpers::DEBUGGER_API_CLASS_NAME, allocator);
62    auto *prop =
63        checker->AllocNode<ir::Identifier>(helpers::CreateGetterName(panda_file::Type::TypeId::REFERENCE), allocator);
64    auto *callee = checker->AllocNode<ir::MemberExpression>(apiClass, prop, ir::MemberExpressionKind::PROPERTY_ACCESS,
65                                                            false, false);
66
67    ArenaVector<ir::Expression *> args(1, checker->AllocNode<ir::NumberLiteral>("0"), allocator->Adapter());
68    auto *callExpression = checker->AllocNode<ir::CallExpression>(callee, std::move(args), nullptr, false);
69
70    auto *asExpression = checker->AllocNode<ir::TSAsExpression>(callExpression, type->Clone(allocator, nullptr), false);
71    return checker->AllocNode<ir::ReturnStatement>(asExpression);
72}
73
74}  // namespace
75
76MethodBuilder::MethodBuilder(checker::ETSChecker *checker, panda_file::MethodDataAccessor &mda,
77                             ir::ModifierFlags classModifierFlags)
78    : checker_(checker), mda_(mda), params_(checker_->Allocator()->Adapter()), classModifierFlags_(classModifierFlags)
79{
80    methodName_ = util::UString(mda_.GetFullName(), checker_->Allocator()).View();
81    modifierFlags_ = ir::ModifierFlags::EXPORT | helpers::GetModifierFlags(mda_, true);
82}
83
84ir::AstNode *MethodBuilder::Build() &&
85{
86    ArenaVector<ir::Statement *> statements(checker_->Allocator()->Adapter());
87
88    CollectParametersAndReturnType();
89
90    bool isCtor = IsConstructor();
91    bool isCctor = IsStaticConstructor();
92
93    if (isCtor) {
94        auto *superConstructorCallStatement = CreateSuperConstructorExpressionCall();
95        statements.push_back(superConstructorCallStatement);
96    }
97
98    auto *retStatement = CreateTypedReturnStatement(checker_, returnType_);
99    statements.push_back(retStatement);
100
101    // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint)
102    auto *id = checker_->AllocNode<ir::Identifier>(methodName_, checker_->Allocator());
103    auto *body = CreateBody(std::move(statements));
104
105    if (isCtor) {
106        return CreateIrConstructor<false>(id, body);
107    }
108    if (isCctor) {
109        return CreateIrConstructor<true>(id, body);
110    }
111    return CreateIrMethod(id, body);
112}
113
114void MethodBuilder::CollectParametersAndReturnType()
115{
116    auto parameters = GetFunctionParameters(checker_, mda_);
117    auto *checker = checker_->Allocator();
118
119    // Start from 1, because 0 is return type
120    for (size_t idx = 1U; idx < parameters.size(); ++idx) {
121        util::UString paramName(GetFieldName(idx), checker);
122        // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint)
123        auto *paramIdent = checker_->AllocNode<ir::Identifier>(paramName.View(), parameters[idx], checker);
124        // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint)
125        auto *param = checker_->AllocNode<ir::ETSParameterExpression>(paramIdent, nullptr);
126        params_.push_back(param);
127    }
128
129    returnType_ = parameters[0];
130}
131
132ir::BlockStatement *MethodBuilder::CreateBody(ArenaVector<ir::Statement *> statements)
133{
134    bool isAbstractClass = ((classModifierFlags_ & ir::ModifierFlags::ABSTRACT) != 0);
135    bool isAbstractMethod = ((modifierFlags_ & ir::ModifierFlags::ABSTRACT) != 0);
136    bool needToCreateBody = !(isAbstractClass && isAbstractMethod);
137
138    ir::BlockStatement *body = nullptr;
139    if (needToCreateBody) {
140        // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint)
141        body = checker_->AllocNode<ir::BlockStatement>(checker_->Allocator(), std::move(statements));
142    }
143
144    return body;
145}
146
147ir::ExpressionStatement *MethodBuilder::CreateSuperConstructorExpressionCall()
148{
149    // NOTE: in future it may be necessary to pass non empty call args,
150    // but currently frontend do not check args number.
151    ArenaVector<ir::Expression *> callArguments(checker_->Allocator()->Adapter());
152
153    // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint)
154    auto *callee = checker_->AllocNode<ir::SuperExpression>();
155    // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint)
156    auto *superConstructorCall =
157        checker_->AllocNode<ir::CallExpression>(callee, std::move(callArguments), nullptr, false);
158    // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint)
159    return checker_->AllocNode<ir::ExpressionStatement>(superConstructorCall);
160}
161
162template <bool IS_STATIC>
163ir::AstNode *MethodBuilder::CreateIrConstructor(ir::Identifier *id, ir::BlockStatement *body)
164{
165    auto scriptFuncFlags = ir::ScriptFunctionFlags::EXPRESSION |
166                           (IS_STATIC ? ir::ScriptFunctionFlags::STATIC_BLOCK : ir::ScriptFunctionFlags::CONSTRUCTOR);
167
168    auto *funcExpr = CreateFunctionExpression(id, body, scriptFuncFlags);
169    auto *allocator = checker_->Allocator();
170
171    if constexpr (IS_STATIC) {
172        // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint)
173        auto *staticBlock = checker_->AllocNode<ir::ClassStaticBlock>(funcExpr, allocator);
174        staticBlock->AddModifier(ir::ModifierFlags::STATIC);
175        return staticBlock;
176    }
177    // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint)
178    return checker_->AllocNode<ir::MethodDefinition>(ir::MethodDefinitionKind::CONSTRUCTOR,
179                                                     id->Clone(allocator, nullptr), funcExpr, ir::ModifierFlags::NONE,
180                                                     allocator, false);
181}
182
183ir::MethodDefinition *MethodBuilder::CreateIrMethod(ir::Identifier *id, ir::BlockStatement *body)
184{
185    auto *allocator = checker_->Allocator();
186    auto *funcExpr = CreateFunctionExpression(id, body, ir::ScriptFunctionFlags::METHOD);
187    // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint)
188    auto *method = checker_->AllocNode<ir::MethodDefinition>(ir::MethodDefinitionKind::METHOD,
189                                                             funcExpr->Function()->Id()->Clone(allocator, nullptr),
190                                                             funcExpr, modifierFlags_, allocator, false);
191    return method;
192}
193
194ir::FunctionExpression *MethodBuilder::CreateFunctionExpression(ir::Identifier *id, ir::BlockStatement *body,
195                                                                ir::ScriptFunctionFlags scriptFuncFlags)
196{
197    auto funcSignature = ir::FunctionSignature(nullptr, std::move(params_), nullptr);
198
199    // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint)
200    auto *func = checker_->AllocNode<ir::ScriptFunction>(
201        checker_->Allocator(),
202        ir::ScriptFunction::ScriptFunctionData {body, std::move(funcSignature), scriptFuncFlags, modifierFlags_});
203
204    func->SetIdent(id);
205    func->SetReturnTypeAnnotation(returnType_);
206
207    // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint)
208    return checker_->AllocNode<ir::FunctionExpression>(func);
209}
210
211}  // namespace ark::es2panda::evaluate
212