已合并
NormalizePath .. unresolved, crashes on Windows #14366
NormalizePath .. unresolved, crashes on Windows #14366
已合并
fanglou创建于 6月30日
共 3 个文件变更+148-24
@@ -382,6 +382,7 @@ set(ARKBASE_TESTS_SOURCES
382 tests/ringbuf/lock_free_ring_buffer_test.cpp382 tests/ringbuf/lock_free_ring_buffer_test.cpp
383 tests/base_thread_test.cpp383 tests/base_thread_test.cpp
384 tests/mem_hooks_test.cpp384 tests/mem_hooks_test.cpp
385+ tests/filesystem_test.cpp
385)386)
386 387 
387if (NOT ENABLE_UNIT_TESTS_FULL_COVERAGE)388if (NOT ENABLE_UNIT_TESTS_FULL_COVERAGE)
@@ -1,5 +1,5 @@
1-/*1+/**
2- * Copyright (c) 2021-2025 Huawei Device Co., Ltd.2+ * Copyright (c) 2021-2026 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at5 * You may obtain a copy of the License at
Timi3Timi3
Timi3Timi36月30日

[兼容性平台] 兼容性检测完成 触发人:@fanglou

结论:不兼容

原因 存在兼容性破坏。具体证据是 arkcompiler_runtime_core/static_core/libarkbase/os/filesystem.h::ark::os::NormalizePath 以 PANDA_PUBLIC_API 导出,PR 14366 修改 arkcompiler_runtime_core/static_core/libarkbase/os/filesystem.cpp::ark::os::NormalizePath 的末尾片段处理:路径末尾为 . 或 .. 时,旧实现把末尾 .. 作为普通片段保留,新实现会弹出前一段或跳过 .,旧调用方对同一合法路径字符串可能得到不同返回值。另一个证据是 arkcompiler_ets_frontend/ets2panda/util/importPathManager.cpp::ImportPathManager::AppendExtensionOrIndexFileIfOmitted,PR 10991 把目录导入且目录内没有 index 文件和可作为 package fraction 的文件时的流程,从继续返回目录真实路径并进入后续 package/varbinder 处理,改为记录 MODULE_INDEX_MISSING 并返回空路径;旧 ETS/ArkTS 源码如 import from ./no_ets_files 或 ../.. 在新编译器下会产生不同的诊断和解析控制流。两项变化都影响旧源码或旧调用方的可观察结果,需要调用方或测试基线适配。/ Compatibility is broken. Evidence: arkcompiler_runtime_core/static_core/libarkbase/os/filesystem.h::ark::os::NormalizePath is exported with PANDA_PUBLIC_API, and PR 14366 changes arkcompiler_runtime_core/static_core/libarkbase/os/filesystem.cpp::ark::os::NormalizePath handling of trailing path segments. For paths ending in . or .., the old implementation preserved a trailing .. segment, while the new implementation removes the previous segment or skips ., so existing callers can observe different return values for the same valid path string. Another evidence point is arkcompiler_ets_frontend/ets2panda/util/importPathManager.cpp::ImportPathManager::AppendExtensionOrIndexFileIfOmitted: PR 10991 changes directory imports with no index file and no package-fraction file from continuing with the real directory path into later package/varbinder handling to logging MODULE_INDEX_MISSING and returning an empty path. Existing ETS/ArkTS sources such as imports from ./no_ets_files or ../.. therefore see different diagnostics and resolution control flow. These changes affect observable behavior of old callers or old source code and require adaptation of callers or test baselines.

分析过程 分析了 openharmony/arkcompiler_runtime_core#14366 和 openharmony/arkcompiler_ets_frontend#10991,确认提交范围、变更文件和相关调用面。前者修改 arkcompiler_runtime_core/static_core/libarkbase/os/filesystem.cpp 中导出的 ark::os::NormalizePath,后者修改 arkcompiler_ets_frontend/ets2panda/util/importPathManager.cpp 的目录导入解析并新增覆盖测试。/ Analyzed openharmony/arkcompiler_runtime_core#14366 and openharmony/arkcompiler_ets_frontend#10991, including commit ranges, changed files, and relevant call sites. The first changes exported ark::os::NormalizePath in arkcompiler_runtime_core/static_core/libarkbase/os/filesystem.cpp; the second changes directory import resolution in arkcompiler_ets_frontend/ets2panda/util/importPathManager.cpp and adds tests.

likedislike
fanglou
fanglou
7月3日 评论:
Timi3Timi36月30日

[兼容性平台] 兼容性检测完成 触发人:@fanglou

结论:不兼容

原因 存在兼容性破坏。具体证据:arkcompiler_runtime_core/static_core/libarkbase/os/filesystem.h:46 将 ark::os::NormalizePath(const std::string &) 声明为 PANDA_PUBLIC_API;PR 14366 修改 arkcompiler_runtime_core/static_core/libarkbase/os/filesystem.cpp:88 的 ark::os::NormalizePath 实现中末尾路径片段为 "."/".." 的处理。旧版本对 "a/.."、"dir/.." 这类旧输入会把末尾 ".." 作为普通片段保留并返回规范化字符串,新版本会弹出上一段;当弹出后 parts 为空时后续仍访问 parts[0],旧调用方在相同输入下可能从返回字符串变为崩溃/未定义行为,且返回值语义也改变。PR 10991 还修改 arkcompiler_ets_frontend/ets2panda/util/importPathManager.cpp:1430 的 ImportPathManager::AppendExtensionOrIndexFileIfOmitted 流程并新增 ImportPathManager::ResolveDirectoryImport;存在目录但没有 index.[ets|ts] 或 .ets package fraction 的旧 import 以前会解析为 PACKAGE 并进入 arkcompiler_ets_frontend/ets2panda/varbinder/ETSBinder.cpp:1314 的后续绑定/查找流程,现在直接记录 diagnostic::MODULE_INDEX_MISSING 并返回空路径;同一旧工程的错误产生阶段和诊断行为发生变化,旧自动化或依赖旧失败形态的流程需要适配。 / This is incompatible. Evidence: arkcompiler_runtime_core/static_core/libarkbase/os/filesystem.h:46 exports ark::os::NormalizePath(const std::string &) as PANDA_PUBLIC_API; PR 14366 changes the ark::os::NormalizePath implementation in arkcompiler_runtime_core/static_core/libarkbase/os/filesystem.cpp:88 for trailing "."/".." path parts. Old inputs such as "a/.." or "dir/.." previously returned a normalized string with trailing ".." preserved as a path part, while the new code pops the previous part; if that leaves parts empty, it still reads parts[0], so an old caller can observe a crash/undefined behavior instead of a returned string, and the return-value semantics also change. PR 10991 changes ImportPathManager::AppendExtensionOrIndexFileIfOmitted in arkcompiler_ets_frontend/ets2panda/util/importPathManager.cpp:1430 and adds ImportPathManager::ResolveDirectoryImport; an import targeting an existing directory without index.[ets|ts] or .ets package fractions now reports diagnostic::MODULE_INDEX_MISSING and returns an empty path instead of first resolving to PACKAGE and reaching the later binding/lookup flow in arkcompiler_ets_frontend/ets2panda/varbinder/ETSBinder.cpp:1314, so the observable failure stage and diagnostic behavior for the same old project changes.

分析过程 分析了 openharmony/arkcompiler_runtime_core#14366 和 openharmony/arkcompiler_ets_frontend#10991,确认了各 PR 的提交范围、变更文件、导出声明、调用路径和相关诊断定义;重点核对 arkcompiler_runtime_core/static_core/libarkbase/os/filesystem.{h,cpp} 中 ark::os::NormalizePath 的公开导出属性,以及 arkcompiler_ets_frontend/ets2panda/util/importPathManager.{h,cpp} 对目录 import 的解析流程。 / Analyzed openharmony/arkcompiler_runtime_core#14366 and openharmony/arkcompiler_ets_frontend#10991, including commit scope, changed files, exported declarations, call paths, and diagnostics; the review focused on the exported ark::os::NormalizePath contract in arkcompiler_runtime_core/static_core/libarkbase/os/filesystem.{h,cpp} and directory import resolution in arkcompiler_ets_frontend/ets2panda/util/importPathManager.{h,cpp}.

likedislike
fanglou
fanglou
7月3日 评论:
@@ -85,6 +85,57 @@ std::string RemoveExtension(const std::string &filepath)
85 return filepath;85 return filepath;
86}86}
87 87 
88+static void HandleDotDotSegment(std::vector<std::string_view> &parts)
89+{
90+ if (parts.empty()) {
91+ parts.emplace_back("..");
92+ return;
93+ }
94+ if (parts.back().empty()) {
95+ return;
96+ }
97+ if (parts.back() == "..") {
98+ parts.emplace_back("..");
99+ return;
100+ }
101+ parts.pop_back();
102+}
103+ 
104+static void AppendFinalPathSegment(std::vector<std::string_view> &parts, std::string_view sv)
105+{
106+ if (sv.empty() || sv == ".") {
107+ return;
108+ }
109+ if (sv == "..") {
110+ HandleDotDotSegment(parts);
111+ return;
112+ }
113+ parts.push_back(sv);
114+}
115+ 
116+static std::string JoinPathParts(std::vector<std::string_view> &parts, char delimChar)
117+{
118+ if (parts.empty()) {
119+ return ".";
120+ }
121+ std::stringstream ss;
122+ if (parts[0].empty()) {
123+ ss << delimChar;
124+ for (size_t i = 1; i < parts.size(); ++i) {
125+ if (i > 1) {
126+ ss << delimChar;
127+ }
128+ ss << parts[i];
129+ }
130+ } else {
131+ ss << parts[0];
132+ for (size_t i = 1; i < parts.size(); ++i) {
133+ ss << delimChar << parts[i];
134+ }
135+ }
136+ return ss.str();
137+}
138+ 
88std::string NormalizePath(const std::string &path)139std::string NormalizePath(const std::string &path)
89{140{
90 if (path.empty()) {141 if (path.empty()) {
@@ -119,35 +170,16 @@ std::string NormalizePath(const std::string &path)
119 }170 }
120 171 
121 if (sv == "..") {172 if (sv == "..") {
122- if (parts.empty()) {173+ HandleDotDotSegment(parts);
123- parts.emplace_back("");
124- continue;
125- }
126- 
127- if (!parts.back().empty()) {
128- parts.pop_back();
129- }
130 continue;174 continue;
131 }175 }
132 176 
133 parts.push_back(sv);177 parts.push_back(sv);
134 }178 }
135 179 
136- std::string_view sv(&path[begin], length);180+ AppendFinalPathSegment(parts, std::string_view(&path[begin], length));
dmitriitr
dmitriitrdmitriitr7月1日

This still leaves a crash path when a relative path is fully reduced by the final segment. For example, NormalizePath("a/..") pushes "a" in the loop, then this call passes the final ".." to AppendFinalPathSegment, which pops the only element from parts. JoinPathParts then asserts !parts.empty() and reads parts[0]; in release builds that becomes an out-of-bounds access/UB. The same happens for inputs like "." or "./" where the final segment is skipped and no part is ever added.

The old implementation always appended the final segment before joining, so these non-empty inputs returned a string instead of crashing. Please handle the fully-collapsed relative case before calling JoinPathParts (and ideally add focused tests for ".", "./", and "a/..").

likedislike
137- parts.push_back(sv);
138 181 
139- std::stringstream ss;182+ return JoinPathParts(parts, delimChar);
140- 
141- ASSERT(!parts.empty());
142- i = 0;
143- ss << parts[i++];
144- 
145- while (i < parts.size()) {
146- ss << delim;
147- ss << parts[i++];
148- }
149- 
150- return ss.str();
151}183}
152 184 
153std::string GetCurrentWorkingDirectory()185std::string GetCurrentWorkingDirectory()
@@ -0,0 +1,91 @@
1+/**
2+ * Copyright (c) 2026 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 "libarkbase/os/filesystem.h"
17+ 
18+#include <gtest/gtest.h>
19+ 
20+namespace ark::os::test {
21+ 
22+TEST(NormalizePathTest, CurrentDirectory)
23+{
24+ EXPECT_EQ(NormalizePath("."), ".");
25+ EXPECT_EQ(NormalizePath("./"), ".");
26+ EXPECT_EQ(NormalizePath("././"), ".");
27+}
28+ 
29+TEST(NormalizePathTest, DotDotCollapsesRealComponent)
30+{
31+ EXPECT_EQ(NormalizePath("a/.."), ".");
32+ EXPECT_EQ(NormalizePath("a/b/.."), "a");
33+ EXPECT_EQ(NormalizePath("a/b/../c"), "a/c");
34+ EXPECT_EQ(NormalizePath("a/../b"), "b");
35+}
36+ 
37+TEST(NormalizePathTest, DotDotPreservesDotDotComponent)
38+{
39+ EXPECT_EQ(NormalizePath(".."), "..");
40+ EXPECT_EQ(NormalizePath("../.."), "../..");
41+ EXPECT_EQ(NormalizePath("../../a"), "../../a");
42+ EXPECT_EQ(NormalizePath("a/../.."), "..");
43+}
44+ 
45+TEST(NormalizePathTest, AbsolutePaths)
46+{
47+ EXPECT_EQ(NormalizePath("/"), "/");
48+ EXPECT_EQ(NormalizePath("/a/b/../c"), "/a/c");
49+ EXPECT_EQ(NormalizePath("/a/.."), "/");
50+ EXPECT_EQ(NormalizePath("/../a"), "/a");
51+ EXPECT_EQ(NormalizePath("/.."), "/");
52+}
53+ 
54+TEST(NormalizePathTest, EmptyPath)
55+{
56+ EXPECT_EQ(NormalizePath(""), "");
57+}
58+ 
59+TEST(NormalizePathTest, DuplicateDelimiters)
60+{
61+ EXPECT_EQ(NormalizePath("a//b"), "a/b");
62+ EXPECT_EQ(NormalizePath("/a//b"), "/a/b");
63+}
64+ 
65+TEST(NormalizePathTest, TrailingDelimiter)
66+{
67+ EXPECT_EQ(NormalizePath("a/"), "a");
68+ EXPECT_EQ(NormalizePath("/a/"), "/a");
69+ EXPECT_EQ(NormalizePath("../"), "..");
70+}
71+ 
72+TEST(NormalizePathTest, DotSegments)
73+{
74+ EXPECT_EQ(NormalizePath("a/./b"), "a/b");
75+ EXPECT_EQ(NormalizePath("./a"), "a");
76+ EXPECT_EQ(NormalizePath("a/."), "a");
77+}
78+ 
79+TEST(NormalizePathTest, MixedDotAndDotDot)
80+{
81+ EXPECT_EQ(NormalizePath("a/./../b"), "b");
82+ EXPECT_EQ(NormalizePath("./a/./b/../c"), "a/c");
83+}
84+ 
85+TEST(NormalizePathTest, DotDotCantPopRootMarker)
86+{
87+ EXPECT_EQ(NormalizePath("/a/../.."), "/");
88+ EXPECT_EQ(NormalizePath("/../../a"), "/a");
89+}
90+ 
91+} // namespace ark::os::test