From c0e5290651844f7a72de7a9a854281211c087277 Mon Sep 17 00:00:00 2001 From: Denis Slynko Date: Wed, 17 Jul 2024 17:19:13 +0300 Subject: [PATCH 1/2] [PT] Group evaluation mode compiler tests into subdirectories Signed-off-by: Denis Slynko --- .../global_function.base.ets | 0 .../global_function.expected.ets | 0 .../global_function.patch.ets | 0 .../global_overloaded_function.base.ets | 0 .../global_overloaded_function.expected.ets | 0 .../global_overloaded_function.patch.ets | 0 .../global_primitive.base.ets | 0 .../global_primitive.expected.ets | 0 .../global_primitive.patch.ets | 0 .../global_std_class.base.ets | 0 .../global_std_class.expected.ets | 0 .../global_std_class.patch.ets | 0 .../global_user_class.base.ets | 0 .../global_user_class.expected.ets | 0 .../global_user_class.patch.ets | 0 .../inheritance_0.base.ets | 0 .../inheritance_0.expected.ets | 0 .../inheritance_0.patch.ets | 0 .../inheritance_1.base.ets | 0 .../inheritance_1.expected.ets | 0 .../inheritance_1.patch.ets | 0 .../inheritance_2.base.ets | 0 .../inheritance_2.expected.ets | 0 .../inheritance_2.patch.ets | 0 .../local_variable_primitive.base.ets | 0 .../local_variable_primitive.expected.ets | 0 .../local_variable_primitive.patch.ets | 0 .../local_variable_std_class.base.ets | 0 .../local_variable_std_class.expected.ets | 0 .../local_variable_std_class.patch.ets | 0 .../local_variable_user_class.base.ets | 0 .../local_variable_user_class.expected.ets | 0 .../local_variable_user_class.patch.ets | 0 .../static_function.base.ets | 0 .../static_function.expected.ets | 0 .../static_function.patch.ets | 0 .../runtime/include/tooling/vreg_value.h | 6 +- .../plugins/evaluate/runner_evaluate.py | 12 +-- .../runner/plugins/evaluate/test_evaluate.py | 86 +++++++------------ 39 files changed, 39 insertions(+), 65 deletions(-) rename static_core/plugins/ets/tests/evaluate/ets/{ => global_function}/global_function.base.ets (100%) rename static_core/plugins/ets/tests/evaluate/ets/{ => global_function}/global_function.expected.ets (100%) rename static_core/plugins/ets/tests/evaluate/ets/{ => global_function}/global_function.patch.ets (100%) rename static_core/plugins/ets/tests/evaluate/ets/{ => global_overloaded_function}/global_overloaded_function.base.ets (100%) rename static_core/plugins/ets/tests/evaluate/ets/{ => global_overloaded_function}/global_overloaded_function.expected.ets (100%) rename static_core/plugins/ets/tests/evaluate/ets/{ => global_overloaded_function}/global_overloaded_function.patch.ets (100%) rename static_core/plugins/ets/tests/evaluate/ets/{ => global_primitive}/global_primitive.base.ets (100%) rename static_core/plugins/ets/tests/evaluate/ets/{ => global_primitive}/global_primitive.expected.ets (100%) rename static_core/plugins/ets/tests/evaluate/ets/{ => global_primitive}/global_primitive.patch.ets (100%) rename static_core/plugins/ets/tests/evaluate/ets/{ => global_std_class}/global_std_class.base.ets (100%) rename static_core/plugins/ets/tests/evaluate/ets/{ => global_std_class}/global_std_class.expected.ets (100%) rename static_core/plugins/ets/tests/evaluate/ets/{ => global_std_class}/global_std_class.patch.ets (100%) rename static_core/plugins/ets/tests/evaluate/ets/{ => global_user_class}/global_user_class.base.ets (100%) rename static_core/plugins/ets/tests/evaluate/ets/{ => global_user_class}/global_user_class.expected.ets (100%) rename static_core/plugins/ets/tests/evaluate/ets/{ => global_user_class}/global_user_class.patch.ets (100%) rename static_core/plugins/ets/tests/evaluate/ets/{ => inheritance_0}/inheritance_0.base.ets (100%) rename static_core/plugins/ets/tests/evaluate/ets/{ => inheritance_0}/inheritance_0.expected.ets (100%) rename static_core/plugins/ets/tests/evaluate/ets/{ => inheritance_0}/inheritance_0.patch.ets (100%) rename static_core/plugins/ets/tests/evaluate/ets/{ => inheritance_1}/inheritance_1.base.ets (100%) rename static_core/plugins/ets/tests/evaluate/ets/{ => inheritance_1}/inheritance_1.expected.ets (100%) rename static_core/plugins/ets/tests/evaluate/ets/{ => inheritance_1}/inheritance_1.patch.ets (100%) rename static_core/plugins/ets/tests/evaluate/ets/{ => inheritance_2}/inheritance_2.base.ets (100%) rename static_core/plugins/ets/tests/evaluate/ets/{ => inheritance_2}/inheritance_2.expected.ets (100%) rename static_core/plugins/ets/tests/evaluate/ets/{ => inheritance_2}/inheritance_2.patch.ets (100%) rename static_core/plugins/ets/tests/evaluate/ets/{ => local_variable_primitive}/local_variable_primitive.base.ets (100%) rename static_core/plugins/ets/tests/evaluate/ets/{ => local_variable_primitive}/local_variable_primitive.expected.ets (100%) rename static_core/plugins/ets/tests/evaluate/ets/{ => local_variable_primitive}/local_variable_primitive.patch.ets (100%) rename static_core/plugins/ets/tests/evaluate/ets/{ => local_variable_std_class}/local_variable_std_class.base.ets (100%) rename static_core/plugins/ets/tests/evaluate/ets/{ => local_variable_std_class}/local_variable_std_class.expected.ets (100%) rename static_core/plugins/ets/tests/evaluate/ets/{ => local_variable_std_class}/local_variable_std_class.patch.ets (100%) rename static_core/plugins/ets/tests/evaluate/ets/{ => local_variable_user_class}/local_variable_user_class.base.ets (100%) rename static_core/plugins/ets/tests/evaluate/ets/{ => local_variable_user_class}/local_variable_user_class.expected.ets (100%) rename static_core/plugins/ets/tests/evaluate/ets/{ => local_variable_user_class}/local_variable_user_class.patch.ets (100%) rename static_core/plugins/ets/tests/evaluate/ets/{ => static_function}/static_function.base.ets (100%) rename static_core/plugins/ets/tests/evaluate/ets/{ => static_function}/static_function.expected.ets (100%) rename static_core/plugins/ets/tests/evaluate/ets/{ => static_function}/static_function.patch.ets (100%) diff --git a/static_core/plugins/ets/tests/evaluate/ets/global_function.base.ets b/static_core/plugins/ets/tests/evaluate/ets/global_function/global_function.base.ets similarity index 100% rename from static_core/plugins/ets/tests/evaluate/ets/global_function.base.ets rename to static_core/plugins/ets/tests/evaluate/ets/global_function/global_function.base.ets diff --git a/static_core/plugins/ets/tests/evaluate/ets/global_function.expected.ets b/static_core/plugins/ets/tests/evaluate/ets/global_function/global_function.expected.ets similarity index 100% rename from static_core/plugins/ets/tests/evaluate/ets/global_function.expected.ets rename to static_core/plugins/ets/tests/evaluate/ets/global_function/global_function.expected.ets diff --git a/static_core/plugins/ets/tests/evaluate/ets/global_function.patch.ets b/static_core/plugins/ets/tests/evaluate/ets/global_function/global_function.patch.ets similarity index 100% rename from static_core/plugins/ets/tests/evaluate/ets/global_function.patch.ets rename to static_core/plugins/ets/tests/evaluate/ets/global_function/global_function.patch.ets diff --git a/static_core/plugins/ets/tests/evaluate/ets/global_overloaded_function.base.ets b/static_core/plugins/ets/tests/evaluate/ets/global_overloaded_function/global_overloaded_function.base.ets similarity index 100% rename from static_core/plugins/ets/tests/evaluate/ets/global_overloaded_function.base.ets rename to static_core/plugins/ets/tests/evaluate/ets/global_overloaded_function/global_overloaded_function.base.ets diff --git a/static_core/plugins/ets/tests/evaluate/ets/global_overloaded_function.expected.ets b/static_core/plugins/ets/tests/evaluate/ets/global_overloaded_function/global_overloaded_function.expected.ets similarity index 100% rename from static_core/plugins/ets/tests/evaluate/ets/global_overloaded_function.expected.ets rename to static_core/plugins/ets/tests/evaluate/ets/global_overloaded_function/global_overloaded_function.expected.ets diff --git a/static_core/plugins/ets/tests/evaluate/ets/global_overloaded_function.patch.ets b/static_core/plugins/ets/tests/evaluate/ets/global_overloaded_function/global_overloaded_function.patch.ets similarity index 100% rename from static_core/plugins/ets/tests/evaluate/ets/global_overloaded_function.patch.ets rename to static_core/plugins/ets/tests/evaluate/ets/global_overloaded_function/global_overloaded_function.patch.ets diff --git a/static_core/plugins/ets/tests/evaluate/ets/global_primitive.base.ets b/static_core/plugins/ets/tests/evaluate/ets/global_primitive/global_primitive.base.ets similarity index 100% rename from static_core/plugins/ets/tests/evaluate/ets/global_primitive.base.ets rename to static_core/plugins/ets/tests/evaluate/ets/global_primitive/global_primitive.base.ets diff --git a/static_core/plugins/ets/tests/evaluate/ets/global_primitive.expected.ets b/static_core/plugins/ets/tests/evaluate/ets/global_primitive/global_primitive.expected.ets similarity index 100% rename from static_core/plugins/ets/tests/evaluate/ets/global_primitive.expected.ets rename to static_core/plugins/ets/tests/evaluate/ets/global_primitive/global_primitive.expected.ets diff --git a/static_core/plugins/ets/tests/evaluate/ets/global_primitive.patch.ets b/static_core/plugins/ets/tests/evaluate/ets/global_primitive/global_primitive.patch.ets similarity index 100% rename from static_core/plugins/ets/tests/evaluate/ets/global_primitive.patch.ets rename to static_core/plugins/ets/tests/evaluate/ets/global_primitive/global_primitive.patch.ets diff --git a/static_core/plugins/ets/tests/evaluate/ets/global_std_class.base.ets b/static_core/plugins/ets/tests/evaluate/ets/global_std_class/global_std_class.base.ets similarity index 100% rename from static_core/plugins/ets/tests/evaluate/ets/global_std_class.base.ets rename to static_core/plugins/ets/tests/evaluate/ets/global_std_class/global_std_class.base.ets diff --git a/static_core/plugins/ets/tests/evaluate/ets/global_std_class.expected.ets b/static_core/plugins/ets/tests/evaluate/ets/global_std_class/global_std_class.expected.ets similarity index 100% rename from static_core/plugins/ets/tests/evaluate/ets/global_std_class.expected.ets rename to static_core/plugins/ets/tests/evaluate/ets/global_std_class/global_std_class.expected.ets diff --git a/static_core/plugins/ets/tests/evaluate/ets/global_std_class.patch.ets b/static_core/plugins/ets/tests/evaluate/ets/global_std_class/global_std_class.patch.ets similarity index 100% rename from static_core/plugins/ets/tests/evaluate/ets/global_std_class.patch.ets rename to static_core/plugins/ets/tests/evaluate/ets/global_std_class/global_std_class.patch.ets diff --git a/static_core/plugins/ets/tests/evaluate/ets/global_user_class.base.ets b/static_core/plugins/ets/tests/evaluate/ets/global_user_class/global_user_class.base.ets similarity index 100% rename from static_core/plugins/ets/tests/evaluate/ets/global_user_class.base.ets rename to static_core/plugins/ets/tests/evaluate/ets/global_user_class/global_user_class.base.ets diff --git a/static_core/plugins/ets/tests/evaluate/ets/global_user_class.expected.ets b/static_core/plugins/ets/tests/evaluate/ets/global_user_class/global_user_class.expected.ets similarity index 100% rename from static_core/plugins/ets/tests/evaluate/ets/global_user_class.expected.ets rename to static_core/plugins/ets/tests/evaluate/ets/global_user_class/global_user_class.expected.ets diff --git a/static_core/plugins/ets/tests/evaluate/ets/global_user_class.patch.ets b/static_core/plugins/ets/tests/evaluate/ets/global_user_class/global_user_class.patch.ets similarity index 100% rename from static_core/plugins/ets/tests/evaluate/ets/global_user_class.patch.ets rename to static_core/plugins/ets/tests/evaluate/ets/global_user_class/global_user_class.patch.ets diff --git a/static_core/plugins/ets/tests/evaluate/ets/inheritance_0.base.ets b/static_core/plugins/ets/tests/evaluate/ets/inheritance_0/inheritance_0.base.ets similarity index 100% rename from static_core/plugins/ets/tests/evaluate/ets/inheritance_0.base.ets rename to static_core/plugins/ets/tests/evaluate/ets/inheritance_0/inheritance_0.base.ets diff --git a/static_core/plugins/ets/tests/evaluate/ets/inheritance_0.expected.ets b/static_core/plugins/ets/tests/evaluate/ets/inheritance_0/inheritance_0.expected.ets similarity index 100% rename from static_core/plugins/ets/tests/evaluate/ets/inheritance_0.expected.ets rename to static_core/plugins/ets/tests/evaluate/ets/inheritance_0/inheritance_0.expected.ets diff --git a/static_core/plugins/ets/tests/evaluate/ets/inheritance_0.patch.ets b/static_core/plugins/ets/tests/evaluate/ets/inheritance_0/inheritance_0.patch.ets similarity index 100% rename from static_core/plugins/ets/tests/evaluate/ets/inheritance_0.patch.ets rename to static_core/plugins/ets/tests/evaluate/ets/inheritance_0/inheritance_0.patch.ets diff --git a/static_core/plugins/ets/tests/evaluate/ets/inheritance_1.base.ets b/static_core/plugins/ets/tests/evaluate/ets/inheritance_1/inheritance_1.base.ets similarity index 100% rename from static_core/plugins/ets/tests/evaluate/ets/inheritance_1.base.ets rename to static_core/plugins/ets/tests/evaluate/ets/inheritance_1/inheritance_1.base.ets diff --git a/static_core/plugins/ets/tests/evaluate/ets/inheritance_1.expected.ets b/static_core/plugins/ets/tests/evaluate/ets/inheritance_1/inheritance_1.expected.ets similarity index 100% rename from static_core/plugins/ets/tests/evaluate/ets/inheritance_1.expected.ets rename to static_core/plugins/ets/tests/evaluate/ets/inheritance_1/inheritance_1.expected.ets diff --git a/static_core/plugins/ets/tests/evaluate/ets/inheritance_1.patch.ets b/static_core/plugins/ets/tests/evaluate/ets/inheritance_1/inheritance_1.patch.ets similarity index 100% rename from static_core/plugins/ets/tests/evaluate/ets/inheritance_1.patch.ets rename to static_core/plugins/ets/tests/evaluate/ets/inheritance_1/inheritance_1.patch.ets diff --git a/static_core/plugins/ets/tests/evaluate/ets/inheritance_2.base.ets b/static_core/plugins/ets/tests/evaluate/ets/inheritance_2/inheritance_2.base.ets similarity index 100% rename from static_core/plugins/ets/tests/evaluate/ets/inheritance_2.base.ets rename to static_core/plugins/ets/tests/evaluate/ets/inheritance_2/inheritance_2.base.ets diff --git a/static_core/plugins/ets/tests/evaluate/ets/inheritance_2.expected.ets b/static_core/plugins/ets/tests/evaluate/ets/inheritance_2/inheritance_2.expected.ets similarity index 100% rename from static_core/plugins/ets/tests/evaluate/ets/inheritance_2.expected.ets rename to static_core/plugins/ets/tests/evaluate/ets/inheritance_2/inheritance_2.expected.ets diff --git a/static_core/plugins/ets/tests/evaluate/ets/inheritance_2.patch.ets b/static_core/plugins/ets/tests/evaluate/ets/inheritance_2/inheritance_2.patch.ets similarity index 100% rename from static_core/plugins/ets/tests/evaluate/ets/inheritance_2.patch.ets rename to static_core/plugins/ets/tests/evaluate/ets/inheritance_2/inheritance_2.patch.ets diff --git a/static_core/plugins/ets/tests/evaluate/ets/local_variable_primitive.base.ets b/static_core/plugins/ets/tests/evaluate/ets/local_variable_primitive/local_variable_primitive.base.ets similarity index 100% rename from static_core/plugins/ets/tests/evaluate/ets/local_variable_primitive.base.ets rename to static_core/plugins/ets/tests/evaluate/ets/local_variable_primitive/local_variable_primitive.base.ets diff --git a/static_core/plugins/ets/tests/evaluate/ets/local_variable_primitive.expected.ets b/static_core/plugins/ets/tests/evaluate/ets/local_variable_primitive/local_variable_primitive.expected.ets similarity index 100% rename from static_core/plugins/ets/tests/evaluate/ets/local_variable_primitive.expected.ets rename to static_core/plugins/ets/tests/evaluate/ets/local_variable_primitive/local_variable_primitive.expected.ets diff --git a/static_core/plugins/ets/tests/evaluate/ets/local_variable_primitive.patch.ets b/static_core/plugins/ets/tests/evaluate/ets/local_variable_primitive/local_variable_primitive.patch.ets similarity index 100% rename from static_core/plugins/ets/tests/evaluate/ets/local_variable_primitive.patch.ets rename to static_core/plugins/ets/tests/evaluate/ets/local_variable_primitive/local_variable_primitive.patch.ets diff --git a/static_core/plugins/ets/tests/evaluate/ets/local_variable_std_class.base.ets b/static_core/plugins/ets/tests/evaluate/ets/local_variable_std_class/local_variable_std_class.base.ets similarity index 100% rename from static_core/plugins/ets/tests/evaluate/ets/local_variable_std_class.base.ets rename to static_core/plugins/ets/tests/evaluate/ets/local_variable_std_class/local_variable_std_class.base.ets diff --git a/static_core/plugins/ets/tests/evaluate/ets/local_variable_std_class.expected.ets b/static_core/plugins/ets/tests/evaluate/ets/local_variable_std_class/local_variable_std_class.expected.ets similarity index 100% rename from static_core/plugins/ets/tests/evaluate/ets/local_variable_std_class.expected.ets rename to static_core/plugins/ets/tests/evaluate/ets/local_variable_std_class/local_variable_std_class.expected.ets diff --git a/static_core/plugins/ets/tests/evaluate/ets/local_variable_std_class.patch.ets b/static_core/plugins/ets/tests/evaluate/ets/local_variable_std_class/local_variable_std_class.patch.ets similarity index 100% rename from static_core/plugins/ets/tests/evaluate/ets/local_variable_std_class.patch.ets rename to static_core/plugins/ets/tests/evaluate/ets/local_variable_std_class/local_variable_std_class.patch.ets diff --git a/static_core/plugins/ets/tests/evaluate/ets/local_variable_user_class.base.ets b/static_core/plugins/ets/tests/evaluate/ets/local_variable_user_class/local_variable_user_class.base.ets similarity index 100% rename from static_core/plugins/ets/tests/evaluate/ets/local_variable_user_class.base.ets rename to static_core/plugins/ets/tests/evaluate/ets/local_variable_user_class/local_variable_user_class.base.ets diff --git a/static_core/plugins/ets/tests/evaluate/ets/local_variable_user_class.expected.ets b/static_core/plugins/ets/tests/evaluate/ets/local_variable_user_class/local_variable_user_class.expected.ets similarity index 100% rename from static_core/plugins/ets/tests/evaluate/ets/local_variable_user_class.expected.ets rename to static_core/plugins/ets/tests/evaluate/ets/local_variable_user_class/local_variable_user_class.expected.ets diff --git a/static_core/plugins/ets/tests/evaluate/ets/local_variable_user_class.patch.ets b/static_core/plugins/ets/tests/evaluate/ets/local_variable_user_class/local_variable_user_class.patch.ets similarity index 100% rename from static_core/plugins/ets/tests/evaluate/ets/local_variable_user_class.patch.ets rename to static_core/plugins/ets/tests/evaluate/ets/local_variable_user_class/local_variable_user_class.patch.ets diff --git a/static_core/plugins/ets/tests/evaluate/ets/static_function.base.ets b/static_core/plugins/ets/tests/evaluate/ets/static_function/static_function.base.ets similarity index 100% rename from static_core/plugins/ets/tests/evaluate/ets/static_function.base.ets rename to static_core/plugins/ets/tests/evaluate/ets/static_function/static_function.base.ets diff --git a/static_core/plugins/ets/tests/evaluate/ets/static_function.expected.ets b/static_core/plugins/ets/tests/evaluate/ets/static_function/static_function.expected.ets similarity index 100% rename from static_core/plugins/ets/tests/evaluate/ets/static_function.expected.ets rename to static_core/plugins/ets/tests/evaluate/ets/static_function/static_function.expected.ets diff --git a/static_core/plugins/ets/tests/evaluate/ets/static_function.patch.ets b/static_core/plugins/ets/tests/evaluate/ets/static_function/static_function.patch.ets similarity index 100% rename from static_core/plugins/ets/tests/evaluate/ets/static_function.patch.ets rename to static_core/plugins/ets/tests/evaluate/ets/static_function/static_function.patch.ets diff --git a/static_core/runtime/include/tooling/vreg_value.h b/static_core/runtime/include/tooling/vreg_value.h index 5a9b7c86fc..f82d5ae53a 100644 --- a/static_core/runtime/include/tooling/vreg_value.h +++ b/static_core/runtime/include/tooling/vreg_value.h @@ -22,14 +22,14 @@ namespace ark::tooling { class VRegValue { public: - explicit VRegValue(int64_t value = 0) : value_(value) {} + constexpr explicit VRegValue(int64_t value = 0) : value_(value) {} - int64_t GetValue() const + constexpr int64_t GetValue() const { return value_; } - void SetValue(int64_t value) + constexpr void SetValue(int64_t value) { value_ = value; } diff --git a/static_core/tests/tests-u-runner/runner/plugins/evaluate/runner_evaluate.py b/static_core/tests/tests-u-runner/runner/plugins/evaluate/runner_evaluate.py index 8e574f1a8a..747bd3017e 100644 --- a/static_core/tests/tests-u-runner/runner/plugins/evaluate/runner_evaluate.py +++ b/static_core/tests/tests-u-runner/runner/plugins/evaluate/runner_evaluate.py @@ -53,16 +53,16 @@ class RunnerEvaluate(RunnerJS): Log.summary(_LOGGER, f"LIST_ROOT set to {self.list_root}") self.test_root = es2panda_test if self.test_root is None else self.test_root + self.test_root = self.test_root / "evaluate/ets" Log.summary(_LOGGER, f"TEST_ROOT set to {self.test_root}") self.collect_excluded_test_lists() self.collect_ignored_test_lists() - self.add_directory("evaluate/ets", "base.ets", flags=[]) - - def add_directory(self, directory: str, extension: str, flags: List[str]) -> None: - new_dir = Path(self.test_root).joinpath(directory) - super().add_directory(new_dir, extension, flags) + tests_root_dir = Path(self.test_root) + for child in tests_root_dir.iterdir(): + if child.is_dir(): + self.add_directory(str(child), "base.ets", flags=[]) def create_test(self, test_file: str, flags: List[str], is_ignored: bool) -> TestEvaluate: test = TestEvaluate(self.test_env, test_file, flags, get_test_id(test_file, self.test_root)) @@ -71,4 +71,4 @@ class RunnerEvaluate(RunnerJS): @property def default_work_dir_root(self) -> Path: - return Path("/tmp") / TestEvaluate.get_test_sources_dir() + return Path("/tmp") / "evaluate" diff --git a/static_core/tests/tests-u-runner/runner/plugins/evaluate/test_evaluate.py b/static_core/tests/tests-u-runner/runner/plugins/evaluate/test_evaluate.py index f7c0196f7c..5716de0b72 100644 --- a/static_core/tests/tests-u-runner/runner/plugins/evaluate/test_evaluate.py +++ b/static_core/tests/tests-u-runner/runner/plugins/evaluate/test_evaluate.py @@ -19,11 +19,11 @@ from __future__ import annotations import difflib from dataclasses import dataclass +from io import TextIOWrapper import json from os import path, makedirs from pathlib import Path -import re -from typing import Callable, Iterable, Optional, Tuple, List +from typing import Callable, Iterable, List, Optional from runner.plugins.ets.ets_templates.test_metadata import get_metadata, TestMetadata from runner.enum_types.fail_kind import FailKind @@ -37,12 +37,9 @@ class EvaluateOptions: panda_files_paths: List[str] class TestEvaluate(TestFileBased): - ETS_GLOBAL_RECORD = ".record ETSGLOBAL " - LOCATION_PATTER = re.compile(r"(\+|-)\s*(\"line\"|\"column\"): \d+") # `difflib` different strings are returned with either `-` or `+` as the first symbol. # Must filter out all non-diff strings NON_DIFF_MARKS = set([' ', '?']) - GLOBAL_RECORD_PREFIX = ".record " def __init__(self, test_env: TestEnv, test_path: str, flags: List[str], test_id: str) -> None: super().__init__(test_env, test_path, flags, test_id) @@ -53,10 +50,12 @@ class TestEvaluate(TestFileBased): # It's supposed if the first step is failed then no step is executed further self.fail_kind = None + test_id_as_path = Path(self.test_id) self.bytecode_path: Path = test_env.work_dir.intermediate - makedirs(self.bytecode_path / self.__class__.get_test_sources_dir(), exist_ok=True) + # Must get the directory of the test, as it represents the test's name. + makedirs(self.bytecode_path / test_id_as_path.parents[0], exist_ok=True) - self.base_filename = Path(Path(self.test_id).stem).stem + self.base_filename = Path(test_id_as_path.stem).stem self.base_abc = str(self.bytecode_path / f"{self.test_id}.abc") self.base_path = self.__class__.get_file_base_name(self.path) @@ -92,30 +91,9 @@ class TestEvaluate(TestFileBased): def expected_file_imports_base(self) -> bool: return "expected_imports_base" in self.metadata.params and bool(self.metadata.params["expected_imports_base"]) - # TODO: support multiple context files - # @property - # def dependent_files(self) -> Sequence[TestETSEvaluate]: - # if not self.metadata.files: - # return [] - - # tests = [] - # for file in self.metadata.files: - # test_path = Path(self.path).parent / Path(file) - # current_test_id = Path(self.test_id) - # test = self.__class__(self.test_env, str(test_path), self.flags, str(current_test_id.parent / Path(file))) - - # test_abc_name = f'{current_test_id.stem}_{Path(test.base_abc).name}' - # test_an_name = f'{current_test_id.stem}_{Path(test.test_an).name}' - - # test.base_abc = str(Path(test.base_abc).parent / Path(test_abc_name)) - # test.test_an = str(Path(test.base_abc).parent / Path(test_an_name)) - - # tests.append(test) - # return tests - # pylint: disable=too-many-return-statements def do_run(self) -> TestEvaluate: - # TODO: test context-independent cases + # NOTE(dslynko): must support cases with multiple context files self._run_compiler(self.path, self.base_abc, dump_ast=False) if not self.passed: return self @@ -165,8 +143,8 @@ class TestEvaluate(TestFileBased): """ expected_output = self._filter_expected_ast(expected_output) - patched_output, patched_imports = self._prepare_ast(json.loads(patched_output)) - expected_output, expected_imports = self._prepare_ast(json.loads(expected_output)) + patched_output, patched_imports = self.__class__._prepare_ast(json.loads(patched_output)) + expected_output, expected_imports = self.__class__._prepare_ast(json.loads(expected_output)) if not self._compare_ast_statements(patched_output, expected_output): return @@ -174,7 +152,6 @@ class TestEvaluate(TestFileBased): self._compare_ast_import_decls(patched_imports, expected_imports) def _compare_ast_statements(self, patched_output: str, expected_output: str) -> bool: - print("Compare AST statements:") diff = difflib.ndiff(patched_output.splitlines(), expected_output.splitlines()) # Find strings indicating differences other than IR locations. error_list = [ @@ -191,23 +168,21 @@ class TestEvaluate(TestFileBased): return True def _compare_ast_import_decls(self, patched_imports: str, expected_imports: str) -> bool: - print("Compare AST import declarations:") - - patch_specifiers_list = [] + patch_specifiers_list: list[dict] = [] expected_specifiers_map: dict = {} - patch_obj = list(json.loads(patched_imports)) - expected_obj = list(json.loads(expected_imports)) + patch_obj: list[dict] = list(json.loads(patched_imports)) + expected_obj: list[dict] = list(json.loads(expected_imports)) for patch_import_decl in patch_obj: - specifiers_list = patch_import_decl.get('specifiers') + specifiers_list = patch_import_decl.get("specifiers") for s in specifiers_list: patch_specifiers_list.append(s) for expected_import_decl in expected_obj: - specifiers = expected_import_decl.get('specifiers') + specifiers: list[dict] = expected_import_decl.get("specifiers") for s in specifiers: - local_import_name = s.get('local').get('name') + local_import_name = s.get("local").get("name") expected_specifiers_map[local_import_name] = s if len(expected_specifiers_map) != len(patch_specifiers_list): @@ -219,7 +194,7 @@ class TestEvaluate(TestFileBased): return False for specifier in patch_specifiers_list: - local_import_name = specifier.get('local').get('name') + local_import_name = specifier.get("local").get("name") expected_specifier = expected_specifiers_map.get(local_import_name, None) if expected_specifier == None: self.passed = False @@ -249,7 +224,7 @@ class TestEvaluate(TestFileBased): imports_list = [] statements_list = obj.get("statements", None) - def filter_lambda(statement): + def filter_lambda(statement: dict[str, str]): if statement.get("type") == "ImportDeclaration": imports_list.append(statement) return 0 @@ -318,9 +293,11 @@ class TestEvaluate(TestFileBased): expected_func_body = self._fetch_bytecode_function(expected, self.expected_func_name, prefix_to_delete) patch_func_body = self._fetch_bytecode_function(patch, self.patch_func_name, prefix_to_delete) - if len(expected_func_body) != len(patch_func_body): - print("Line number is different.") + if expected_func_body is None or patch_func_body is None or len(expected_func_body) != len(patch_func_body): self.passed = False + self.fail_kind = FailKind.COMPARE_FAIL + error_report = "Expected and patch bytecode differ in count" + self.report = TestReport("", error_report, 0) return diff = difflib.ndiff("".join(expected_func_body).splitlines(), "".join(patch_func_body).splitlines()) @@ -333,11 +310,9 @@ class TestEvaluate(TestFileBased): self.fail_kind = FailKind.COMPARE_FAIL error_report = "\n" + "\n".join(error_list) self.report = TestReport("", error_report, 0) - - return @staticmethod - def _fetch_bytecode_function(file, name, prefix) -> list: + def _fetch_bytecode_function(file: TextIOWrapper, name: str, prefixes: Iterable[str]) -> list[str] | None: """ Find function by 'name' in the disassembled code in the given 'file'. Return a list of the found function body lines. @@ -349,18 +324,21 @@ class TestEvaluate(TestFileBased): while idx < len(lines): if lines[idx][:len(name)] == name: while True: - func_body.append(__class__._remove_prefix(lines[idx], prefix)) - if lines[idx][0] == '}': + func_body.append(__class__._remove_prefix(lines[idx], prefixes)) + if lines[idx][0] == "}": return func_body idx += 1 idx += 1 + return None @staticmethod - def _remove_prefix(line: str, prefix: list | str) -> str: - for p in prefix: - line = line.replace(p, '') + def _remove_prefix(line: str, prefixes: Iterable[str]) -> str: + for p in prefixes: + line = line.replace(p, "") return line +# ===================================================================================================================== +# Compiler and disassembler launchers # ===================================================================================================================== def _create_evaluate_options(self) -> EvaluateOptions: @@ -432,7 +410,3 @@ class TestEvaluate(TestFileBased): if self.is_negative_compile: return return_code != 0 return return_code == 0 and path.exists(output_path) and path.getsize(output_path) > 0 - - @staticmethod - def get_test_sources_dir() -> Path: - return Path("evaluate") / "ets" -- Gitee From 39daf89e9630c8fd3c75d285b97ea0efc284b4fd Mon Sep 17 00:00:00 2001 From: Denis Slynko Date: Thu, 18 Jul 2024 16:49:15 +0300 Subject: [PATCH 2/2] [PT] Refactor evaluation helpers * Move evaluation helpers functions out from `debuggable_thread.cpp` * Provide exception details in `Runtime.evaluate` Signed-off-by: Denis Slynko --- .../runtime/tests/tooling/debugger/client.py | 183 ------------------ .../tooling/inspector/debuggable_thread.cpp | 178 +++++++---------- .../tooling/inspector/debuggable_thread.h | 30 ++- .../inspector/{ => evaluation}/base64.h | 2 +- .../tooling/inspector/evaluation/helpers.cpp | 127 ++++++++++++ .../tooling/inspector/evaluation/helpers.h | 32 +++ .../runtime/tooling/inspector/inspector.cpp | 8 +- .../runtime/tooling/inspector/inspector.h | 2 +- .../tooling/inspector/inspector_server.cpp | 12 +- .../tooling/inspector/inspector_server.h | 3 +- .../inspector/types/exception_details.h | 6 +- 11 files changed, 276 insertions(+), 307 deletions(-) delete mode 100644 static_core/runtime/tests/tooling/debugger/client.py rename static_core/runtime/tooling/inspector/{ => evaluation}/base64.h (98%) create mode 100644 static_core/runtime/tooling/inspector/evaluation/helpers.cpp create mode 100644 static_core/runtime/tooling/inspector/evaluation/helpers.h diff --git a/static_core/runtime/tests/tooling/debugger/client.py b/static_core/runtime/tests/tooling/debugger/client.py deleted file mode 100644 index 6828472cd6..0000000000 --- a/static_core/runtime/tests/tooling/debugger/client.py +++ /dev/null @@ -1,183 +0,0 @@ -import argparse -import base64 -import json -import logging -import pathlib -import subprocess -import sys -import unittest -import websockets - - -class InspectorClient: - def __init__(self, websocket: websockets.WebSocketClientProtocol): - self.__ws = websocket - self.__current_id = 0 - - @property - def websocket(self) -> websockets.WebSocketClientProtocol: - return self.__ws - - @property - def id(self) -> int: - return self.__current_id - - @staticmethod - def load_panda_file(path: pathlib.Path) -> bytes: - with path.open(mode='rb') as f: - return base64.b64encode(f.read()) - - async def call_method(self, method: str, **kwargs): - wait_response = True - if "wait_response" in kwargs: - wait_response = kwargs["wait_response"] - del kwargs["wait_response"] - - return await self.__send({"method": method, "params": kwargs}, wait_response) - - async def step_into(self): - return await self.__step("stepInto") - - async def step_over(self): - return await self.__step("stepOver") - - async def step_out(self): - return await self.__step("stepOut") - - async def set_breakpoint_by_url(self, file_name: str, line_no: int): - base_file_regex = f".*{file_name}.*" - response = await self.call_method( - "Debugger.setBreakpointByUrl", - lineNumber=line_no, - urlRegex=base_file_regex - ) - assert "breakpointId" in response["result"] and int(response["result"]["breakpointId"]) == 0 - assert "locations" in response["result"] and len(response["result"]["locations"]) >= 1 - - async def __step(self, kind: str): - return await self.call_method(f"Debugger.{kind}", breakOnAsyncCall=True, skipList=[]) - - async def __send(self, message: dict, wait_response: bool) -> dict | None: - self.__current_id += 1 - - message["id"] = self.id - await self.__ws.send(json.dumps(message)) - - if wait_response: - while True: - message = json.loads(await self.__ws.recv()) - if "id" in message and int(message["id"]) == self.id: - return message - - return None - - -class RuntimeEvaluateTest(unittest.IsolatedAsyncioTestCase): - def __init__(self, host: str, port: int, path_to_libarkinspector: str, path_to_etsstdlib: str, timeout: int = 5) -> None: - self.__ws_url = f"ws://{host}:{port}" - self.__path_to_libarkinspector = path_to_libarkinspector - self.__path_to_etsstdlib = path_to_etsstdlib - self.__timeout = timeout - self.__ark_proc: subprocess.Popen | None = None - - async def asyncTearDown(self): - await super().asyncTearDown() - if self.__ark_proc: - self.__ark_proc.kill() - - outs, errs = self.__ark_proc.communicate() - self.__ark_proc = None - print(f"Runtime has not finished properly:\nStdout:\n{outs}\nStderr:\n{errs}\n") - - async def test_basic_evaluation(self): - self.__ark_proc = self.__run_ark_vm("klass_base.abc", "ETSGLOBAL::foo") - await self.__run_test(self._do_basic_evaluation, "klass_patch.abc", "klass_base", 30) - try: - outs: str = self.__ark_proc.communicate(timeout=self.__timeout)[0] - - self.assertEqual(self.__ark_proc.returncode, 0) - self.__ark_proc = None - - self.assertTrue("Starting evaluation patch" in outs) - self.assertTrue("Ending evaluation patch" in outs) - self.assertTrue("From global foo: 1" in outs) - after_evaluation_message_pos: int = outs.find("After evaluation") - self.assertGreater(after_evaluation_message_pos, 0) - self.assertGreater(outs.find("string string string string string", end=after_evaluation_message_pos), 0) - except subprocess.TimeoutExpired: - # Will be processed during teardown. - pass - - async def _do_basic_evaluation(self, sender: InspectorClient, patch_file_path: str, base_file_name: str, bt_line: int): - await RuntimeEvaluateTest._initialize_debugger(sender) - - await sender.set_breakpoint_by_url(base_file_name, bt_line) - - # Step to reach the breakpoint. - await sender.step_over() - event = json.loads(await sender.websocket.recv()) - assert "method" in event and event["method"] == "Debugger.paused" - - # Execute evaluate patch. - patch = InspectorClient.load_panda_file(pathlib.Path(patch_file_path)) - result = await sender.call_method("Runtime.evaluate", expression=patch.decode("utf-8")) - print("Evaluation result: ", result["result"]) - - await sender.call_method("Debugger.resume") - # Double step out for correct runtime shutdown. - await sender.step_out() - await sender.step_out() - - @staticmethod - async def _initialize_debugger(sender: InspectorClient): - await sender.call_method("Runtime.enable", wait_response=False) - event = json.loads(await sender.websocket.recv()) - assert event["method"] == "Runtime.executionContextCreated" - response = json.loads(await sender.websocket.recv()) - assert response["id"] == sender.id - - response = await sender.call_method("Debugger.enable", maxScriptsCacheSize=10000000) - assert "debuggerId" in response["result"] and response["result"]["debuggerId"] == "debugger" - - await sender.call_method("Debugger.setPauseOnExceptions", state="all") - await sender.call_method("Runtime.runIfWaitingForDebugger") - - async def __run_test(self, test_case, *args): - async with websockets.connect(self.__ws_url) as ws: - sender = InspectorClient(ws) - await test_case(sender, *args) - - def __run_ark_vm(self, path_to_abc: str, entrypoint: str, break_on_start: bool = True) -> subprocess.Popen: - cmd = [ - "ark", - "--log-info=debugger", - "--load-runtimes=ets", - "--interpreter-type=cpp", - f"--boot-panda-files={self.__path_to_etsstdlib}", - f"--debugger-library-path={self.__path_to_libarkinspector}", - ] - if break_on_start: - cmd += ["--debugger-break-on-start"] - cmd += [ - path_to_abc, - entrypoint, - ] - return subprocess.Popen( - cmd, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - encoding="utf-8", - errors="ignore", - ) - - -if __name__ == "__main__": - parser = argparse.ArgumentParser() - - parser.add_argument("-v", "--verbose", help="Verbose websocket logs", action="store_true") - args = parser.parse_args() - - if args.verbose: - logging.basicConfig(stream=sys.stdout, level=logging.DEBUG) - - unittest.main() diff --git a/static_core/runtime/tooling/inspector/debuggable_thread.cpp b/static_core/runtime/tooling/inspector/debuggable_thread.cpp index 2d8526d1d7..e988e9ccab 100644 --- a/static_core/runtime/tooling/inspector/debuggable_thread.cpp +++ b/static_core/runtime/tooling/inspector/debuggable_thread.cpp @@ -13,11 +13,8 @@ * limitations under the License. */ -#include "runtime/tooling/inspector/base64.h" #include "runtime/tooling/inspector/debuggable_thread.h" -#include "libpandabase/os/filesystem.h" - namespace ark::tooling::inspector { DebuggableThread::DebuggableThread( ManagedThread *thread, @@ -150,125 +147,61 @@ bool DebuggableThread::RequestToObjectRepository(std::function> FindPatchFilePath(const panda_file::File *pf) +EvaluationResult DebuggableThread::Evaluate(const std::string &bcFragment) { - for (uint32_t id : pf->GetClasses()) { - panda_file::File::EntityId entityId(id); - if (pf->IsExternal(entityId)) { - continue; - } - - panda_file::ClassDataAccessor cda(*pf, entityId); - auto sourceFileId = cda.GetSourceFileId(); - if (sourceFileId) { - const auto *sourceFilePath = utf::Mutf8AsCString(pf->GetStringData(sourceFileId.value()).data); - auto optLang = cda.GetSourceLang(); - return {sourceFilePath, optLang}; - } - } - return {nullptr, {}}; -} - -static std::string PathBaseName(const std::string &path) -{ - constexpr std::string_view delim = os::file::File::GetPathDelim(); - return path.substr(path.find_last_of(delim) + 1); -} - -static std::pair GetClassAndMethodNames(const std::string &sourceFilePath) -{ - std::string methodName = PathBaseName(os::RemoveExtension(sourceFilePath)); - std::string className = "L" + methodName + "/" + methodName + ";"; - return {className, methodName}; -} - -struct EntryMethodInfo { - std::optional optLang {}; - const uint8_t *classDescriptor {nullptr}; - const uint8_t *methodName {nullptr}; -}; - -static Method *LoadFileAndGetEntryMethod(ClassLinkerContext *ctx, std::unique_ptr &&pf, - EntryMethodInfo &&methodInfo) -{ - auto *linker = Runtime::GetCurrent()->GetClassLinker(); - linker->AddPandaFile(std::move(pf), ctx); - - ClassLinkerExtension *extension = - linker->GetExtension(methodInfo.optLang.value_or(panda_file::SourceLang::PANDA_ASSEMBLY)); - ASSERT(extension); - - auto *klass = extension->GetClass(methodInfo.classDescriptor, true, ctx); - if (!klass) { - LOG(WARNING, DEBUGGER) << "Evaluate failed to load class"; - return nullptr; - } - - return klass->GetDirectMethod(methodInfo.methodName); -} - -std::optional DebuggableThread::Evaluate(const std::string &bcFragment) -{ - std::optional eval; + std::optional optResult; + std::optional optException; // TODO: ensure that evaluation is done in proper context. - RequestToObjectRepository([this, bcFragment, &eval](ObjectRepository &objectRepo) { - std::string binaryBytecode(PtBase64::DecodedSize(bcFragment.size()), 0); - PtBase64::Decode(binaryBytecode.data(), bcFragment.c_str(), bcFragment.size()); - auto pf = ark::panda_file::OpenPandaFileFromMemory(binaryBytecode.c_str(), binaryBytecode.size()); - if (pf == nullptr) { - LOG(WARNING, DEBUGGER) << "Evaluate failed to read bytecode"; - return; - } - - // Source file name will be used as entry class and method name. - auto [sourceFilePath, optLang] = FindPatchFilePath(pf.get()); - if (sourceFilePath == nullptr) { - LOG(WARNING, DEBUGGER) << "Evaluate failed to find entry class"; - return; - } - - auto [className, methodName] = GetClassAndMethodNames(sourceFilePath); - LOG(INFO, DEBUGGER) << "Evaluation class and method: " << methodName; - + RequestToObjectRepository([this, bcFragment, &optResult, &optException](ObjectRepository &objectRepo) { ASSERT(thread_->GetCurrentFrame()); auto *ctx = thread_->GetCurrentFrame()->GetMethod()->GetClass()->GetLoadContext(); - // TODO: implement evaluation-patches cache - auto *method = LoadFileAndGetEntryMethod( - ctx, std::move(pf), - {optLang, utf::CStringAsMutf8(className.c_str()), utf::CStringAsMutf8(methodName.c_str())}); - if (!method) { - LOG(WARNING, DEBUGGER) << "Evaluate failed to find method"; + + auto *method = LoadEvaluationPatch(ctx, bcFragment); + if (method == nullptr) { return; } - auto *prevException = thread_->GetException(); - Value result; - { - // Some debugger functionality must be disabled in evaluation mode. - evaluation_mode_ = true; - // Evaluation patch should not accept any arguments. - result = method->Invoke(thread_, nullptr); - evaluation_mode_ = false; - } + auto [result, exception] = InvokeEvaluationMethod(method); - if ((prevException == nullptr) && thread_->HasPendingException()) { - LOG(WARNING, DEBUGGER) << "Evaluation has completed with a exception " - << thread_->GetException()->ClassAddr()->GetName(); + if (exception != nullptr) { + optException.emplace(CreateExceptionDetails(objectRepo, exception)); } - // Restore the previous exception, clears any new exceptions as well. - thread_->SetException(prevException); auto resultTypeId = method->GetReturnType().GetId(); - eval.emplace(objectRepo.CreateObject(result, resultTypeId)); + optResult.emplace(objectRepo.CreateObject(result, resultTypeId)); }); - return eval; + return std::make_pair(optResult, optException); +} + +std::pair DebuggableThread::InvokeEvaluationMethod(Method *method) +{ + ASSERT(method); + + auto *prevException = thread_->GetException(); + + // Some debugger functionality must be disabled in evaluation mode. + evaluationMode_ = true; + // Evaluation patch should not accept any arguments. + Value result = method->Invoke(thread_, nullptr); + evaluationMode_ = false; + + ObjectHeader *newException = nullptr; + if (thread_->HasPendingException() && thread_->GetException() != prevException) { + newException = thread_->GetException(); + LOG(WARNING, DEBUGGER) << "Evaluation has completed with a exception " + << thread_->GetException()->ClassAddr()->GetName(); + } + // Restore the previous exception. + thread_->SetException(prevException); + + return std::make_pair(result, newException); } void DebuggableThread::OnException(bool uncaught) { - if (evaluation_mode_) { + if (evaluationMode_) { return; } os::memory::LockHolder lock(mutex_); @@ -281,7 +214,7 @@ void DebuggableThread::OnException(bool uncaught) void DebuggableThread::OnFramePop() { - if (evaluation_mode_) { + if (evaluationMode_) { return; } os::memory::LockHolder lock(mutex_); @@ -290,7 +223,7 @@ void DebuggableThread::OnFramePop() bool DebuggableThread::OnMethodEntry() { - if (evaluation_mode_) { + if (evaluationMode_) { return false; } os::memory::LockHolder lock(mutex_); @@ -299,7 +232,7 @@ bool DebuggableThread::OnMethodEntry() void DebuggableThread::OnSingleStep(const PtLocation &location) { - if (evaluation_mode_) { + if (evaluationMode_) { return; } os::memory::LockHolder lock(mutex_); @@ -388,4 +321,35 @@ void DebuggableThread::Resume() postResume_(); } + +ExceptionDetails DebuggableThread::CreateExceptionDetails(ObjectRepository &objectRepo, ObjectHeader *exception) +{ + ASSERT(exception); + + const char *source = nullptr; + size_t lineNum = 0; + auto walker = StackWalker::Create(thread_); + if (walker.HasFrame()) { + auto *method = walker.GetMethod(); + source = utf::Mutf8AsCString(method->GetClassSourceFile().data); + lineNum = method->GetLineNumFromBytecodeOffset(walker.GetBytecodePc()); + } + + // Yet unable to retrieve column number by bytecode, so pass 0. + // NOTE(dslynko): must retrieve exception message in language agnostic manner. + ExceptionDetails exceptionDetails(GetNewExceptionId(), "", lineNum, 0); + if (source != nullptr) { + exceptionDetails.SetUrl(source); + } + + auto exceptionRemoteObject = objectRepo.CreateObject(TypedValue::Reference(exception)); + exceptionDetails.SetExceptionObject(exceptionRemoteObject); + + return exceptionDetails; +} + +size_t DebuggableThread::GetNewExceptionId() +{ + return currentExceptionId_++; +} } // namespace ark::tooling::inspector diff --git a/static_core/runtime/tooling/inspector/debuggable_thread.h b/static_core/runtime/tooling/inspector/debuggable_thread.h index 4cf2e94563..0baae3791c 100644 --- a/static_core/runtime/tooling/inspector/debuggable_thread.h +++ b/static_core/runtime/tooling/inspector/debuggable_thread.h @@ -17,8 +17,10 @@ #define PANDA_TOOLING_INSPECTOR_DEBUGGABLE_THREAD_H #include "runtime/tooling/debugger.h" +#include "runtime/tooling/inspector/evaluation/helpers.h" #include "runtime/tooling/inspector/object_repository.h" #include "runtime/tooling/inspector/thread_state.h" +#include "runtime/tooling/inspector/types/exception_details.h" #include "runtime/tooling/inspector/types/numeric_id.h" #include "runtime/tooling/inspector/types/pause_on_exceptions_state.h" @@ -85,8 +87,14 @@ public: // Executes a request to object repository on a paused thread (does nothing for running threads) bool RequestToObjectRepository(std::function request); - // Executes a given bytecode fragment (accepted in binary format) on this thread - std::optional Evaluate(const std::string &bcFragment); + /** + * @brief Loads provided bytecode file and executes evaluation method. + * File must contain public class with equally named static method taking no arguments. + * Name of both class and method must equal source code file name, path to which must be included into binary. + * @param bcFragment base64 encoded panda file. + * @returns pair of optional result and exception details if it was raised by evaluated code. + */ + EvaluationResult Evaluate(const std::string &bcFragment); /// The following methods should be called on an application thread @@ -106,6 +114,13 @@ public: // Notification that a call to console was performed. Returns its arguments presented as remote objects std::vector OnConsoleCall(const PandaVector &arguments); + /** + * @brief Executes method in evaluation mode. + * @param method static method taking no arguments. + * @returns pair of result (might be void) and pointer to exception (if appeared). + */ + std::pair InvokeEvaluationMethod(Method *method); + private: // Suspends a paused thread. Should be called on an application thread void Suspend(ObjectRepository &objectRepository, const std::vector &hitBreakpoints, @@ -114,6 +129,14 @@ private: // Marks a paused thread as not suspended. Should be called on the server thread void Resume() REQUIRES(mutex_); + /** + * @brief Get verbose information about the raised exception. + */ + ExceptionDetails CreateExceptionDetails(ObjectRepository &objectRepo, ObjectHeader *exception); + + size_t GetNewExceptionId(); + +private: ManagedThread *thread_; std::function &, ObjectHeader *)> preSuspend_; std::function &, ObjectHeader *)> postSuspend_; @@ -125,9 +148,10 @@ private: os::memory::Mutex mutex_; ThreadState state_ GUARDED_BY(mutex_); bool suspended_ GUARDED_BY(mutex_) {false}; - bool evaluation_mode_ {false}; + bool evaluationMode_ {false}; std::optional> request_ GUARDED_BY(mutex_); os::memory::ConditionVariable requestDone_ GUARDED_BY(mutex_); + size_t currentExceptionId_ {0}; }; } // namespace ark::tooling::inspector diff --git a/static_core/runtime/tooling/inspector/base64.h b/static_core/runtime/tooling/inspector/evaluation/base64.h similarity index 98% rename from static_core/runtime/tooling/inspector/base64.h rename to static_core/runtime/tooling/inspector/evaluation/base64.h index 455275d5ea..6906bfc966 100644 --- a/static_core/runtime/tooling/inspector/base64.h +++ b/static_core/runtime/tooling/inspector/evaluation/base64.h @@ -19,7 +19,7 @@ #include namespace ark::tooling::inspector { -// TODO: borrowed from arkcompiler_toolchain, deside the future fate of this class. +// TODO(dslynko): borrowed from arkcompiler_toolchain, deside the future fate of this class. class PtBase64 { public: PtBase64() = default; diff --git a/static_core/runtime/tooling/inspector/evaluation/helpers.cpp b/static_core/runtime/tooling/inspector/evaluation/helpers.cpp new file mode 100644 index 0000000000..d8b5cd0d65 --- /dev/null +++ b/static_core/runtime/tooling/inspector/evaluation/helpers.cpp @@ -0,0 +1,127 @@ +/** + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "libpandabase/os/filesystem.h" +#include "libpandafile/class_data_accessor.h" +#include "runtime/include/runtime.h" +#include "runtime/tooling/inspector/evaluation/base64.h" +#include "runtime/tooling/inspector/evaluation/helpers.h" + +namespace ark::tooling::inspector { + +static std::string PathBaseName(const std::string &path) +{ + constexpr std::string_view delim = os::file::File::GetPathDelim(); + return path.substr(path.find_last_of(delim) + 1); +} + +static bool ValidateEvaluationMethod(Method *method) +{ + if (!method) { + LOG(WARNING, DEBUGGER) << "Evaluate failed to find method"; + return false; + } + // No arguments can be provided by interface. + if (method->GetNumArgs() != 0) { + LOG(WARNING, DEBUGGER) << "Evaluation cannot have any arguments, but got " << method->GetNumArgs(); + return false; + } + // Currently support only primitive types + auto type = method->GetReturnType(); + if (!type.IsPrimitive()) { + LOG(WARNING, DEBUGGER) << "Evaluation method can return only primitive values"; + return false; + } + + return true; +} + +static std::pair> FindPatchFilePath(const panda_file::File *pf) +{ + for (uint32_t id : pf->GetClasses()) { + panda_file::File::EntityId entityId(id); + if (pf->IsExternal(entityId)) { + continue; + } + + panda_file::ClassDataAccessor cda(*pf, entityId); + auto sourceFileId = cda.GetSourceFileId(); + if (sourceFileId) { + const auto *sourceFilePath = utf::Mutf8AsCString(pf->GetStringData(sourceFileId.value()).data); + auto optLang = cda.GetSourceLang(); + return {sourceFilePath, optLang}; + } + } + return {nullptr, {}}; +} + +static std::pair GetClassAndMethodNames(const std::string &sourceFilePath) +{ + std::string methodName = PathBaseName(os::RemoveExtension(sourceFilePath)); + std::string className = "L" + methodName + "/" + methodName + ";"; + return {className, methodName}; +} + +static Method *LoadFileAndGetEntryMethod(ClassLinkerContext *ctx, std::unique_ptr &&pf) +{ + // Extract information about evaluation entry method. By convention, the passed + // bytecode must contain a class with a static method, which both have the same names as panda file has. + auto [sourceFilePath, optLang] = FindPatchFilePath(pf.get()); + if (sourceFilePath == nullptr) { + LOG(WARNING, DEBUGGER) << "Evaluate failed to find entry class"; + return nullptr; + } + auto [className, methodName] = GetClassAndMethodNames(sourceFilePath); + LOG(INFO, DEBUGGER) << "Evaluation class and method: " << methodName; + + // Add panda file into context of the target thread. + auto *linker = Runtime::GetCurrent()->GetClassLinker(); + // TODO: verify that the panda file was not loaded before. + linker->AddPandaFile(std::move(pf), ctx); + ClassLinkerExtension *extension = linker->GetExtension(optLang.value_or(panda_file::SourceLang::PANDA_ASSEMBLY)); + ASSERT(extension != nullptr); + + // TODO: may use `GetClass` which accepts panda file as argument, as it can be faster. + // Linker must accept nullptr as error handler, otherwise managed exception will occur on failure. + auto *klass = linker->GetClass(utf::CStringAsMutf8(className.c_str()), true, extension->ResolveContext(ctx)); + if (klass == nullptr) { + LOG(WARNING, DEBUGGER) << "Evaluate failed to load class"; + return nullptr; + } + return klass->GetDirectMethod(utf::CStringAsMutf8(methodName.c_str())); +} + +Method *LoadEvaluationPatch(ClassLinkerContext *ctx, const std::string &encodedBytecodeFragment) +{ + ASSERT(ctx != nullptr); + ASSERT(!encodedBytecodeFragment.empty()); + + auto sz = encodedBytecodeFragment.size(); + std::string binaryBytecode(PtBase64::DecodedSize(sz), 0); + PtBase64::Decode(binaryBytecode.data(), encodedBytecodeFragment.c_str(), sz); + auto pf = ark::panda_file::OpenPandaFileFromMemory(binaryBytecode.c_str(), binaryBytecode.size()); + if (pf == nullptr) { + LOG(WARNING, DEBUGGER) << "Evaluate failed to read bytecode"; + return nullptr; + } + + auto *method = LoadFileAndGetEntryMethod(ctx, std::move(pf)); + if (ValidateEvaluationMethod(method)) { + return method; + } + return nullptr; +} + +} // namespace ark::tooling::inspector diff --git a/static_core/runtime/tooling/inspector/evaluation/helpers.h b/static_core/runtime/tooling/inspector/evaluation/helpers.h new file mode 100644 index 0000000000..28ffeeacb0 --- /dev/null +++ b/static_core/runtime/tooling/inspector/evaluation/helpers.h @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef PANDA_TOOLING_INSPECTOR_EVALUATION_HELPERS_H +#define PANDA_TOOLING_INSPECTOR_EVALUATION_HELPERS_H + +#include "runtime/class_linker_context.h" +#include "runtime/tooling/inspector/types/exception_details.h" + +#include + +namespace ark::tooling::inspector { + +using EvaluationResult = std::pair, std::optional>; + +Method *LoadEvaluationPatch(ClassLinkerContext *ctx, const std::string &encodedBytecodeFragment); + +} // namespace ark::tooling::inspector + +#endif // PANDA_TOOLING_INSPECTOR_EVALUATION_HELPERS_H diff --git a/static_core/runtime/tooling/inspector/inspector.cpp b/static_core/runtime/tooling/inspector/inspector.cpp index 5a99d488c6..7e2c7d788c 100644 --- a/static_core/runtime/tooling/inspector/inspector.cpp +++ b/static_core/runtime/tooling/inspector/inspector.cpp @@ -493,21 +493,21 @@ void Inspector::DebuggableThreadPostSuspend(PtThread thread, ObjectRepository &o }); } -std::optional Inspector::Evaluate(PtThread thread, const std::string &bcFragment) +EvaluationResult Inspector::Evaluate(PtThread thread, const std::string &bcFragment) { os::memory::ReadLockHolder lock(vmDeathLock_); if (UNLIKELY(CheckVmDead())) { - return {}; + return EvaluationResult({}, {}); } auto it = threads_.find(thread); if (it == threads_.end()) { - return {}; + return EvaluationResult({}, {}); } if (UNLIKELY(!it->second.IsPaused())) { LogDebuggerNotPaused("evaluate"); - return {}; + return EvaluationResult({}, {}); } return it->second.Evaluate(bcFragment); diff --git a/static_core/runtime/tooling/inspector/inspector.h b/static_core/runtime/tooling/inspector/inspector.h index 3cd002d324..f8296068eb 100644 --- a/static_core/runtime/tooling/inspector/inspector.h +++ b/static_core/runtime/tooling/inspector/inspector.h @@ -96,7 +96,7 @@ private: void DebuggableThreadPostSuspend(PtThread thread, ObjectRepository &objectRepository, const std::vector &hitBreakpoints, ObjectHeader *exception); - std::optional Evaluate(PtThread thread, const std::string &bcFragment); + EvaluationResult Evaluate(PtThread thread, const std::string &bcFragment); ALWAYS_INLINE bool CheckVmDead() REQUIRES_SHARED(vmDeathLock_) { diff --git a/static_core/runtime/tooling/inspector/inspector_server.cpp b/static_core/runtime/tooling/inspector/inspector_server.cpp index 3412b09ed7..5a25363e62 100644 --- a/static_core/runtime/tooling/inspector/inspector_server.cpp +++ b/static_core/runtime/tooling/inspector/inspector_server.cpp @@ -523,8 +523,7 @@ void InspectorServer::OnCallRuntimeRunIfWaitingForDebugger(std::function(PtThread, const std::string &)> &&handler) +void InspectorServer::OnCallRuntimeEvaluate(std::function &&handler) { server_.OnCall("Runtime.evaluate", [this, handler = std::move(handler)](auto &sessionId, auto &result, const JsonObject ¶ms) { @@ -536,13 +535,18 @@ void InspectorServer::OnCallRuntimeEvaluate( return; } - auto evalResult = handler(thread, *expressionStr); - // set result to undefined on failure + auto [evalResult, exceptionDetails] = handler(thread, *expressionStr); + if (!evalResult) { + // NOTE(dslynko): might return error instead of `undefined`. LOG(DEBUG, DEBUGGER) << "Evaluation failed for expression: " << *expressionStr; evalResult.emplace(RemoteObject::Undefined()); } result.AddProperty("result", evalResult->ToJson()); + + if (exceptionDetails) { + result.AddProperty("exceptionDetails", exceptionDetails->ToJson()); + } }); } diff --git a/static_core/runtime/tooling/inspector/inspector_server.h b/static_core/runtime/tooling/inspector/inspector_server.h index e203232841..1a26bda10c 100644 --- a/static_core/runtime/tooling/inspector/inspector_server.h +++ b/static_core/runtime/tooling/inspector/inspector_server.h @@ -21,6 +21,7 @@ #include "types/numeric_id.h" #include "console_call_type.h" +#include "tooling/inspector/evaluation/helpers.h" #include "tooling/inspector/types/pause_on_exceptions_state.h" #include "tooling/inspector/types/property_descriptor.h" #include "tooling/inspector/types/remote_object.h" @@ -92,7 +93,7 @@ public: void OnCallRuntimeGetProperties( std::function(PtThread, RemoteObjectId, bool)> &&handler); void OnCallRuntimeRunIfWaitingForDebugger(std::function &&handler); - void OnCallRuntimeEvaluate(std::function(PtThread, const std::string &)> &&handler); + void OnCallRuntimeEvaluate(std::function &&handler); private: struct CallFrameInfo { diff --git a/static_core/runtime/tooling/inspector/types/exception_details.h b/static_core/runtime/tooling/inspector/types/exception_details.h index c102c6f2c3..c67bd78711 100644 --- a/static_core/runtime/tooling/inspector/types/exception_details.h +++ b/static_core/runtime/tooling/inspector/types/exception_details.h @@ -89,18 +89,18 @@ public: return url_.has_value(); } - const std::optional &GetException() const + const std::optional &GetExceptionObject() const { return exception_; } - ExceptionDetails &SetException(RemoteObject exception) + ExceptionDetails &SetExceptionObject(RemoteObject exception) { exception_ = std::move(exception); return *this; } - bool HasException() const + bool HasExceptionObject() const { return exception_.has_value(); } -- Gitee