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/helpers.h" 17#include "evaluate/entityDeclarator-inl.h" 18#include "evaluate/scopedDebugInfoPlugin-inl.h" 19#include "evaluate/debugInfoDeserialization/debugInfoDeserializer.h" 20 21#include "checker/ETSchecker.h" 22#include "parser/program/program.h" 23#include "compiler/lowering/phase.h" 24#include "compiler/lowering/util.h" 25#include "compiler/lowering/scopesInit/scopesInitPhase.h" 26#include "ir/statements/blockStatement.h" 27 28namespace ark::es2panda::evaluate { 29 30namespace { 31 32ir::VariableDeclaration *CreateVariableDeclaration(checker::ETSChecker *checker, ir::Identifier *ident, 33 ir::Expression *init) 34{ 35 auto *declarator = checker->AllocNode<ir::VariableDeclarator>(ir::VariableDeclaratorFlag::CONST, ident, init); 36 37 ArenaVector<ir::VariableDeclarator *> declarators(1, declarator, checker->Allocator()->Adapter()); 38 auto *declaration = checker->AllocNode<ir::VariableDeclaration>( 39 ir::VariableDeclaration::VariableDeclarationKind::CONST, checker->Allocator(), std::move(declarators), false); 40 41 declarator->SetParent(declaration); 42 return declaration; 43} 44 45/** 46 * @brief Break function's last statement into variable declaration and return statement. 47 * Hence we ensure that expression will return result, and local variables 48 * could be updated by inserting `DebuggerAPI.setLocal<>` calls between result and return. 49 * @param checker used for allocation purposes only. 50 * @param methodName used for returned variable name generation. 51 * @param lastStatement function's last statement to break. 52 * @returns pair of created AST nodes for variable declaration and return statement. 53 */ 54std::pair<ir::VariableDeclaration *, ir::ReturnStatement *> BreakLastStatement(checker::ETSChecker *checker, 55 util::StringView methodName, 56 ir::ExpressionStatement *lastStatement) 57{ 58 static constexpr std::string_view GENERATED_VAR_SUFFIX = "_generated_var"; 59 60 ASSERT(checker); 61 ASSERT(lastStatement); 62 auto *allocator = checker->Allocator(); 63 64 auto returnVariableNameView = [methodName, allocator]() { 65 std::stringstream ss; 66 ss << methodName << GENERATED_VAR_SUFFIX; 67 util::UString variableName(ss.str(), allocator); 68 return variableName.View(); 69 }(); 70 auto *variableIdent = checker->AllocNode<ir::Identifier>(returnVariableNameView, allocator); 71 auto *exprInit = lastStatement->AsExpressionStatement()->GetExpression(); 72 auto *variableDeclaration = CreateVariableDeclaration(checker, variableIdent, exprInit); 73 74 auto *returnStatement = checker->AllocNode<ir::ReturnStatement>(variableIdent->Clone(allocator, nullptr)); 75 76 // Unattach previous statement. 77 lastStatement->SetParent(nullptr); 78 79 return std::make_pair(variableDeclaration, returnStatement); 80} 81 82} // namespace 83 84ScopedDebugInfoPlugin::ScopedDebugInfoPlugin(parser::Program *globalProgram, checker::ETSChecker *checker, 85 const CompilerOptions &options) 86 : globalProgram_(globalProgram), 87 checker_(checker), 88 context_(options), 89 irCheckHelper_(checker, globalProgram->VarBinder()->AsETSBinder()), 90 debugInfoStorage_(options, checker->Allocator()), 91 debugInfoDeserializer_(*this), 92 pathResolver_(debugInfoStorage_), 93 prologueEpilogueMap_(checker->Allocator()->Adapter()), 94 proxyProgramsCache_(checker->Allocator()), 95 entityDeclarator_(*this) 96{ 97 ASSERT(globalProgram_); 98 ASSERT(checker_); 99 100 ValidateEvaluationOptions(options); 101 102 auto isContextValid = debugInfoStorage_.FillEvaluateContext(context_); 103 if (!isContextValid) { 104 LOG(FATAL, ES2PANDA) << "Can't create evaluate context" << std::endl; 105 } 106 107 CreateContextPrograms(); 108} 109 110void ScopedDebugInfoPlugin::PreCheck() 111{ 112 irCheckHelper_.PreCheck(); 113 114 // Find evaluation method after parse and before any checks. 115 context_.FindEvaluationMethod(GetEvaluatedExpressionProgram()); 116} 117 118void ScopedDebugInfoPlugin::PostCheck() 119{ 120 ASSERT(prologueEpilogueMap_.empty()); 121 122 [[maybe_unused]] auto inserted = InsertReturnStatement(); 123 LOG(DEBUG, ES2PANDA) << "Evaluation method will return: " << std::boolalpha << inserted << std::noboolalpha; 124} 125 126bool ScopedDebugInfoPlugin::InsertReturnStatement() 127{ 128 auto *lastStatement = context_.lastStatement; 129 if (lastStatement == nullptr) { 130 // Last evaluation statement cannot return a value. 131 return false; 132 } 133 auto *returnType = lastStatement->GetExpression()->TsType(); 134 if (returnType == nullptr || !returnType->HasTypeFlag(checker::TypeFlag::ETS_PRIMITIVE_RETURN)) { 135 // NOTE(dslynko): currently expression evaluation supports only primitives. 136 // In future this condition might be replaced with `returnType is not void`. 137 return false; 138 } 139 140 // As an example, the code below will transform 141 // ``` 142 // localVar += 1 // This expression returns new value of `localVar`. 143 // DebuggerAPI.setLocalInt(0, localVar) // Already generated by plugin. 144 // ``` 145 // into 146 // ``` 147 // const eval_file_generated_var = (localVar += 1) 148 // DebuggerAPI.setLocalInt(0, localVar) 149 // return eval_file_generated_var 150 // ``` 151 // which will also modify method signature's return type. 152 153 auto *evalMethodStatements = context_.methodStatements; 154 auto &statementsList = evalMethodStatements->Statements(); 155 // Omit the emplaced `DebuggerAPI.setLocal<>` calls and find the original last statement. 156 auto lastStatementIter = std::find(statementsList.rbegin(), statementsList.rend(), lastStatement); 157 ASSERT(lastStatementIter != statementsList.rend()); 158 159 // Break the last user's statement into variable declaration and return statement. 160 auto *scope = compiler::NearestScope(lastStatement); 161 auto *scriptFunction = evalMethodStatements->Parent()->AsScriptFunction(); 162 auto [variableDeclaration, returnStatement] = 163 BreakLastStatement(checker_, scriptFunction->Id()->Name(), lastStatement); 164 165 // Attach new nodes to statements block. 166 variableDeclaration->SetParent(evalMethodStatements); 167 *lastStatementIter = variableDeclaration; 168 returnStatement->SetParent(evalMethodStatements); 169 statementsList.emplace_back(returnStatement); 170 171 scriptFunction->AddFlag(ir::ScriptFunctionFlags::HAS_RETURN); 172 auto *signature = scriptFunction->Signature(); 173 signature->AddSignatureFlag(checker::SignatureFlags::NEED_RETURN_TYPE); 174 signature->SetReturnType(returnType); 175 176 auto newNodeInitializer = [this, scope](ir::AstNode *node) { 177 auto *varBinder = GetETSBinder(); 178 helpers::DoScopedAction(checker_, varBinder, GetEvaluatedExpressionProgram(), scope, nullptr, 179 [this, varBinder, scope, node]() { 180 compiler::InitScopesPhaseETS::RunExternalNode(node, varBinder); 181 varBinder->HandleCustomNodes(node); 182 varBinder->ResolveReferencesForScope(node, scope); 183 node->Check(checker_); 184 }); 185 }; 186 newNodeInitializer(variableDeclaration); 187 newNodeInitializer(returnStatement); 188 189 return true; 190} 191 192void ScopedDebugInfoPlugin::AddPrologueEpilogue(ir::BlockStatement *block) 193{ 194 auto iter = prologueEpilogueMap_.find(block); 195 if (iter == prologueEpilogueMap_.end()) { 196 return; 197 } 198 199 // Prepend prologue. 200 auto &statements = block->Statements(); 201 for (auto *stmt : iter->second.first) { 202 statements.insert(statements.begin(), stmt); 203 } 204 205 // Append epilogue. 206 for (auto *stmt : iter->second.second) { 207 statements.emplace_back(stmt); 208 } 209 210 prologueEpilogueMap_.erase(iter); 211} 212 213varbinder::Variable *ScopedDebugInfoPlugin::FindIdentifier(ir::Identifier *ident) 214{ 215 ASSERT(ident); 216 217 helpers::SafeStateScope s(checker_, GetETSBinder()); 218 219 auto *var = FindLocalVariable(ident); 220 if (var != nullptr) { 221 return var; 222 } 223 var = FindGlobalVariable(ident); 224 if (var != nullptr) { 225 return var; 226 } 227 var = FindClass(ident); 228 if (var != nullptr) { 229 return var; 230 } 231 return FindGlobalFunction(ident); 232} 233 234varbinder::Variable *ScopedDebugInfoPlugin::FindClass(ir::Identifier *ident) 235{ 236 // The following algorithm is used: 237 // - Search for `import * as B from "C"` statements. 238 // - If found, [Not implemented yet] 239 // - Else, proceed. 240 // - Search classes which defined in the context file: 241 // - If found, recreate its structure and return. 242 // - Else, proceed. 243 // - Search through the imported entities extracted from imports/exports table: 244 // - If the class was found, create parser::Program corresponding for the import source, 245 // where the class could be recreated. 246 // - Else, return nullptr. 247 248 // NOTE: support "import * as X". 249 250 ASSERT(ident); 251 LOG(DEBUG, ES2PANDA) << "ScopedDebugInfoPlugin: FindClass " << ident->Name(); 252 253 auto *importerProgram = GetETSBinder()->Program(); 254 const auto &identName = ident->Name(); 255 256 // Search "import * as B" statements. 257 // NOTE: separate this into a method. 258 auto importPath = pathResolver_.FindNamedImportAll(context_.sourceFilePath.Utf8(), identName.Utf8()); 259 if (!importPath.empty()) { 260 UNREACHABLE(); 261 return nullptr; 262 } 263 264 // Search in the context file. 265 auto classId = debugInfoStorage_.FindClass(context_.sourceFilePath.Utf8(), identName.Utf8()); 266 if (classId.IsValid()) { 267 return entityDeclarator_.ImportGlobalEntity( 268 context_.sourceFilePath, identName, importerProgram, identName, 269 [classId](auto *deserializer, auto *program, auto declSourcePath, auto declName) { 270 return deserializer->CreateIrClass(classId, program, declSourcePath, declName); 271 }); 272 } 273 274 // Search in imported entities. 275 auto optFoundEntity = pathResolver_.FindImportedEntity(context_.sourceFilePath.Utf8(), identName.Utf8()); 276 if (!optFoundEntity) { 277 return nullptr; 278 } 279 280 const auto &[entitySourceFile, entitySourceName] = optFoundEntity.value(); 281 282 classId = debugInfoStorage_.FindClass(entitySourceFile, entitySourceName); 283 if (!classId.IsValid()) { 284 // The entity is not a class. 285 return nullptr; 286 } 287 288 // Must pass the name of class as declared in the found file. 289 return entityDeclarator_.ImportGlobalEntity( 290 entitySourceFile, entitySourceName, importerProgram, identName, 291 [classId](auto *deserializer, auto *program, auto declSourcePath, auto declName) { 292 return deserializer->CreateIrClass(classId, program, declSourcePath, declName); 293 }); 294} 295 296varbinder::Variable *ScopedDebugInfoPlugin::FindGlobalFunction(ir::Identifier *ident) 297{ 298 // Correct overload resolution requires us to create all reachable functions with the given name, 299 // so that Checker later could choose the correct one. 300 ASSERT(ident); 301 LOG(DEBUG, ES2PANDA) << "ScopedDebugInfoPlugin: FindGlobalFunction " << ident->Name(); 302 303 auto *allocator = Allocator(); 304 305 auto *importerProgram = GetETSBinder()->Program(); 306 auto identName = ident->Name(); 307 308 ArenaVector<std::pair<parser::Program *, ArenaVector<ir::AstNode *>>> createdMethods(allocator->Adapter()); 309 310 // Build every global function from the context file. 311 createdMethods.emplace_back(GetProgram(context_.sourceFilePath), ArenaVector<ir::AstNode *>(allocator->Adapter())); 312 auto &fromContextFile = createdMethods.back().second; 313 314 auto *var = entityDeclarator_.ImportGlobalEntity( 315 context_.sourceFilePath, identName, importerProgram, identName, 316 [&fromContextFile](auto *deserializer, auto *program, auto declSourcePath, auto declName) { 317 return deserializer->CreateIrGlobalMethods(fromContextFile, program, declSourcePath, declName); 318 }); 319 320 // Then search in imports. 321 ArenaVector<EntityInfo> importedFunctions(allocator->Adapter()); 322 pathResolver_.FindImportedFunctions(importedFunctions, context_.sourceFilePath.Utf8(), identName.Utf8()); 323 324 // Build all the found functions. 325 for (const auto &[funcSourceFile, funcSourceName] : importedFunctions) { 326 createdMethods.emplace_back(GetProgram(funcSourceFile), ArenaVector<ir::AstNode *>(allocator->Adapter())); 327 auto &fromImported = createdMethods.back().second; 328 329 auto *importedVar = entityDeclarator_.ImportGlobalEntity( 330 funcSourceFile, funcSourceName, importerProgram, identName, 331 [&fromImported](auto *deserializer, auto *program, auto declSourcePath, auto declName) { 332 return deserializer->CreateIrGlobalMethods(fromImported, program, declSourcePath, declName); 333 }); 334 if (importedVar != nullptr) { 335 ASSERT(var == nullptr || var == importedVar); 336 var = importedVar; 337 } 338 } 339 340 // Run Checker only after all functions are created, so that overloading could work correctly. 341 for (auto &[program, methods] : createdMethods) { 342 auto *globalClass = program->GlobalClass(); 343 auto *globalClassScope = program->GlobalClassScope(); 344 for (auto *method : methods) { 345 irCheckHelper_.CheckNewNode(method, globalClassScope, globalClass, program); 346 } 347 } 348 349 return var; 350} 351 352varbinder::Variable *ScopedDebugInfoPlugin::FindGlobalVariable(ir::Identifier *ident) 353{ 354 ASSERT(ident); 355 LOG(DEBUG, ES2PANDA) << "ScopedDebugInfoPlugin: FindGlobalVariable " << ident->Name(); 356 357 auto *importerProgram = GetETSBinder()->Program(); 358 auto identName = ident->Name(); 359 360 // Search in the context file. 361 auto *var = entityDeclarator_.ImportGlobalEntity(context_.sourceFilePath, identName, importerProgram, identName, 362 &DebugInfoDeserializer::CreateIrGlobalVariable); 363 if (var != nullptr) { 364 return var; 365 } 366 367 // Search within the imports. 368 auto optFoundEntity = pathResolver_.FindImportedEntity(context_.sourceFilePath.Utf8(), identName.Utf8()); 369 if (!optFoundEntity) { 370 return nullptr; 371 } 372 373 const auto &[entitySourceFile, entitySourceName] = optFoundEntity.value(); 374 375 // Search once again, but in the exported source. Must pass the name of entity as declared in the found file. 376 return entityDeclarator_.ImportGlobalEntity(entitySourceFile, entitySourceName, importerProgram, identName, 377 &DebugInfoDeserializer::CreateIrGlobalVariable); 378} 379 380varbinder::Variable *ScopedDebugInfoPlugin::FindLocalVariable(ir::Identifier *ident) 381{ 382 ASSERT(ident); 383 // Search local variables only in evaluation method. 384 if (helpers::GetEnclosingBlock(ident) != context_.methodStatements) { 385 return nullptr; 386 } 387 388 LOG(DEBUG, ES2PANDA) << "ScopedDebugInfoPlugin: FindLocalVariable " << ident->Name(); 389 390 // NOTE: verify that function arguments are included. 391 const auto &localVariableTable = context_.extractor->GetLocalVariableTable(context_.methodId); 392 return debugInfoDeserializer_.CreateIrLocalVariable(ident, localVariableTable, context_.bytecodeOffset); 393} 394 395void ScopedDebugInfoPlugin::ValidateEvaluationOptions(const CompilerOptions &options) 396{ 397 if (!options.isEtsModule) { 398 LOG(FATAL, ES2PANDA) << "Evaluation mode must be used in conjunction with ets-module option."; 399 } 400} 401 402void ScopedDebugInfoPlugin::CreateContextPrograms() 403{ 404 debugInfoStorage_.EnumerateContextFiles([this](auto sourceFilePath, auto, auto, auto moduleName) { 405 CreateEmptyProgram(sourceFilePath, moduleName); 406 return true; 407 }); 408} 409 410parser::Program *ScopedDebugInfoPlugin::CreateEmptyProgram(std::string_view sourceFilePath, std::string_view moduleName) 411{ 412 auto *allocator = Allocator(); 413 414 // Checker doesn't yet have `VarBinder`, must retrieve it from `globalProgram_`. 415 parser::Program *program = allocator->New<parser::Program>(allocator, GetETSBinder()); 416 auto omitModuleName = moduleName.empty(); 417 program->SetSource({sourceFilePath, "", globalProgram_->SourceFileFolder().Utf8(), !omitModuleName}); 418 program->SetModuleInfo(moduleName, false, omitModuleName); 419 auto *etsScript = 420 allocator->New<ir::ETSScript>(allocator, ArenaVector<ir::Statement *>(allocator->Adapter()), program); 421 program->SetAst(etsScript); 422 423 helpers::AddExternalProgram(globalProgram_, program, moduleName); 424 proxyProgramsCache_.AddProgram(program); 425 426 return program; 427} 428 429parser::Program *ScopedDebugInfoPlugin::GetProgram(util::StringView fileName) 430{ 431 auto *program = proxyProgramsCache_.GetProgram(fileName); 432 ASSERT(program); 433 return program; 434} 435 436parser::Program *ScopedDebugInfoPlugin::GetEvaluatedExpressionProgram() 437{ 438 auto *program = GetETSBinder()->GetContext()->parserProgram; 439 ASSERT(program); 440 return program; 441} 442 443varbinder::ETSBinder *ScopedDebugInfoPlugin::GetETSBinder() 444{ 445 return globalProgram_->VarBinder()->AsETSBinder(); 446} 447 448ArenaAllocator *ScopedDebugInfoPlugin::Allocator() 449{ 450 return checker_->Allocator(); 451} 452 453} // namespace ark::es2panda::evaluate 454