1/**
2 * Copyright (c) 2021-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 "ETSchecker.h"
17
18namespace ark::es2panda::checker {
19
20CheckerContext::CheckerContext(Checker *checker, CheckerStatus newStatus, ETSObjectType const *containingClass,
21                               Signature *containingSignature)
22    : parent_(checker),
23      status_(newStatus),
24      capturedVars_(parent_->Allocator()->Adapter()),
25      smartCasts_(parent_->Allocator()->Adapter()),
26      containingClass_(containingClass),
27      containingSignature_(containingSignature),
28      testSmartCasts_(parent_->Allocator()->Adapter()),
29      breakSmartCasts_(parent_->Allocator()->Adapter())
30{
31}
32
33void CheckerContext::SetSmartCast(varbinder::Variable const *const variable, checker::Type *const smartType) noexcept
34{
35    // Just block captured and modified variables here instead of finding all their usage occurrences.
36    if (!variable->HasFlag(varbinder::VariableFlags::CAPTURED_MODIFIED)) {
37        smartCasts_.insert_or_assign(variable, smartType);
38    }
39}
40
41SmartCastTypes CheckerContext::CloneTestSmartCasts(bool const clearData) noexcept
42{
43    if (testSmartCasts_.empty()) {
44        return std::nullopt;
45    }
46
47    SmartCastTestArray smartCasts {};
48    smartCasts.reserve(testSmartCasts_.size());
49
50    for (auto [variable, types] : testSmartCasts_) {
51        if (types.first != nullptr || types.second != nullptr) {
52            smartCasts.emplace_back(variable, types.first, types.second);
53        }
54    }
55
56    if (clearData) {
57        ClearTestSmartCasts();
58    }
59
60    return std::make_optional(smartCasts);
61}
62
63SmartCastArray CheckerContext::CloneSmartCasts(bool const clearData) noexcept
64{
65    SmartCastArray smartCasts {};
66
67    if (!smartCasts_.empty()) {
68        smartCasts.reserve(smartCasts_.size());
69
70        for (auto const [variable, type] : smartCasts_) {
71            smartCasts.emplace_back(variable, type);
72        }
73    }
74
75    if (clearData) {
76        ClearSmartCasts();
77    }
78
79    return smartCasts;
80}
81
82void CheckerContext::RestoreSmartCasts(SmartCastArray const &otherSmartCasts)
83{
84    smartCasts_.clear();
85    if (!otherSmartCasts.empty()) {
86        for (auto [variable, type] : otherSmartCasts) {
87            smartCasts_.emplace(variable, type);
88        }
89    }
90}
91
92void CheckerContext::RemoveSmartCasts(SmartCastArray const &otherSmartCasts) noexcept
93{
94    if (!smartCasts_.empty()) {
95        auto it = smartCasts_.begin();
96        while (it != smartCasts_.end()) {
97            if (std::find_if(otherSmartCasts.begin(), otherSmartCasts.end(), [&it](auto const &item) -> bool {
98                    return item.first == it->first;
99                }) == otherSmartCasts.end()) {
100                it = smartCasts_.erase(it);
101            } else {
102                ++it;
103            }
104        }
105    }
106}
107
108//  Auxiliary private method returning combined type (if types differ) or 'nullptr' if types are identical
109//  and no smart cast change is required.
110checker::Type *CheckerContext::CombineTypes(checker::Type *const typeOne, checker::Type *const typeTwo) const noexcept
111{
112    ASSERT(typeOne != nullptr && typeTwo != nullptr);
113    auto *const checker = parent_->AsETSChecker();
114
115    if (checker->Relation()->IsIdenticalTo(typeOne, typeTwo)) {
116        return nullptr;
117    }
118
119    return checker->CreateETSUnionType({typeOne, typeTwo});
120}
121
122void CheckerContext::CombineSmartCasts(SmartCastArray const &otherSmartCasts)
123{
124    auto *const checker = parent_->AsETSChecker();
125
126    for (auto [variable, type] : otherSmartCasts) {
127        auto const it = smartCasts_.find(variable);
128        if (it == smartCasts_.end()) {
129            continue;
130        }
131        // Smart cast presents in both sets
132        if (auto *const smartType = CombineTypes(it->second, type); smartType != nullptr) {
133            // Remove it or set to new combined value
134            if (checker->Relation()->IsIdenticalTo(it->first->TsType(), smartType)) {
135                smartCasts_.erase(it);
136            } else {
137                it->second = smartType;
138            }
139        }
140    }
141
142    // Remove smart casts that don't present in the other set.
143    RemoveSmartCasts(otherSmartCasts);
144}
145
146// Second return value shows if the 'IN_LOOP' flag should be cleared on exit from the loop (case of nested loops).
147std::pair<SmartCastArray, bool> CheckerContext::EnterLoop(ir::LoopStatement const &loop) noexcept
148{
149    bool const clearFlag = !IsInLoop();
150    if (clearFlag) {
151        status_ |= CheckerStatus::IN_LOOP;
152    }
153
154    auto smartCasts = CloneSmartCasts();
155
156    SmartVariables changedVariables {};
157    loop.Iterate([this, &changedVariables](ir::AstNode *childNode) { CheckAssignments(childNode, changedVariables); });
158
159    if (!changedVariables.empty()) {
160        for (auto const *variable : changedVariables) {
161            smartCasts_.erase(variable);
162        }
163    }
164
165    return {std::move(smartCasts), clearFlag};
166}
167
168void CheckerContext::ExitLoop(SmartCastArray &prevSmartCasts, bool const clearFlag,
169                              ir::LoopStatement *loopStatement) noexcept
170{
171    if (clearFlag) {
172        status_ &= ~CheckerStatus::IN_LOOP;
173    }
174
175    if (!breakSmartCasts_.empty()) {
176        auto it = breakSmartCasts_.begin();
177
178        while (it != breakSmartCasts_.end()) {
179            if (it->first != loopStatement) {
180                ++it;
181            } else {
182                CombineSmartCasts(it->second);
183                it = breakSmartCasts_.erase(it);
184            }
185        }
186    }
187
188    //  Now we don't process smart casts inside the loops correctly, thus just combine them on exit from the loop.
189    CombineSmartCasts(prevSmartCasts);
190}
191
192void CheckerContext::CheckAssignments(ir::AstNode const *node, SmartVariables &changedVariables) noexcept
193{
194    if (node == nullptr) {  //  Just in case!
195        return;
196    }
197
198    if (!node->IsAssignmentExpression()) {
199        node->Iterate(
200            [this, &changedVariables](ir::AstNode *childNode) { CheckAssignments(childNode, changedVariables); });
201        return;
202    }
203
204    auto const *assignment = node->AsAssignmentExpression();
205    if (assignment->Left()->IsIdentifier()) {
206        auto const *const ident = assignment->Left()->AsIdentifier();
207
208        auto const *variable = ident->Variable();
209        if (variable == nullptr) {
210            //  NOTE: we're interesting in the local variables ONLY!
211            variable = parent_->AsETSChecker()->FindVariableInFunctionScope(
212                ident->Name(), varbinder::ResolveBindingOptions::ALL_NON_TYPE);
213        }
214
215        if (variable != nullptr) {
216            changedVariables.insert(variable);
217        }
218    }
219
220    assignment->Right()->Iterate(
221        [this, &changedVariables](ir::AstNode *childNode) { CheckAssignments(childNode, changedVariables); });
222}
223
224SmartCastArray CheckerContext::CheckTryBlock(ir::BlockStatement const &tryBlock) noexcept
225{
226    SmartVariables changedVariables {};
227    tryBlock.Iterate(
228        [this, &changedVariables](ir::AstNode *childNode) { CheckAssignments(childNode, changedVariables); });
229
230    SmartCastArray smartCasts {};
231    if (!smartCasts_.empty()) {
232        smartCasts.reserve(smartCasts_.size());
233
234        for (auto const [variable, type] : smartCasts_) {
235            if (changedVariables.find(variable) == changedVariables.end()) {
236                smartCasts.emplace_back(variable, type);
237            }
238        }
239    }
240
241    return smartCasts;
242}
243
244//  Check that the expression is a part of logical OR/AND or unary negation operators chain
245//  (other cases are not interested)
246bool CheckerContext::IsInValidChain(ir::AstNode const *parent) noexcept
247{
248    while (parent != nullptr && !parent->IsIfStatement() && !parent->IsConditionalExpression()) {
249        if (parent->IsBinaryExpression()) {
250            auto const operation = parent->AsBinaryExpression()->OperatorType();
251            if (operation != lexer::TokenType::PUNCTUATOR_LOGICAL_OR &&
252                operation != lexer::TokenType::PUNCTUATOR_LOGICAL_AND) {
253                return false;
254            }
255        } else if (parent->IsUnaryExpression()) {
256            if (parent->AsUnaryExpression()->OperatorType() != lexer::TokenType::PUNCTUATOR_EXCLAMATION_MARK) {
257                return false;
258            }
259        } else {
260            return false;
261        }
262        parent = parent->Parent();
263    }
264    return parent != nullptr;
265}
266
267void CheckerContext::CheckIdentifierSmartCastCondition(ir::Identifier const *const identifier) noexcept
268{
269    if (!IsInTestExpression()) {
270        return;
271    }
272
273    auto const *const variable = identifier->Variable();
274    ASSERT(variable != nullptr);
275
276    //  Smart cast for extended conditional check can be applied only to the variables of reference types.
277    if (auto const *const variableType = variable->TsType(); !variableType->IsETSReferenceType()) {
278        return;
279    }
280
281    if (!IsInValidChain(identifier->Parent())) {
282        return;
283    }
284
285    ASSERT(testCondition_.variable == nullptr);
286    if (identifier->TsType()->PossiblyETSNullish()) {
287        testCondition_ = {variable, parent_->AsETSChecker()->GlobalETSNullType(), true, false};
288    }
289}
290
291void CheckerContext::CheckUnarySmartCastCondition(ir::UnaryExpression const *const unaryExpression) noexcept
292{
293    if (!IsInTestExpression() || unaryExpression->OperatorType() != lexer::TokenType::PUNCTUATOR_EXCLAMATION_MARK) {
294        return;
295    }
296
297    auto const *const argument = unaryExpression->Argument();
298    if (argument == nullptr || (!argument->IsIdentifier() && !argument->IsBinaryExpression())) {
299        return;
300    }
301
302    if (!IsInValidChain(unaryExpression->Parent())) {
303        return;
304    }
305
306    if (testCondition_.variable != nullptr) {
307        testCondition_.negate = !testCondition_.negate;
308    }
309}
310
311void CheckerContext::CheckBinarySmartCastCondition(ir::BinaryExpression *const binaryExpression) noexcept
312{
313    if (!IsInTestExpression() || !IsInValidChain(binaryExpression->Parent())) {
314        return;
315    }
316
317    if (auto const operatorType = binaryExpression->OperatorType(); operatorType == lexer::TokenType::KEYW_INSTANCEOF) {
318        ASSERT(testCondition_.variable == nullptr);
319        if (binaryExpression->Left()->IsIdentifier()) {
320            testCondition_ = {binaryExpression->Left()->AsIdentifier()->Variable(),
321                              binaryExpression->Right()->TsType()};
322        }
323    } else if (operatorType == lexer::TokenType::PUNCTUATOR_STRICT_EQUAL ||
324               operatorType == lexer::TokenType::PUNCTUATOR_NOT_STRICT_EQUAL ||
325               operatorType == lexer::TokenType::PUNCTUATOR_EQUAL ||
326               operatorType == lexer::TokenType::PUNCTUATOR_NOT_EQUAL) {
327        ASSERT(testCondition_.variable == nullptr);
328        CheckSmartCastEqualityCondition(binaryExpression);
329    }
330}
331
332//  Extracted just to avoid large length and depth of method 'CheckBinarySmartCastCondition()'.
333void CheckerContext::CheckSmartCastEqualityCondition(ir::BinaryExpression *const binaryExpression) noexcept
334{
335    varbinder::Variable const *variable = nullptr;
336    checker::Type *testedType = nullptr;
337    auto const operatorType = binaryExpression->OperatorType();
338
339    bool strict = operatorType == lexer::TokenType::PUNCTUATOR_NOT_STRICT_EQUAL ||
340                  operatorType == lexer::TokenType::PUNCTUATOR_STRICT_EQUAL;
341
342    // extracted just to avoid extra nested level
343    auto const getTestedType = [&variable, &testedType, &strict](ir::Identifier const *const identifier,
344                                                                 ir::Expression *const expression) -> void {
345        ASSERT(identifier != nullptr && expression != nullptr);
346        variable = identifier->Variable();
347        if (expression->IsLiteral()) {
348            testedType = expression->TsType();
349            if (!expression->IsNullLiteral() && !expression->IsUndefinedLiteral()) {
350                strict = false;
351            }
352        }
353    };
354
355    if (binaryExpression->Left()->IsIdentifier()) {
356        getTestedType(binaryExpression->Left()->AsIdentifier(), binaryExpression->Right());
357    }
358
359    if (testedType == nullptr && binaryExpression->Right()->IsIdentifier()) {
360        getTestedType(binaryExpression->Right()->AsIdentifier(), binaryExpression->Left());
361    }
362
363    if (testedType != nullptr) {
364        bool const negate = operatorType == lexer::TokenType::PUNCTUATOR_NOT_STRICT_EQUAL ||
365                            operatorType == lexer::TokenType::PUNCTUATOR_NOT_EQUAL;
366
367        if (testedType->DefinitelyETSNullish()) {
368            testCondition_ = {variable, testedType, negate, strict};
369        }
370    }
371}
372
373void CheckerContext::ClearTestSmartCasts() noexcept
374{
375    testCondition_ = {};
376    testSmartCasts_.clear();
377    operatorType_ = lexer::TokenType::EOS;
378}
379
380checker::Type *CheckerContext::GetSmartCast(varbinder::Variable const *const variable) const noexcept
381{
382    if (IsInTestExpression()) {
383        if (operatorType_ == lexer::TokenType::PUNCTUATOR_LOGICAL_AND) {
384            if (auto const it = testSmartCasts_.find(variable);
385                it != testSmartCasts_.end() && it->second.first != nullptr) {
386                return it->second.first;
387            }
388        } else if (operatorType_ == lexer::TokenType::PUNCTUATOR_LOGICAL_OR) {
389            if (auto const it = testSmartCasts_.find(variable);
390                it != testSmartCasts_.end() && it->second.second != nullptr) {
391                return it->second.second;
392            }
393        }
394    }
395
396    auto const it = smartCasts_.find(variable);
397    return it == smartCasts_.end() ? nullptr : it->second;
398}
399
400void CheckerContext::OnBreakStatement(ir::BreakStatement const *breakStatement)
401{
402    ir::Statement const *targetStatement = breakStatement->Target()->AsStatement();
403    ASSERT(targetStatement != nullptr);
404    if (targetStatement->IsLabelledStatement()) {
405        targetStatement = targetStatement->AsLabelledStatement()->Body();
406    }
407    ASSERT(targetStatement != nullptr);
408
409    auto const inInnerScope = [targetStatement](varbinder::Scope const *scope, ir::AstNode const *parent) -> bool {
410        do {
411            parent = parent->Parent();
412            if (parent->IsScopeBearer() && parent->Scope() == scope) {
413                return true;
414            }
415        } while (parent != targetStatement);
416        return false;
417    };
418
419    status_ |= CheckerStatus::MEET_BREAK;
420
421    if (smartCasts_.empty()) {
422        return;
423    }
424
425    SmartCastArray smartCasts {};
426    smartCasts.reserve(smartCasts_.size());
427
428    for (auto const [variable, type] : smartCasts_) {
429        if (!inInnerScope(variable->AsLocalVariable()->GetScope(), breakStatement)) {
430            smartCasts.emplace_back(variable, type);
431        }
432    }
433
434    if (!smartCasts.empty()) {
435        AddBreakSmartCasts(targetStatement, std::move(smartCasts));
436    }
437
438    ClearSmartCasts();
439}
440
441void CheckerContext::AddBreakSmartCasts(ir::Statement const *targetStatement, SmartCastArray &&smartCasts)
442{
443    breakSmartCasts_.emplace(targetStatement, std::move(smartCasts));
444}
445
446void CheckerContext::CombineBreakSmartCasts(ir::Statement const *targetStatement)
447{
448    ASSERT(smartCasts_.empty());
449
450    if (!breakSmartCasts_.empty()) {
451        bool firstCase = true;
452        auto it = breakSmartCasts_.begin();
453
454        while (it != breakSmartCasts_.end()) {
455            if (it->first != targetStatement) {
456                ++it;
457                continue;
458            }
459
460            if (firstCase) {
461                firstCase = false;
462                RestoreSmartCasts(it->second);
463            } else {
464                CombineSmartCasts(it->second);
465            }
466
467            it = breakSmartCasts_.erase(it);
468        }
469    }
470}
471}  // namespace ark::es2panda::checker
472