已合并
[feat] add json validator #5068
sjtulxh创建于 26 天前
[feat] add json validator #5068
已合并
sjtulxh创建于 26 天前
14 个文件变更+1246-94
@@ -208,11 +208,13 @@ add_executable(ascendc_ut_asc_compile_exporter
208 asc_compile_exporter/test_file_utils.cpp208 asc_compile_exporter/test_file_utils.cpp
209 asc_compile_exporter/test_manifest_bundle_types.cpp209 asc_compile_exporter/test_manifest_bundle_types.cpp
210 asc_compile_exporter/test_process_runner.cpp210 asc_compile_exporter/test_process_runner.cpp
211+ asc_compile_exporter/test_resource_manifest_validator.cpp
211 asc_compile_exporter/test_ascendc_tool_log.cpp212 asc_compile_exporter/test_ascendc_tool_log.cpp
212 ${ASCENDC_DIR}/tools/build/asc_compile_exporter/build_workspace.cpp213 ${ASCENDC_DIR}/tools/build/asc_compile_exporter/build_workspace.cpp
213 ${ASCENDC_DIR}/tools/build/asc_compile_exporter/bundle_source_generator.cpp214 ${ASCENDC_DIR}/tools/build/asc_compile_exporter/bundle_source_generator.cpp
214 ${ASCENDC_DIR}/tools/build/asc_compile_exporter/collected_manifest_repository.cpp215 ${ASCENDC_DIR}/tools/build/asc_compile_exporter/collected_manifest_repository.cpp
215 ${ASCENDC_DIR}/tools/build/asc_compile_exporter/manifest_bundle_compiler.cpp216 ${ASCENDC_DIR}/tools/build/asc_compile_exporter/manifest_bundle_compiler.cpp
217+ ${ASCENDC_DIR}/tools/build/asc_compile_exporter/resource_manifest_validator.cpp
216 ${ASCENDC_DIR}/tools/build/common/file_utils.cpp218 ${ASCENDC_DIR}/tools/build/common/file_utils.cpp
217 ${ASCENDC_DIR}/tools/build/common/process_runner.cpp219 ${ASCENDC_DIR}/tools/build/common/process_runner.cpp
218)220)
@@ -208,11 +208,15 @@ protected:
208 }208 }
209 209 
210 void CreateManifest(210 void CreateManifest(
211- const std::string& unit, const std::string& name, const std::string& baseDir = "resources") const211+ const std::string& unit, const std::string& name, const std::string& resourcePath = "resources") const
212 {212 {
213 WriteFile(213 WriteFile(
214 JoinTestPath(unit, name + "_manifest.json"),214 JoinTestPath(unit, name + "_manifest.json"),
215- "{\"base_dir\":\"" + baseDir + "\",\"name\":\"" + name + "\"}");215+ "{\"schema_version\":\"1.0\",\"resource_id\":\"" + name +
216+ "\",\"soc_version\":\"test\",\"resource_path\":\"" + resourcePath +
217+ "\",\"kernels\":[{\"kernel_name\":\"" + name +
218+ "\",\"objects\":[{\"object_name\":\"kernel.o\",\"object_type\":\"basic\",\"commands\":[{\"type\":"
219+ "\"compile\",\"stage\":0,\"cmd\":[\"${output}/kernel.o\"]}],\"outputs\":[\"${output}/kernel.o\"]}]}]}");
216 }220 }
217 221 
218 static void ExpectCompileFailure(const BuildCollectedBundleRequest& request)222 static void ExpectCompileFailure(const BuildCollectedBundleRequest& request)
@@ -329,7 +333,7 @@ TEST_F(AscCompileExporterTest, RejectsMalformedJsonWithoutReplacingOutput)
329 EXPECT_EQ(ReadFile(request.outputPath), "previous bundle");333 EXPECT_EQ(ReadFile(request.outputPath), "previous bundle");
330}334}
331 335 
332-TEST_F(AscCompileExporterTest, RejectsBaseDirectoryOutsideManifestDirectory)336+TEST_F(AscCompileExporterTest, RejectsResourcePathOutsideManifestDirectory)
333{337{
334 const std::string unit = JoinTestPath(root_, "collection/Traversal");338 const std::string unit = JoinTestPath(root_, "collection/Traversal");
335 WriteFile(JoinTestPath(root_, "collection/outside/kernel.cpp"), "source");339 WriteFile(JoinTestPath(root_, "collection/outside/kernel.cpp"), "source");
@@ -10,6 +10,9 @@
10 10 
11#include <gtest/gtest.h>11#include <gtest/gtest.h>
12 12 
13+#include <atomic>
14+#include <cstdlib>
15+#include <new>
13#include <sstream>16#include <sstream>
14#include <string>17#include <string>
15#include <vector>18#include <vector>
@@ -21,6 +24,26 @@
21#include "asc_compile_exporter.cpp"24#include "asc_compile_exporter.cpp"
22#undef main25#undef main
23 26 
27+namespace {
28+std::atomic<bool> g_failNextAllocation{false};
29+}
30+ 
31+void* operator new(std::size_t size)
32+{
33+ if (g_failNextAllocation.exchange(false)) {
34+ throw std::bad_alloc();
35+ }
36+ void* allocation = std::malloc(size == 0U ? 1U : size);
37+ if (allocation == nullptr) {
38+ throw std::bad_alloc();
39+ }
40+ return allocation;
41+}
42+ 
43+void operator delete(void* allocation) noexcept { std::free(allocation); }
44+ 
45+void operator delete(void* allocation, std::size_t) noexcept { std::free(allocation); }
46+ 
24namespace {47namespace {
25 48 
26using asc_compile_exporter_test::ModuleTest;49using asc_compile_exporter_test::ModuleTest;
@@ -45,6 +68,22 @@ private:
45 std::streambuf* previous_;68 std::streambuf* previous_;
46};69};
47 70 
71+class CoutCapture final {
72+public:
73+ CoutCapture() : previous_(std::cout.rdbuf(stream_.rdbuf())) {}
74+ 
75+ ~CoutCapture() { std::cout.rdbuf(previous_); }
76+ 
77+ std::string Content() const { return stream_.str(); }
78+ 
79+ CoutCapture(const CoutCapture&) = delete;
80+ CoutCapture& operator=(const CoutCapture&) = delete;
81+ 
82+private:
83+ std::ostringstream stream_;
84+ std::streambuf* previous_;
85+};
86+ 
48bool ParseCommand(std::vector<std::string> arguments, BuildCollectedBundleRequest& request)87bool ParseCommand(std::vector<std::string> arguments, BuildCollectedBundleRequest& request)
49{88{
50 std::vector<char*> argv;89 std::vector<char*> argv;
@@ -62,7 +101,7 @@ int RunCommand(std::vector<std::string> arguments)
62 for (std::string& argument : arguments) {101 for (std::string& argument : arguments) {
63 argv.push_back(const_cast<char*>(argument.c_str()));102 argv.push_back(const_cast<char*>(argument.c_str()));
64 }103 }
65- return ::Run(static_cast<int>(argv.size()), argv.data());104+ return RunWithExceptionHandling(static_cast<int>(argv.size()), argv.data());
66}105}
67 106 
68int RunProgramCommand(std::vector<std::string> arguments)107int RunProgramCommand(std::vector<std::string> arguments)
@@ -75,6 +114,17 @@ int RunProgramCommand(std::vector<std::string> arguments)
75 return AscCompileExporterProgramMain(static_cast<int>(argv.size()), argv.data());114 return AscCompileExporterProgramMain(static_cast<int>(argv.size()), argv.data());
76}115}
77 116 
117+size_t CountOccurrences(const std::string& text, const std::string& value)
118+{
119+ size_t count = 0U;
120+ size_t position = 0U;
121+ while ((position = text.find(value, position)) != std::string::npos) {
122+ ++count;
123+ position += value.size();
124+ }
125+ return count;
126+}
127+ 
78TEST(CompileExporterCliUnitTest, ParseJobsAcceptsOnlyPositiveUint32Values)128TEST(CompileExporterCliUnitTest, ParseJobsAcceptsOnlyPositiveUint32Values)
79{129{
80 uint32_t jobs = 99U;130 uint32_t jobs = 99U;
@@ -221,6 +271,7 @@ TEST_F(ModuleTest, CliValidatesArgumentsWhenRead)
221 271 
222TEST_F(ModuleTest, RunPassesValidatedArgumentsToCompiler)272TEST_F(ModuleTest, RunPassesValidatedArgumentsToCompiler)
223{273{
274+ CerrCapture capture;
224 const std::string manifestRoot = FileUtils::JoinPath(root_, "collection");275 const std::string manifestRoot = FileUtils::JoinPath(root_, "collection");
225 const std::string output = FileUtils::JoinPath(root_, "output/bundle.so");276 const std::string output = FileUtils::JoinPath(root_, "output/bundle.so");
226 ASSERT_TRUE(FileUtils::CreateDirectories(manifestRoot));277 ASSERT_TRUE(FileUtils::CreateDirectories(manifestRoot));
@@ -230,6 +281,7 @@ TEST_F(ModuleTest, RunPassesValidatedArgumentsToCompiler)
230 "--cxx", "/usr/bin/c++"}),281 "--cxx", "/usr/bin/c++"}),
231 1);282 1);
232 EXPECT_TRUE(FileUtils::IsDirectory(FileUtils::ParentPath(output)));283 EXPECT_TRUE(FileUtils::IsDirectory(FileUtils::ParentPath(output)));
284+ EXPECT_EQ(CountOccurrences(capture.Content(), "Compilation failed. Please check plog for details."), 1U);
233}285}
234 286 
235TEST_F(ModuleTest, RunBuildsBundleWithKeywordArguments)287TEST_F(ModuleTest, RunBuildsBundleWithKeywordArguments)
@@ -264,6 +316,41 @@ TEST(CompileExporterCliUnitTest, ProgramMainReturnsRunStatusAndPrintsArgument)
264 EXPECT_NE(capture.Content().find("Usage:"), std::string::npos);316 EXPECT_NE(capture.Content().find("Usage:"), std::string::npos);
265 EXPECT_NE(capture.Content().find("Required arguments:"), std::string::npos);317 EXPECT_NE(capture.Content().find("Required arguments:"), std::string::npos);
266 EXPECT_NE(capture.Content().find("Optional arguments:"), std::string::npos);318 EXPECT_NE(capture.Content().find("Optional arguments:"), std::string::npos);
319+ EXPECT_EQ(CountOccurrences(capture.Content(), "Compilation failed. Please check plog for details."), 0U);
320+}
321+ 
322+TEST(CompileExporterCliUnitTest, HelpOptionsPrintUsageToStdoutAndExitSuccessfully)
323+{
324+ {
325+ CerrCapture errorCapture;
326+ CoutCapture outputCapture;
327+ EXPECT_EQ(RunProgramCommand({"asc_compile_exporter", "-h"}), 0);
328+ EXPECT_NE(outputCapture.Content().find("Usage:"), std::string::npos);
329+ EXPECT_NE(outputCapture.Content().find(" -h, --help"), std::string::npos);
330+ EXPECT_EQ(errorCapture.Content(), "");
331+ }
332+ 
333+ CerrCapture errorCapture;
334+ CoutCapture outputCapture;
335+ EXPECT_EQ(RunProgramCommand({"asc_compile_exporter", "--unknown", "--help"}), 0);
336+ EXPECT_NE(outputCapture.Content().find("Usage:"), std::string::npos);
337+ EXPECT_NE(outputCapture.Content().find(" -h, --help"), std::string::npos);
338+ EXPECT_EQ(errorCapture.Content(), "");
339+}
340+ 
341+TEST(CompileExporterCliUnitTest, ProgramMainDirectsUsersToPlogAfterException)
342+{
343+ CerrCapture capture;
344+ std::vector<std::string> arguments = {"asc_compile_exporter", std::string(1024U, 'x')};
345+ std::vector<char*> argv;
346+ argv.reserve(arguments.size());
347+ for (std::string& argument : arguments) {
348+ argv.push_back(const_cast<char*>(argument.c_str()));
349+ }
350+ 
351+ g_failNextAllocation = true;
352+ EXPECT_EQ(RunWithExceptionHandling(static_cast<int>(argv.size()), argv.data()), 1);
353+ EXPECT_EQ(CountOccurrences(capture.Content(), "Compilation failed. Please check plog for details."), 1U);
267}354}
268 355 
269} // namespace356} // namespace
@@ -27,6 +27,7 @@ namespace {
27constexpr size_t TEST_MAX_DIRECTORY_DEPTH = 64U;27constexpr size_t TEST_MAX_DIRECTORY_DEPTH = 64U;
28 28 
29using asc_compile_exporter_test::ModuleTest;29using asc_compile_exporter_test::ModuleTest;
30+using asc_compile_exporter_test::ReadTestFile;
30using asc_compile_exporter_test::WriteTestFile;31using asc_compile_exporter_test::WriteTestFile;
31 32 
32TEST_F(ModuleTest, RepositorySortsManifestsAndResourcesAndSkipsEmptyFiles)33TEST_F(ModuleTest, RepositorySortsManifestsAndResourcesAndSkipsEmptyFiles)
@@ -54,42 +55,80 @@ TEST_F(ModuleTest, RepositorySortsManifestsAndResourcesAndSkipsEmptyFiles)
54 EXPECT_EQ(std::string(units[1].files[0].data.begin(), units[1].files[0].data.end()), "a");55 EXPECT_EQ(std::string(units[1].files[0].data.begin(), units[1].files[0].data.end()), "a");
55}56}
56 57 
57-TEST_F(ModuleTest, RepositoryReportsManifestJsonAndBaseDirErrors)58+TEST_F(ModuleTest, RepositoryReportsManifestJsonAndResourcePathErrors)
58{59{
59 const std::string malformed = FileUtils::JoinPath(root_, "malformed/Unit");60 const std::string malformed = FileUtils::JoinPath(root_, "malformed/Unit");
60 WriteTestFile(FileUtils::JoinPath(malformed, "resources/kernel.cpp"), "source");61 WriteTestFile(FileUtils::JoinPath(malformed, "resources/kernel.cpp"), "source");
61- WriteTestFile(FileUtils::JoinPath(malformed, "Unit_manifest.json"), "{\"base_dir\":");62+ WriteTestFile(FileUtils::JoinPath(malformed, "Unit_manifest.json"), "{\"resource_path\":");
62 std::vector<ManifestUnit> units;63 std::vector<ManifestUnit> units;
63 EXPECT_FALSE(CollectedManifestRepository(FileUtils::JoinPath(root_, "malformed")).Load(units));64 EXPECT_FALSE(CollectedManifestRepository(FileUtils::JoinPath(root_, "malformed")).Load(units));
64 65 
65- const std::string missing = FileUtils::JoinPath(root_, "missing-base/Unit");66+ const std::string missing = FileUtils::JoinPath(root_, "missing-resource-path/Unit");
66 WriteTestFile(FileUtils::JoinPath(missing, "resources/kernel.cpp"), "source");67 WriteTestFile(FileUtils::JoinPath(missing, "resources/kernel.cpp"), "source");
67- WriteTestFile(FileUtils::JoinPath(missing, "Unit_manifest.json"), "{}");68+ CreateManifest(missing, "Unit");
68- EXPECT_FALSE(CollectedManifestRepository(FileUtils::JoinPath(root_, "missing-base")).Load(units));69+ std::string missingResourcePath = ReadTestFile(FileUtils::JoinPath(missing, "Unit_manifest.json"));
70+ const std::string field = "\"resource_path\":\"resources\",";
71+ missingResourcePath.erase(missingResourcePath.find(field), field.size());
72+ WriteTestFile(FileUtils::JoinPath(missing, "Unit_manifest.json"), missingResourcePath);
73+ EXPECT_FALSE(CollectedManifestRepository(FileUtils::JoinPath(root_, "missing-resource-path")).Load(units));
69 74 
70 const std::string wrongType = FileUtils::JoinPath(root_, "wrong-type/Unit");75 const std::string wrongType = FileUtils::JoinPath(root_, "wrong-type/Unit");
71 WriteTestFile(FileUtils::JoinPath(wrongType, "resources/kernel.cpp"), "source");76 WriteTestFile(FileUtils::JoinPath(wrongType, "resources/kernel.cpp"), "source");
72- WriteTestFile(FileUtils::JoinPath(wrongType, "Unit_manifest.json"), "{\"base_dir\":7}");77+ CreateManifest(wrongType, "Unit");
78+ std::string wrongResourcePath = ReadTestFile(FileUtils::JoinPath(wrongType, "Unit_manifest.json"));
79+ const size_t value = wrongResourcePath.find("\"resources\"");
80+ wrongResourcePath.replace(value, std::string("\"resources\"").size(), "7");
81+ WriteTestFile(FileUtils::JoinPath(wrongType, "Unit_manifest.json"), wrongResourcePath);
73 EXPECT_FALSE(CollectedManifestRepository(FileUtils::JoinPath(root_, "wrong-type")).Load(units));82 EXPECT_FALSE(CollectedManifestRepository(FileUtils::JoinPath(root_, "wrong-type")).Load(units));
74}83}
75 84 
76-TEST_F(ModuleTest, RepositorySkipsSymlinkedManifestsAndAcceptsAbsoluteBaseDir)85+TEST_F(ModuleTest, RepositorySkipsSymlinkedManifests)
77{86{
78- const std::string collection = FileUtils::JoinPath(root_, "collection-absolute");87+ const std::string collection = FileUtils::JoinPath(root_, "collection-symlink");
79 const std::string unit = FileUtils::JoinPath(collection, "Unit");88 const std::string unit = FileUtils::JoinPath(collection, "Unit");
80- const std::string resources = FileUtils::JoinPath(unit, "resources");89+ WriteTestFile(FileUtils::JoinPath(unit, "resources/kernel.cpp"), "source");
81- WriteTestFile(FileUtils::JoinPath(resources, "kernel.cpp"), "source");90+ CreateManifest(unit, "Unit");
82- WriteTestFile(FileUtils::JoinPath(unit, "Unit_manifest.json"), "{\"base_dir\":\"" + resources + "\"}");
83 91 
84 const std::string ignoredManifest = FileUtils::JoinPath(root_, "ignored_manifest.json");92 const std::string ignoredManifest = FileUtils::JoinPath(root_, "ignored_manifest.json");
85- WriteTestFile(ignoredManifest, "{\"base_dir\":\"resources\"}");93+ WriteTestFile(ignoredManifest, "ignored");
86 ASSERT_EQ(symlink(ignoredManifest.c_str(), FileUtils::JoinPath(collection, "Link_manifest.json").c_str()), 0);94 ASSERT_EQ(symlink(ignoredManifest.c_str(), FileUtils::JoinPath(collection, "Link_manifest.json").c_str()), 0);
87 95 
88 std::vector<ManifestUnit> units;96 std::vector<ManifestUnit> units;
89 ASSERT_TRUE(CollectedManifestRepository(collection).Load(units));97 ASSERT_TRUE(CollectedManifestRepository(collection).Load(units));
90 ASSERT_EQ(units.size(), 1U);98 ASSERT_EQ(units.size(), 1U);
91- ASSERT_EQ(units[0].files.size(), 1U);99+ EXPECT_NE(units[0].json.find("Unit"), std::string::npos);
92- EXPECT_EQ(units[0].files[0].filePath, "resources/kernel.cpp");100+}
101+ 
102+TEST_F(ModuleTest, RepositoryLoadsManifestWithNoResourceFiles)
103+{
104+ const std::string collection = FileUtils::JoinPath(root_, "collection-empty-resource-path");
105+ const std::string unit = FileUtils::JoinPath(collection, "Unit");
106+ CreateManifest(unit, "Unit");
107+ std::string manifest = ReadTestFile(FileUtils::JoinPath(unit, "Unit_manifest.json"));
108+ const std::string resourcePath = "\"resource_path\":\"resources\"";
109+ manifest.replace(manifest.find(resourcePath), resourcePath.size(), "\"resource_path\":\"\"");
110+ WriteTestFile(FileUtils::JoinPath(unit, "Unit_manifest.json"), manifest);
111+ 
112+ std::vector<ManifestUnit> units;
113+ ASSERT_TRUE(CollectedManifestRepository(collection).Load(units));
114+ ASSERT_EQ(units.size(), 1U);
115+ EXPECT_TRUE(units[0].files.empty());
116+}
117+ 
118+TEST_F(ModuleTest, RepositoryRejectsAbsoluteResourcePath)
119+{
120+ const std::string collection = FileUtils::JoinPath(root_, "collection-absolute");
121+ const std::string unit = FileUtils::JoinPath(collection, "Unit");
122+ const std::string resources = FileUtils::JoinPath(unit, "resources");
123+ WriteTestFile(FileUtils::JoinPath(resources, "kernel.cpp"), "source");
124+ CreateManifest(unit, "Unit");
125+ std::string absoluteResourcePath = ReadTestFile(FileUtils::JoinPath(unit, "Unit_manifest.json"));
126+ const size_t value = absoluteResourcePath.find("resources");
127+ absoluteResourcePath.replace(value, std::string("resources").size(), resources);
128+ WriteTestFile(FileUtils::JoinPath(unit, "Unit_manifest.json"), absoluteResourcePath);
129+ 
130+ std::vector<ManifestUnit> units;
131+ EXPECT_FALSE(CollectedManifestRepository(collection).Load(units));
93}132}
94 133 
95TEST_F(ModuleTest, RepositoryRejectsNonRegularResource)134TEST_F(ModuleTest, RepositoryRejectsNonRegularResource)
@@ -108,10 +147,11 @@ TEST_F(ModuleTest, RepositoryEnforcesDirectoryDepthLimit)
108{147{
109 const std::string allowedCollection = FileUtils::JoinPath(root_, "collection-depth-allowed");148 const std::string allowedCollection = FileUtils::JoinPath(root_, "collection-depth-allowed");
110 std::string allowedDirectory = allowedCollection;149 std::string allowedDirectory = allowedCollection;
111- for (size_t depth = 0U; depth < TEST_MAX_DIRECTORY_DEPTH; ++depth) {150+ for (size_t depth = 0U; depth + 1U < TEST_MAX_DIRECTORY_DEPTH; ++depth) {
112 allowedDirectory = FileUtils::JoinPath(allowedDirectory, "level-" + std::to_string(depth));151 allowedDirectory = FileUtils::JoinPath(allowedDirectory, "level-" + std::to_string(depth));
113 }152 }
114- WriteTestFile(FileUtils::JoinPath(allowedDirectory, "Deep_manifest.json"), "{\"base_dir\":\".\"}");153+ WriteTestFile(FileUtils::JoinPath(allowedDirectory, "resources/kernel.cpp"), "source");
154+ CreateManifest(allowedDirectory, "Deep");
115 std::vector<ManifestUnit> units;155 std::vector<ManifestUnit> units;
116 EXPECT_TRUE(CollectedManifestRepository(allowedCollection).Load(units));156 EXPECT_TRUE(CollectedManifestRepository(allowedCollection).Load(units));
117 157 
@@ -41,6 +41,16 @@ TEST_F(ModuleTest, FileUtilsBuildsAndComparesPaths)
41 EXPECT_FALSE(absolute.empty());41 EXPECT_FALSE(absolute.empty());
42 EXPECT_EQ(absolute[0], '/');42 EXPECT_EQ(absolute[0], '/');
43 43 
44+ EXPECT_TRUE(FileUtils::IsSafeRelativePath("relative/path"));
45+ EXPECT_TRUE(FileUtils::IsSafeRelativePath("file.cpp"));
46+ EXPECT_FALSE(FileUtils::IsSafeRelativePath(""));
47+ EXPECT_FALSE(FileUtils::IsSafeRelativePath("/absolute/path"));
48+ EXPECT_FALSE(FileUtils::IsSafeRelativePath("relative\\path"));
49+ EXPECT_FALSE(FileUtils::IsSafeRelativePath("relative/../outside"));
50+ EXPECT_FALSE(FileUtils::IsSafeRelativePath("./relative"));
51+ EXPECT_FALSE(FileUtils::IsSafeRelativePath("relative//path"));
52+ EXPECT_FALSE(FileUtils::IsSafeRelativePath(std::string("embedded\0nul", 12U)));
53+ 
44 EXPECT_TRUE(FileUtils::IsPathWithin("/root/child", "/root"));54 EXPECT_TRUE(FileUtils::IsPathWithin("/root/child", "/root"));
45 EXPECT_TRUE(FileUtils::IsPathWithin("/root", "/root"));55 EXPECT_TRUE(FileUtils::IsPathWithin("/root", "/root"));
46 EXPECT_TRUE(FileUtils::IsPathWithin("/root/nested/../child", "/root"));56 EXPECT_TRUE(FileUtils::IsPathWithin("/root/nested/../child", "/root"));
@@ -0,0 +1,318 @@
1+/**
2+ * Copyright (c) 2025 Huawei Technologies Co., Ltd.
3+ * This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4+ * CANN Open Software License Agreement Version 2.0 (the "License").
5+ * Please refer to the License for details. You may not use this file except in compliance with the License.
6+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7+ * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8+ * See LICENSE in the root of the software repository for the full text of the LICENSE.
9+ */
10+ 
11+#include <gtest/gtest.h>
12+ 
13+#include <functional>
14+#include <string>
15+ 
16+#include <unistd.h>
17+ 
18+#include "file_utils.h"
19+#include "nlohmann/json.hpp"
20+#include "resource_manifest_validator.h"
21+#include "test_support.h"
22+ 
23+namespace ascendc {
24+namespace manifest_generator {
25+namespace {
26+ 
27+using Json = nlohmann::json;
28+using ManifestMutation = std::function<void(Json&)>;
29+using asc_compile_exporter_test::ModuleTest;
30+using asc_compile_exporter_test::WriteTestFile;
31+ 
32+Json MakeValidManifest()
33+{
34+ Json constant = {
35+ {"name", "weight"},
36+ {"parameter_index", Json::number_unsigned_t(0U)},
37+ {"byte_size", Json::number_unsigned_t(4U)},
38+ {"file", "${resource}/resources/weight.bin"},
39+ {"template", "weight_template"},
40+ };
41+ Json command = {
42+ {"type", "compile"},
43+ {"stage", Json::number_unsigned_t(0U)},
44+ {"cmd", Json::array(
45+ {"${options:common_compile}", "${source_file_path}", "${resource}/resources/weight.bin",
46+ "${env:ASCEND_HOME_PATH}/include", "${source_file_path}", "${output}/kernel.o"})},
47+ };
48+ Json object = {
49+ {"object_name", "kernel.o"},
50+ {"object_type", "basic"},
51+ {"commands", Json::array({command})},
52+ {"outputs", Json::array({"${output}/kernel.o"})},
53+ };
54+ Json kernel = {
55+ {"kernel_name", "test_kernel"},
56+ {"constant_infos", Json::array({constant})},
57+ {"objects", Json::array({object})},
58+ {"link_options", Json::array()},
59+ };
60+ return {
61+ {"resource_id", "test-resource"},
62+ {"soc_version", "Ascend910B"},
63+ {"source_file", "kernel.cpp"},
64+ {"resource_path", "resources"},
65+ {"options", {{"common_compile", Json::array({"-O2", "-I${resource}/resources/include"})}}},
66+ {"kernels", Json::array({kernel})},
67+ };
68+}
69+ 
70+Json& Kernel(Json& manifest) { return manifest["kernels"][0U]; }
71+ 
72+Json& Object(Json& manifest) { return Kernel(manifest)["objects"][0U]; }
73+ 
74+Json& Command(Json& manifest) { return Object(manifest)["commands"][0U]; }
75+ 
76+Json& Constant(Json& manifest) { return Kernel(manifest)["constant_infos"][0U]; }
77+ 
78+class ResourceManifestValidatorTest : public ModuleTest {
79+protected:
80+ void SetUp() override
81+ {
82+ ModuleTest::SetUp();
83+ manifestPath_ = FileUtils::JoinPath(root_, "test_manifest.json");
84+ WriteTestFile(FileUtils::JoinPath(root_, "resources/weight.bin"), "weight");
85+ ASSERT_TRUE(FileUtils::CreateDirectories(FileUtils::JoinPath(root_, "resources/include")));
86+ }
87+ 
88+ bool Validate(const Json& manifest) const { return ValidateResourceManifest(manifest, manifestPath_); }
89+ 
90+ void ExpectInvalid(const char* scenario, const ManifestMutation& mutate) const
91+ {
92+ SCOPED_TRACE(scenario);
93+ Json manifest = MakeValidManifest();
94+ mutate(manifest);
95+ EXPECT_FALSE(Validate(manifest));
96+ }
97+ 
98+ std::string manifestPath_;
99+};
100+ 
101+TEST_F(ResourceManifestValidatorTest, AcceptsCompleteAndMinimalValidManifests)
102+{
103+ EXPECT_TRUE(Validate(MakeValidManifest()));
104+ 
105+ Json skManifest = MakeValidManifest();
106+ Object(skManifest)["object_type"] = "sk";
107+ EXPECT_TRUE(Validate(skManifest));
108+ 
109+ Json embeddedOutput = MakeValidManifest();
110+ Command(embeddedOutput)["cmd"].back() = "-o${output}/kernel.o";
111+ EXPECT_TRUE(Validate(embeddedOutput));
112+ 
113+ Json minimal = MakeValidManifest();
114+ minimal.erase("source_file");
115+ minimal["resource_path"] = "";
116+ minimal.erase("options");
117+ Kernel(minimal).erase("constant_infos");
118+ Kernel(minimal).erase("link_options");
119+ Command(minimal)["cmd"] = Json::array({"kernel.cpp", "${env:ASCEND_HOME_PATH}/include", "${output}/kernel.o"});
120+ EXPECT_TRUE(Validate(minimal));
121+ 
122+ Json noCommonCompile = MakeValidManifest();
123+ noCommonCompile["options"] = Json::object();
124+ Command(noCommonCompile)["cmd"].erase(Command(noCommonCompile)["cmd"].begin());
125+ EXPECT_TRUE(Validate(noCommonCompile));
126+ 
127+ Json emptyCommonCompile = noCommonCompile;
128+ emptyCommonCompile["options"]["common_compile"] = Json::array();
129+ EXPECT_TRUE(Validate(emptyCommonCompile));
130+ 
131+ Json namedOptions = MakeValidManifest();
132+ namedOptions["options"]["custom_compile"] =
133+ Json::array({"${env:CUSTOM_INCLUDE}/include", "${source_file_path}", "-I${resource}/resources/include"});
134+ Command(namedOptions)["cmd"][0U] = "${options:custom_compile}";
135+ Command(namedOptions)["cmd"].insert(Command(namedOptions)["cmd"].begin() + 1U, "${options:custom_compile}");
136+ EXPECT_TRUE(Validate(namedOptions));
137+}
138+ 
139+TEST_F(ResourceManifestValidatorTest, RejectsInvalidTopLevelFields)
140+{
141+ ExpectInvalid("manifest is not an object", [](Json& manifest) { manifest = Json::array(); });
142+ ExpectInvalid("missing resource_id", [](Json& manifest) { manifest.erase("resource_id"); });
143+ ExpectInvalid("resource_id has wrong type", [](Json& manifest) { manifest["resource_id"] = 1U; });
144+ ExpectInvalid("resource_id is empty", [](Json& manifest) { manifest["resource_id"] = ""; });
145+ ExpectInvalid("missing soc_version", [](Json& manifest) { manifest.erase("soc_version"); });
146+ ExpectInvalid("soc_version has wrong type", [](Json& manifest) { manifest["soc_version"] = 1U; });
147+ ExpectInvalid("soc_version is empty", [](Json& manifest) { manifest["soc_version"] = ""; });
148+ ExpectInvalid("missing resource_path", [](Json& manifest) { manifest.erase("resource_path"); });
149+ ExpectInvalid("missing kernels", [](Json& manifest) { manifest.erase("kernels"); });
150+ ExpectInvalid("kernels has wrong type", [](Json& manifest) { manifest["kernels"] = Json::object(); });
151+ ExpectInvalid("kernels is empty", [](Json& manifest) { manifest["kernels"] = Json::array(); });
152+}
153+ 
154+TEST_F(ResourceManifestValidatorTest, ValidatesManifestPathsAndCommonOptions)
155+{
156+ ExpectInvalid("source_file has wrong type", [](Json& manifest) { manifest["source_file"] = 1U; });
157+ ExpectInvalid(
158+ "source_file contains a directory", [](Json& manifest) { manifest["source_file"] = "src/kernel.cpp"; });
159+ ExpectInvalid("source_file traverses", [](Json& manifest) { manifest["source_file"] = "../kernel.cpp"; });
160+ ExpectInvalid("resource_path has wrong type", [](Json& manifest) { manifest["resource_path"] = 1U; });
161+ ExpectInvalid("resource_path is absolute", [](Json& manifest) { manifest["resource_path"] = "/resources"; });
162+ ExpectInvalid("resource_path traverses", [](Json& manifest) { manifest["resource_path"] = "../resources"; });
163+ ExpectInvalid("options has wrong type", [](Json& manifest) { manifest["options"] = Json::array(); });
164+ ExpectInvalid(
165+ "common_compile has wrong type", [](Json& manifest) { manifest["options"]["common_compile"] = "-O2"; });
166+ ExpectInvalid("common_compile contains non-string", [](Json& manifest) {
167+ manifest["options"]["common_compile"] = Json::array({1U});
168+ });
169+ ExpectInvalid("common_compile contains common marker", [](Json& manifest) {
170+ manifest["options"]["common_compile"] = Json::array({"${options:common_compile}"});
171+ });
172+}
173+ 
174+TEST_F(ResourceManifestValidatorTest, ValidatesMarkersAndReferencedPaths)
175+{
176+ ExpectInvalid("command contains embedded NUL", [](Json& manifest) {
177+ Command(manifest)["cmd"][1U] = std::string("prefix\0suffix", 13U);
178+ });
179+ ExpectInvalid(
180+ "command marker is unterminated", [](Json& manifest) { Command(manifest)["cmd"][1U] = "${source_dir"; });
181+ ExpectInvalid(
182+ "command marker is unsupported", [](Json& manifest) { Command(manifest)["cmd"][1U] = "${unknown}/x"; });
183+ ExpectInvalid("resource marker occurs twice", [](Json& manifest) {
184+ Command(manifest)["cmd"][1U] = "${resource}/resources/${resource}/x";
185+ });
186+ ExpectInvalid(
187+ "resource marker has no relative path", [](Json& manifest) { Command(manifest)["cmd"][1U] = "${resource}"; });
188+ ExpectInvalid("resource path traverses", [](Json& manifest) {
189+ Command(manifest)["cmd"][1U] = "${resource}/resources/../outside";
190+ });
191+ ExpectInvalid("resource path is outside resource_path", [](Json& manifest) {
192+ Command(manifest)["cmd"][1U] = "${resource}/outside/file.cpp";
193+ });
194+ ExpectInvalid("resource marker with empty resource_path", [](Json& manifest) { manifest["resource_path"] = ""; });
195+ ExpectInvalid("resource target is missing", [](Json& manifest) {
196+ Command(manifest)["cmd"][1U] = "${resource}/resources/missing.bin";
197+ });
198+ const std::string symlinkTarget = FileUtils::JoinPath(root_, "resource-target.bin");
199+ const std::string symlinkPath = FileUtils::JoinPath(root_, "resources/resource-link.bin");
200+ WriteTestFile(symlinkTarget, "target");
201+ ASSERT_EQ(symlink(symlinkTarget.c_str(), symlinkPath.c_str()), 0);
202+ ExpectInvalid("resource target is a symlink", [&symlinkPath](Json& manifest) {
203+ Command(manifest)["cmd"][1U] = "${resource}/resources/" + FileUtils::FileName(symlinkPath);
204+ });
205+ ExpectInvalid("source_file_path requires source_file", [](Json& manifest) { manifest.erase("source_file"); });
206+ ExpectInvalid("environment marker name is invalid", [](Json& manifest) {
207+ Command(manifest)["cmd"][3U] = "${env:INVALID-NAME}/include";
208+ });
209+ ExpectInvalid(
210+ "environment marker name is empty", [](Json& manifest) { Command(manifest)["cmd"][3U] = "${env:}/include"; });
211+ ExpectInvalid("output has no relative path", [](Json& manifest) { Command(manifest)["cmd"].back() = "${output}"; });
212+ ExpectInvalid("output contains another marker", [](Json& manifest) {
213+ Command(manifest)["cmd"].back() = "${output}/${source_dir}/kernel.o";
214+ });
215+ ExpectInvalid(
216+ "output path traverses", [](Json& manifest) { Command(manifest)["cmd"].back() = "${output}/../kernel.o"; });
217+ ExpectInvalid("output contains a directory", [](Json& manifest) {
218+ Command(manifest)["cmd"].back() = "${output}/objects/kernel.o";
219+ });
220+}
221+ 
222+TEST_F(ResourceManifestValidatorTest, ValidatesConstants)
223+{
224+ ExpectInvalid(
225+ "constant is not an object", [](Json& manifest) { Kernel(manifest)["constant_infos"][0U] = "weight"; });
226+ ExpectInvalid("constant has extra field", [](Json& manifest) { Constant(manifest)["extra"] = true; });
227+ ExpectInvalid("constant name is missing", [](Json& manifest) {
228+ Constant(manifest).erase("name");
229+ Constant(manifest)["extra"] = true;
230+ });
231+ ExpectInvalid("constant name has wrong type", [](Json& manifest) { Constant(manifest)["name"] = 1U; });
232+ ExpectInvalid("constant name is empty", [](Json& manifest) { Constant(manifest)["name"] = ""; });
233+ ExpectInvalid("parameter_index has wrong type", [](Json& manifest) { Constant(manifest)["parameter_index"] = -1; });
234+ ExpectInvalid("byte_size has wrong type", [](Json& manifest) { Constant(manifest)["byte_size"] = "4"; });
235+ ExpectInvalid(
236+ "byte_size is zero", [](Json& manifest) { Constant(manifest)["byte_size"] = Json::number_unsigned_t(0U); });
237+ ExpectInvalid("constant file has wrong type", [](Json& manifest) { Constant(manifest)["file"] = 1U; });
238+ ExpectInvalid("constant template has wrong type", [](Json& manifest) { Constant(manifest)["template"] = 1U; });
239+ ExpectInvalid("constant template is empty", [](Json& manifest) { Constant(manifest)["template"] = ""; });
240+ ExpectInvalid(
241+ "constant file lacks resource prefix", [](Json& manifest) { Constant(manifest)["file"] = "resources/x"; });
242+ ExpectInvalid("constant file is outside resource_path", [](Json& manifest) {
243+ Constant(manifest)["file"] = "${resource}/outside/weight.bin";
244+ });
245+ ExpectInvalid("constant name is duplicated", [](Json& manifest) {
246+ Kernel(manifest)["constant_infos"].push_back(Constant(manifest));
247+ });
248+}
249+ 
250+TEST_F(ResourceManifestValidatorTest, ValidatesKernelStructure)
251+{
252+ ExpectInvalid("kernel is not an object", [](Json& manifest) { manifest["kernels"][0U] = "kernel"; });
253+ ExpectInvalid("kernel name is missing", [](Json& manifest) { Kernel(manifest).erase("kernel_name"); });
254+ ExpectInvalid("kernel name has wrong type", [](Json& manifest) { Kernel(manifest)["kernel_name"] = 1U; });
255+ ExpectInvalid("kernel name is empty", [](Json& manifest) { Kernel(manifest)["kernel_name"] = ""; });
256+ ExpectInvalid(
257+ "constant_infos has wrong type", [](Json& manifest) { Kernel(manifest)["constant_infos"] = Json::object(); });
258+ ExpectInvalid("objects is missing", [](Json& manifest) { Kernel(manifest).erase("objects"); });
259+ ExpectInvalid("objects has wrong type", [](Json& manifest) { Kernel(manifest)["objects"] = Json::object(); });
260+ ExpectInvalid("objects is empty", [](Json& manifest) { Kernel(manifest)["objects"] = Json::array(); });
261+ ExpectInvalid(
262+ "link_options has wrong type", [](Json& manifest) { Kernel(manifest)["link_options"] = Json::object(); });
263+ ExpectInvalid("kernel name is duplicated", [](Json& manifest) { manifest["kernels"].push_back(Kernel(manifest)); });
264+}
265+ 
266+TEST_F(ResourceManifestValidatorTest, ValidatesObjectStructureAndOutputs)
267+{
268+ ExpectInvalid("object is not an object", [](Json& manifest) { Kernel(manifest)["objects"][0U] = "object"; });
269+ ExpectInvalid("object name is missing", [](Json& manifest) { Object(manifest).erase("object_name"); });
270+ ExpectInvalid("object name has wrong type", [](Json& manifest) { Object(manifest)["object_name"] = 1U; });
271+ ExpectInvalid("object name is empty", [](Json& manifest) { Object(manifest)["object_name"] = ""; });
272+ ExpectInvalid("object type is missing", [](Json& manifest) { Object(manifest).erase("object_type"); });
273+ ExpectInvalid("object type has wrong type", [](Json& manifest) { Object(manifest)["object_type"] = 1U; });
274+ ExpectInvalid("commands is missing", [](Json& manifest) { Object(manifest).erase("commands"); });
275+ ExpectInvalid("commands has wrong type", [](Json& manifest) { Object(manifest)["commands"] = Json::object(); });
276+ ExpectInvalid("commands is empty", [](Json& manifest) { Object(manifest)["commands"] = Json::array(); });
277+ ExpectInvalid("outputs is missing", [](Json& manifest) { Object(manifest).erase("outputs"); });
278+ ExpectInvalid("outputs has wrong type", [](Json& manifest) { Object(manifest)["outputs"] = Json::object(); });
279+ ExpectInvalid("outputs is empty", [](Json& manifest) { Object(manifest)["outputs"] = Json::array(); });
280+ ExpectInvalid(
281+ "object name contains a path", [](Json& manifest) { Object(manifest)["object_name"] = "obj/kernel.o"; });
282+ ExpectInvalid(
283+ "object name is duplicated", [](Json& manifest) { Kernel(manifest)["objects"].push_back(Object(manifest)); });
284+ ExpectInvalid("object output is not a string", [](Json& manifest) { Object(manifest)["outputs"][0U] = 1U; });
285+ ExpectInvalid(
286+ "object output has invalid syntax", [](Json& manifest) { Object(manifest)["outputs"][0U] = "kernel.o"; });
287+ ExpectInvalid("object output is not referenced", [](Json& manifest) {
288+ Object(manifest)["outputs"][0U] = "${output}/other.o";
289+ });
290+}
291+ 
292+TEST_F(ResourceManifestValidatorTest, ValidatesCommands)
293+{
294+ ExpectInvalid("command is not an object", [](Json& manifest) { Object(manifest)["commands"][0U] = "compile"; });
295+ ExpectInvalid("command type is missing", [](Json& manifest) { Command(manifest).erase("type"); });
296+ ExpectInvalid("command type has wrong type", [](Json& manifest) { Command(manifest)["type"] = 1U; });
297+ ExpectInvalid("command stage is missing", [](Json& manifest) { Command(manifest).erase("stage"); });
298+ ExpectInvalid("command stage has wrong type", [](Json& manifest) { Command(manifest)["stage"] = -1; });
299+ ExpectInvalid("command cmd is missing", [](Json& manifest) { Command(manifest).erase("cmd"); });
300+ ExpectInvalid("command cmd has wrong type", [](Json& manifest) { Command(manifest)["cmd"] = Json::object(); });
301+ ExpectInvalid("command option is not a string", [](Json& manifest) { Command(manifest)["cmd"][0U] = 1U; });
302+ ExpectInvalid("common marker references empty options", [](Json& manifest) {
303+ manifest["options"]["common_compile"] = Json::array();
304+ });
305+ ExpectInvalid("options marker references missing field", [](Json& manifest) {
306+ Command(manifest)["cmd"][0U] = "${options:missing_compile}";
307+ });
308+ ExpectInvalid("options marker has invalid name", [](Json& manifest) {
309+ Command(manifest)["cmd"][0U] = "${options:invalid-name}";
310+ });
311+ ExpectInvalid("options marker is embedded", [](Json& manifest) {
312+ Command(manifest)["cmd"][0U] = "prefix${options:common_compile}";
313+ });
314+}
315+ 
316+} // namespace
317+} // namespace manifest_generator
318+} // namespace ascendc
@@ -118,7 +118,10 @@ protected:
118 {118 {
119 WriteTestFile(119 WriteTestFile(
120 ascendc::FileUtils::JoinPath(unit, name + "_manifest.json"),120 ascendc::FileUtils::JoinPath(unit, name + "_manifest.json"),
121- "{\"base_dir\":\"resources\",\"name\":\"" + name + "\"}");121+ "{\"schema_version\":\"1.0\",\"resource_id\":\"" + name +
122+ "\",\"soc_version\":\"test\",\"resource_path\":\"resources\",\"kernels\":[{\"kernel_name\":\"" + name +
123+ "\",\"objects\":[{\"object_name\":\"kernel.o\",\"object_type\":\"basic\",\"commands\":[{\"type\":"
124+ "\"compile\",\"stage\":0,\"cmd\":[\"${output}/kernel.o\"]}],\"outputs\":[\"${output}/kernel.o\"]}]}]}");
122 }125 }
123 126 
124 std::string root_;127 std::string root_;
@@ -38,6 +38,7 @@ add_executable(asc_compile_exporter
38 bundle_source_generator.cpp38 bundle_source_generator.cpp
39 collected_manifest_repository.cpp39 collected_manifest_repository.cpp
40 manifest_bundle_compiler.cpp40 manifest_bundle_compiler.cpp
41+ resource_manifest_validator.cpp
41 ../common/file_utils.cpp42 ../common/file_utils.cpp
42 ../common/process_runner.cpp43 ../common/process_runner.cpp
43)44)
@@ -24,21 +24,36 @@
24#include "manifest_bundle_compiler.h"24#include "manifest_bundle_compiler.h"
25 25 
26namespace {26namespace {
27- 27+void PrintUsage(std::ostream& output, const char* program)
28-void PrintUsage(const char* program)
29{28{
30- std::cerr << "Usage:\n " << program29+ output << "Usage:\n " << program
31- << " --input-dir <path> --output <path> --make <path> --cxx <path>"30+ << " --input-dir <path> --output <path> --make <path> --cxx <path>"
32- " [--save-temp-files] [--jobs <N>]\n\n"31+ " [--save-temp-files] [--jobs <N>]\n "
33- "Required arguments:\n"32+ << program
34- " --input-dir <path> Directory recursively searched for *_manifest.json files.\n"33+ << " -h | --help\n\n"
35- " --output <path> Output path ending in .so or .o, including its parent directory.\n"34+ "Required arguments:\n"
36- " --make <path> Path to the make executable used to build generated sources.\n"35+ " --input-dir <path> Directory recursively searched for *_manifest.json files.\n"
37- " --cxx <path> Path to the C++ compiler used by the generated Makefile.\n"36+ " --output <path> Output path ending in .so or .o, including its parent directory.\n"
38- "\nOptional arguments:\n"37+ " --make <path> Path to the make executable used to build generated sources.\n"
39- " --save-temp-files Preserve the private build directory under TMPDIR or /tmp.\n"38+ " --cxx <path> Path to the C++ compiler used by the generated Makefile.\n"
40- " --jobs <N> Positive make job count, capped at the detected CPU count;\n"39+ "\nOptional arguments:\n"
41- " defaults to half the CPU count, with a minimum of one.\n";40+ " --save-temp-files Preserve the private build directory under TMPDIR or /tmp.\n"
41+ " --jobs <N> Positive make job count, capped at the detected CPU count;\n"
42+ " defaults to half the CPU count, with a minimum of one.\n"
43+ " -h, --help Print this usage and exit.\n";
44+}
45+ 
46+bool IsHelpRequested(int argc, char** argv)
47+{
48+ for (int index = 1; index < argc; ++index) {
49+ if (argv[index] != nullptr) {
50+ const std::string argument = argv[index];
51+ if (argument == "-h" || argument == "--help") {
52+ return true;
53+ }
54+ }
55+ }
56+ return false;
42}57}
43 58 
44bool ReportArgumentError(const char* reason, const std::string& argument)59bool ReportArgumentError(const char* reason, const std::string& argument)
@@ -228,28 +243,31 @@ bool ParseArguments(int argc, char** argv, ascendc::manifest_generator::BuildCol
228 return true;243 return true;
229}244}
230 245 
231-int Run(int argc, char** argv)246+int RunWithExceptionHandling(int argc, char** argv)
232{247{
233- ascendc::manifest_generator::BuildCollectedBundleRequest request;248+ try {
234- if (!ParseArguments(argc, argv, request)) {249+ if (IsHelpRequested(argc, argv)) {
235- PrintUsage(argv[0]);250+ PrintUsage(std::cout, argv[0]);
251+ return 0;
252+ }
253+ ascendc::manifest_generator::BuildCollectedBundleRequest request;
254+ if (!ParseArguments(argc, argv, request)) {
255+ PrintUsage(std::cerr, argv[0]);
256+ return 1;
257+ }
258+ ascendc::manifest_generator::ManifestBundleCompiler compiler(std::move(request));
259+ if (!compiler.Compile()) {
260+ std::cerr << "Compilation failed. Please check plog for details." << '\n';
261+ return 1;
262+ }
263+ return 0;
264+ } catch (const std::exception& error) {
265+ ASCENDLOGE("asc_compile_exporter failed unexpectedly: %s", error.what());
266+ std::cerr << "Compilation failed. Please check plog for details." << '\n';
236 return 1;267 return 1;
237 }268 }
238- ascendc::manifest_generator::ManifestBundleCompiler compiler(std::move(request));
239- if (!compiler.Compile()) {
240- return 1;
241- }
242- return 0;
243}269}
244 270 
245} // namespace271} // namespace
246 272 
247-int main(int argc, char** argv)273+int main(int argc, char** argv) { return RunWithExceptionHandling(argc, argv); }
248-{
249- try {
250- return Run(argc, argv);
251- } catch (const std::exception& error) {
252- ASCENDLOGE("asc_compile_exporter failed unexpectedly: %s", error.what());
253- return 1;
254- }
255-}
@@ -21,6 +21,7 @@
21#include "ascendc_tool_log.h"21#include "ascendc_tool_log.h"
22#include "file_utils.h"22#include "file_utils.h"
23#include "nlohmann/json.hpp"23#include "nlohmann/json.hpp"
24+#include "resource_manifest_validator.h"
24 25 
25using Json = nlohmann::json;26using Json = nlohmann::json;
26 27 
@@ -68,7 +69,7 @@ private:
68 size_t fileCount_{0U};69 size_t fileCount_{0U};
69};70};
70 71 
71-bool ReadBaseDir(const std::string& text, const std::string& manifestPath, std::string& baseDir)72+bool ValidateAndExtractResourcePath(const std::string& text, const std::string& manifestPath, std::string& resourcePath)
72{73{
73 const Json manifest = Json::parse(text, nullptr, false);74 const Json manifest = Json::parse(text, nullptr, false);
74 if (manifest.is_discarded()) {75 if (manifest.is_discarded()) {
@@ -76,12 +77,16 @@ bool ReadBaseDir(const std::string& text, const std::string& manifestPath, std::
76 return false;77 return false;
77 }78 }
78 79 
79- const Json::const_iterator baseDirValue = manifest.find("base_dir");80+ if (!ValidateResourceManifest(manifest, manifestPath)) {
80- if (baseDirValue == manifest.end() || !baseDirValue->is_string()) {
81- ASCENDLOGE("Manifest base_dir is unavailable: %s", manifestPath.c_str());
82 return false;81 return false;
83 }82 }
84- baseDir = baseDirValue->get<std::string>();83+ 
84+ const Json::const_iterator resourcePathValue = manifest.find("resource_path");
85+ if (resourcePathValue == manifest.end() || !resourcePathValue->is_string()) {
86+ ASCENDLOGE("Manifest resource_path is unavailable: %s", manifestPath.c_str());
87+ return false;
88+ }
89+ resourcePath = resourcePathValue->get<std::string>();
85 return true;90 return true;
86}91}
87 92 
@@ -190,7 +195,7 @@ bool LoadResourceFiles(
190 fs::recursive_directory_iterator iterator(fs::path(directory), fs::directory_options::none, error);195 fs::recursive_directory_iterator iterator(fs::path(directory), fs::directory_options::none, error);
191 const fs::recursive_directory_iterator end;196 const fs::recursive_directory_iterator end;
192 if (error) {197 if (error) {
193- ASCENDLOGE("Failed to enumerate manifest base_dir %s: %s", directory.c_str(), error.message().c_str());198+ ASCENDLOGE("Failed to enumerate manifest resource_path %s: %s", directory.c_str(), error.message().c_str());
194 return false;199 return false;
195 }200 }
196 while (iterator != end) {201 while (iterator != end) {
@@ -201,7 +206,7 @@ bool LoadResourceFiles(
201 return false;206 return false;
202 }207 }
203 if (fs::is_symlink(status)) {208 if (fs::is_symlink(status)) {
204- ASCENDLOGE("Manifest base_dir contains a symlink: %s", source.c_str());209+ ASCENDLOGE("Manifest resource_path contains a symlink: %s", source.c_str());
205 return false;210 return false;
206 }211 }
207 if (fs::is_directory(status)) {212 if (fs::is_directory(status)) {
@@ -211,7 +216,7 @@ bool LoadResourceFiles(
211 return false;216 return false;
212 }217 }
213 } else if (!fs::is_regular_file(status)) {218 } else if (!fs::is_regular_file(status)) {
214- ASCENDLOGE("Manifest base_dir contains a non-regular file: %s", source.c_str());219+ ASCENDLOGE("Manifest resource_path contains a non-regular file: %s", source.c_str());
215 return false;220 return false;
216 } else if (!LoadResourceFile(source, manifestRoot, manifestPath, budget, unit)) {221 } else if (!LoadResourceFile(source, manifestRoot, manifestPath, budget, unit)) {
217 return false;222 return false;
@@ -235,18 +240,23 @@ bool LoadUnit(const std::string& manifestPath, ResourceBudget& budget, ManifestU
235 return false;240 return false;
236 }241 }
237 unit.json.assign(manifestBytes.begin(), manifestBytes.end());242 unit.json.assign(manifestBytes.begin(), manifestBytes.end());
238- std::string baseDir;243+ std::string resourcePath;
239- if (!ReadBaseDir(unit.json, manifestPath, baseDir)) {244+ if (!ValidateAndExtractResourcePath(unit.json, manifestPath, resourcePath)) {
240 return false;245 return false;
241 }246 }
247+ if (resourcePath.empty()) {
248+ ASCENDLOGD("Loaded manifest %s with no resource files", manifestPath.c_str());
249+ return true;
250+ }
242 251 
243 const std::string manifestRoot = FileUtils::ParentPath(manifestPath);252 const std::string manifestRoot = FileUtils::ParentPath(manifestPath);
244- const fs::path baseDirPath(baseDir);253+ const fs::path resourcePathValue(resourcePath);
245- const std::string resourceInput = baseDirPath.is_absolute() ? baseDir : FileUtils::JoinPath(manifestRoot, baseDir);254+ const std::string resourceInput =
255+ resourcePathValue.is_absolute() ? resourcePath : FileUtils::JoinPath(manifestRoot, resourcePath);
246 std::string resourceRoot;256 std::string resourceRoot;
247 if (!FileUtils::ResolveSubdirectory(resourceInput, manifestRoot, resourceRoot)) {257 if (!FileUtils::ResolveSubdirectory(resourceInput, manifestRoot, resourceRoot)) {
248 ASCENDLOGE(258 ASCENDLOGE(
249- "Manifest base_dir must be a directory below the manifest root and must not be a symlink: %s",259+ "Manifest resource_path must be a directory below the manifest root and must not be a symlink: %s",
250 resourceInput.c_str());260 resourceInput.c_str());
251 return false;261 return false;
252 }262 }
@@ -0,0 +1,573 @@
1+/**
2+ * Copyright (c) 2025 Huawei Technologies Co., Ltd.
3+ * This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4+ * CANN Open Software License Agreement Version 2.0 (the "License").
5+ * Please refer to the License for details. You may not use this file except in compliance with the License.
6+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7+ * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8+ * See LICENSE in the root of the software repository for the full text of the License.
9+ */
10+ 
11+#include "resource_manifest_validator.h"
12+ 
13+#include <map>
14+#include <set>
15+ 
16+#include <boost/filesystem.hpp>
17+#include <boost/system/error_code.hpp>
18+ 
19+#include "ascendc_tool_log.h"
20+#include "file_utils.h"
21+#include "nlohmann/json.hpp"
22+ 
23+namespace ascendc {
24+namespace manifest_generator {
25+namespace {
26+ 
27+using Json = nlohmann::json;
28+ 
29+constexpr const char* RESOURCE_ROOT_MARKER = "${resource}";
30+constexpr const char* OUTPUT_ROOT_MARKER = "${output}";
31+constexpr const char* SOURCE_FILE_PATH_MARKER = "${source_file_path}";
32+constexpr const char* ENVIRONMENT_MARKER_PREFIX = "${env:";
33+constexpr const char* OPTIONS_MARKER_PREFIX = "${options:";
34+ 
35+enum class MarkerRequirement { None, SourceFile };
36+ 
37+const std::map<std::string, MarkerRequirement> KNOWN_MARKERS = {
38+ {RESOURCE_ROOT_MARKER, MarkerRequirement::None},
39+ {SOURCE_FILE_PATH_MARKER, MarkerRequirement::SourceFile},
40+};
41+ 
42+bool CheckAndLog(bool condition, int lineNumber, const std::string& message) noexcept
43+{
44+ if (condition) {
45+ return true;
46+ }
47+ ASCENDLOGE("Manifest validation failed: line=%d reason=%s", lineNumber, message.c_str());
48+ return false;
49+}
50+ 
51+} // namespace
52+ 
53+ResourceManifestValidator::ResourceManifestValidator(const Json& manifest, const std::string& manifestPath)
54+ : manifest_(manifest),
55+ manifestPath_(manifestPath),
56+ manifestRoot_(FileUtils::ParentPath(manifestPath)),
57+ options_(nullptr),
58+ hasSourceFile_(false)
59+{}
60+ 
61+bool ResourceManifestValidator::IsIdentifier(const std::string& value)
62+{
63+ if (value.empty() ||
64+ !((value[0] >= 'A' && value[0] <= 'Z') || (value[0] >= 'a' && value[0] <= 'z') || value[0] == '_')) {
65+ return false;
66+ }
67+ for (size_t index = 1U; index < value.size(); ++index) {
68+ const char character = value[index];
69+ if (!((character >= 'A' && character <= 'Z') || (character >= 'a' && character <= 'z') ||
70+ (character >= '0' && character <= '9') || character == '_')) {
71+ return false;
72+ }
73+ }
74+ return true;
75+}
76+ 
77+bool ResourceManifestValidator::IsNamedMarker(const std::string& marker, const std::string& prefix)
78+{
79+ return marker.size() >= prefix.size() + 1U && marker.compare(0U, prefix.size(), prefix) == 0 &&
80+ marker.back() == '}';
81+}
82+ 
83+std::string ResourceManifestValidator::ExtractNamedMarkerName(const std::string& marker, const std::string& prefix)
84+{
85+ return marker.substr(prefix.size(), marker.size() - prefix.size() - 1U);
86+}
87+ 
88+bool ResourceManifestValidator::ValidateManifestBaseFields()
89+{
90+ return CheckAndLog(manifest_.is_object(), __LINE__, "invalid manifest: expected an object") &&
91+ CheckAndLog(manifest_.contains("resource_id"), __LINE__, "missing required field: resource_id") &&
92+ CheckAndLog(manifest_.at("resource_id").is_string(), __LINE__, "invalid resource_id: expected a string") &&
93+ CheckAndLog(
94+ !manifest_.at("resource_id").get_ref<const std::string&>().empty(), __LINE__,
95+ "invalid resource_id: expected a nonempty string") &&
96+ CheckAndLog(manifest_.contains("soc_version"), __LINE__, "missing required field: soc_version") &&
97+ CheckAndLog(manifest_.at("soc_version").is_string(), __LINE__, "invalid soc_version: expected a string") &&
98+ CheckAndLog(
99+ !manifest_.at("soc_version").get_ref<const std::string&>().empty(), __LINE__,
100+ "invalid soc_version: expected a nonempty string") &&
101+ CheckAndLog(manifest_.contains("resource_path"), __LINE__, "missing required field: resource_path") &&
102+ CheckAndLog(
103+ manifest_.at("resource_path").is_string(), __LINE__, "invalid resource_path: expected a string") &&
104+ CheckAndLog(manifest_.contains("kernels"), __LINE__, "missing required field: kernels") &&
105+ CheckAndLog(manifest_.at("kernels").is_array(), __LINE__, "invalid kernels: expected an array") &&
106+ CheckAndLog(!manifest_.at("kernels").empty(), __LINE__, "invalid kernels: expected a nonempty array");
107+}
108+ 
109+bool ResourceManifestValidator::ValidateManifestFields()
110+{
111+ std::string sourceFile;
112+ if (manifest_.contains("source_file")) {
113+ if (!CheckAndLog(manifest_.at("source_file").is_string(), __LINE__, "invalid source_file: expected a string")) {
114+ return false;
115+ }
116+ sourceFile = manifest_.at("source_file").get_ref<const std::string&>();
117+ if (!CheckAndLog(
118+ FileUtils::IsSafeRelativePath(sourceFile) && FileUtils::FileName(sourceFile) == sourceFile, __LINE__,
119+ "invalid source_file: expected a plain file name")) {
120+ return false;
121+ }
122+ hasSourceFile_ = true;
123+ }
124+ if (!resourcePath_.empty() &&
125+ !CheckAndLog(
126+ FileUtils::IsSafeRelativePath(resourcePath_), __LINE__,
127+ "invalid resource_path: expected a safe relative POSIX path, value=" + resourcePath_)) {
128+ return false;
129+ }
130+ if (manifest_.contains("options")) {
131+ if (!CheckAndLog(manifest_.at("options").is_object(), __LINE__, "invalid options: expected an object")) {
132+ return false;
133+ }
134+ options_ = &manifest_.at("options");
135+ }
136+ 
137+ ASCENDLOGD(
138+ "Validated manifest fields: resource_id=%s soc_version=%s source_file=%s resource_path=%s",
139+ manifest_.at("resource_id").get_ref<const std::string&>().c_str(),
140+ manifest_.at("soc_version").get_ref<const std::string&>().c_str(), sourceFile.c_str(), resourcePath_.c_str());
141+ return true;
142+}
143+ 
144+bool ResourceManifestValidator::ValidateResourceTarget(const std::string& relativePath)
145+{
146+ const std::string target = FileUtils::JoinPath(manifestRoot_, relativePath);
147+ boost::system::error_code error;
148+ const boost::filesystem::file_status status =
149+ boost::filesystem::symlink_status(boost::filesystem::path(target), error);
150+ return CheckAndLog(
151+ !error, __LINE__, "failed to inspect resource target: path=" + target + " error=" + error.message()) &&
152+ CheckAndLog(
153+ boost::filesystem::exists(status), __LINE__,
154+ "invalid resource target: path does not exist, path=" + target) &&
155+ CheckAndLog(
156+ !boost::filesystem::is_symlink(status), __LINE__,
157+ "invalid resource target: symlink is not allowed, path=" + target) &&
158+ CheckAndLog(
159+ boost::filesystem::is_regular_file(status) || boost::filesystem::is_directory(status), __LINE__,
160+ "invalid resource target: expected a regular file or directory, path=" + target);
161+}
162+ 
163+bool ResourceManifestValidator::ValidateResourceReference(const std::string& value)
164+{
165+ const size_t marker = value.find(RESOURCE_ROOT_MARKER);
166+ if (marker == std::string::npos) {
167+ return true;
168+ }
169+ const size_t pathBegin = marker + std::char_traits<char>::length(RESOURCE_ROOT_MARKER);
170+ if (!CheckAndLog(
171+ value.find(RESOURCE_ROOT_MARKER, pathBegin) == std::string::npos, __LINE__,
172+ "invalid resource reference: multiple ${resource} markers are not allowed") ||
173+ !CheckAndLog(
174+ pathBegin + 1U < value.size() && value[pathBegin] == '/', __LINE__,
175+ "invalid resource reference: expected ${resource}/<relative-path>")) {
176+ return false;
177+ }
178+ const std::string path = value.substr(pathBegin + 1U);
179+ return CheckAndLog(
180+ FileUtils::IsSafeRelativePath(path), __LINE__,
181+ "invalid resource reference: expected a safe relative POSIX path, path=" + path) &&
182+ CheckAndLog(
183+ !resourcePath_.empty(), __LINE__,
184+ "invalid resource reference: ${resource} requires a nonempty resource_path") &&
185+ CheckAndLog(
186+ FileUtils::IsPathWithin(path, resourcePath_), __LINE__,
187+ "invalid resource reference: path is outside resource_path, path=" + path) &&
188+ ValidateResourceTarget(path);
189+}
190+ 
191+bool ResourceManifestValidator::ValidateOutputMarker(const std::string& value)
192+{
193+ const size_t markerSize = std::char_traits<char>::length(OUTPUT_ROOT_MARKER);
194+ if (!CheckAndLog(
195+ value.rfind(OUTPUT_ROOT_MARKER, 0U) == 0U && value.size() > markerSize + 1U && value[markerSize] == '/',
196+ __LINE__, "invalid output reference: expected ${output}/<file-name>")) {
197+ return false;
198+ }
199+ const std::string path = value.substr(markerSize + 1U);
200+ return CheckAndLog(
201+ path.find("${") == std::string::npos, __LINE__,
202+ "invalid output reference: relative path must not contain another marker") &&
203+ CheckAndLog(
204+ FileUtils::IsSafeRelativePath(path), __LINE__,
205+ "invalid output reference: expected a safe relative POSIX path, path=" + path) &&
206+ CheckAndLog(
207+ FileUtils::FileName(path) == path, __LINE__,
208+ "invalid output reference: expected a file name without a path, value=" + path);
209+}
210+ 
211+bool ResourceManifestValidator::ValidateMarkerSyntax(const std::string& value)
212+{
213+ if (!CheckAndLog(
214+ value.find('\0') == std::string::npos, __LINE__, "invalid command value: embedded NUL is not allowed")) {
215+ return false;
216+ }
217+ size_t begin = 0U;
218+ while ((begin = value.find("${", begin)) != std::string::npos) {
219+ const size_t end = value.find('}', begin + 2U);
220+ if (!CheckAndLog(end != std::string::npos, __LINE__, "invalid command marker: missing closing brace")) {
221+ return false;
222+ }
223+ const std::string marker = value.substr(begin, end - begin + 1U);
224+ const std::map<std::string, MarkerRequirement>::const_iterator knownMarker = KNOWN_MARKERS.find(marker);
225+ if (knownMarker != KNOWN_MARKERS.end()) {
226+ if (knownMarker->second == MarkerRequirement::SourceFile &&
227+ !CheckAndLog(hasSourceFile_, __LINE__, "invalid ${source_file_path} marker: source_file is required")) {
228+ return false;
229+ }
230+ } else if (IsNamedMarker(marker, ENVIRONMENT_MARKER_PREFIX)) {
231+ const std::string name = ExtractNamedMarkerName(marker, ENVIRONMENT_MARKER_PREFIX);
232+ if (!CheckAndLog(
233+ IsIdentifier(name), __LINE__,
234+ "invalid environment marker name: expected an ASCII identifier, name=" + name)) {
235+ return false;
236+ }
237+ } else {
238+ return CheckAndLog(false, __LINE__, "unsupported command marker: marker=" + marker);
239+ }
240+ begin = end + 1U;
241+ }
242+ return ValidateResourceReference(value);
243+}
244+ 
245+bool ResourceManifestValidator::ValidateOptionValue(const std::string& value)
246+{
247+ const size_t outputBegin = value.find(OUTPUT_ROOT_MARKER);
248+ if (outputBegin == std::string::npos) {
249+ return ValidateMarkerSyntax(value);
250+ }
251+ return ValidateOutputMarker(value.substr(outputBegin));
252+}
253+ 
254+bool ResourceManifestValidator::ValidateOptionValue(const std::string& value, std::set<std::string>& referencedOutputs)
255+{
256+ if (!ValidateOptionValue(value)) {
257+ return false;
258+ }
259+ const size_t outputBegin = value.find(OUTPUT_ROOT_MARKER);
260+ if (outputBegin != std::string::npos) {
261+ referencedOutputs.insert(value.substr(outputBegin));
262+ }
263+ return true;
264+}
265+ 
266+bool ResourceManifestValidator::ValidateOptionReference(const std::string& marker)
267+{
268+ if (!CheckAndLog(
269+ IsNamedMarker(marker, OPTIONS_MARKER_PREFIX), __LINE__,
270+ "invalid options marker: expected ${options:<name>}, marker=" + marker)) {
271+ return false;
272+ }
273+ const std::string name = ExtractNamedMarkerName(marker, OPTIONS_MARKER_PREFIX);
274+ return CheckAndLog(
275+ IsIdentifier(name), __LINE__,
276+ "invalid options marker name: expected an ASCII identifier, name=" + name) &&
277+ ValidateOptionArray(name);
278+}
279+ 
280+bool ResourceManifestValidator::ValidateOptionArray(const std::string& name)
281+{
282+ if (validatedOptions_.count(name) != 0U) {
283+ return true;
284+ }
285+ if (!CheckAndLog(options_ != nullptr, __LINE__, "invalid options marker: manifest options object is missing") ||
286+ !CheckAndLog(
287+ options_->contains(name), __LINE__,
288+ "invalid options marker: referenced field does not exist, name=" + name)) {
289+ return false;
290+ }
291+ const Json& values = options_->at(name);
292+ if (!CheckAndLog(values.is_array(), __LINE__, "invalid options field: expected an array, name=" + name) ||
293+ !CheckAndLog(!values.empty(), __LINE__, "invalid options field: expected a nonempty array, name=" + name)) {
294+ return false;
295+ }
296+ for (const Json& value : values) {
297+ if (!CheckAndLog(value.is_string(), __LINE__, "invalid options item: expected a string, name=" + name) ||
298+ !ValidateOptionValue(value.get_ref<const std::string&>())) {
299+ return false;
300+ }
301+ }
302+ validatedOptions_.insert(name);
303+ return true;
304+}
305+ 
306+bool ResourceManifestValidator::ValidateConstant(const Json& constant, std::set<std::string>& constantNames)
307+{
308+ if (!CheckAndLog(constant.is_object(), __LINE__, "invalid constant: expected an object") ||
309+ !CheckAndLog(constant.contains("name"), __LINE__, "missing required constant field: name") ||
310+ !CheckAndLog(
311+ constant.contains("parameter_index"), __LINE__, "missing required constant field: parameter_index") ||
312+ !CheckAndLog(constant.contains("byte_size"), __LINE__, "missing required constant field: byte_size") ||
313+ !CheckAndLog(constant.contains("file"), __LINE__, "missing required constant field: file") ||
314+ !CheckAndLog(constant.contains("template"), __LINE__, "missing required constant field: template") ||
315+ !CheckAndLog(constant.size() == 5U, __LINE__, "invalid constant: expected exactly five fields") ||
316+ !CheckAndLog(constant.at("name").is_string(), __LINE__, "invalid constant name: expected a string") ||
317+ !CheckAndLog(
318+ !constant.at("name").get_ref<const std::string&>().empty(), __LINE__,
319+ "invalid constant name: expected a nonempty string") ||
320+ !CheckAndLog(
321+ constant.at("parameter_index").is_number_unsigned(), __LINE__,
322+ "invalid parameter_index: expected an unsigned integer") ||
323+ !CheckAndLog(
324+ constant.at("byte_size").is_number_unsigned(), __LINE__,
325+ "invalid byte_size: expected an unsigned integer") ||
326+ !CheckAndLog(
327+ constant.at("byte_size").get<Json::number_unsigned_t>() != 0U, __LINE__,
328+ "invalid byte_size: expected a nonzero value") ||
329+ !CheckAndLog(constant.at("file").is_string(), __LINE__, "invalid constant file: expected a string") ||
330+ !CheckAndLog(constant.at("template").is_string(), __LINE__, "invalid constant template: expected a string") ||
331+ !CheckAndLog(
332+ !constant.at("template").get_ref<const std::string&>().empty(), __LINE__,
333+ "invalid constant template: expected a nonempty string")) {
334+ return false;
335+ }
336+ 
337+ const std::string name = constant.at("name").get_ref<const std::string&>();
338+ const std::string file = constant.at("file").get_ref<const std::string&>();
339+ if (!CheckAndLog(
340+ file.rfind(std::string(RESOURCE_ROOT_MARKER) + "/", 0U) == 0U, __LINE__,
341+ "invalid constant file: expected ${resource}/<relative-path>") ||
342+ !ValidateResourceReference(file) ||
343+ !CheckAndLog(constantNames.insert(name).second, __LINE__, "duplicate constant name: name=" + name)) {
344+ return false;
345+ }
346+ 
347+ ASCENDLOGD(
348+ "Validated constant: name=%s parameter_index=%llu byte_size=%llu", name.c_str(),
349+ static_cast<unsigned long long>(constant.at("parameter_index").get<Json::number_unsigned_t>()),
350+ static_cast<unsigned long long>(constant.at("byte_size").get<Json::number_unsigned_t>()));
351+ return true;
352+}
353+ 
354+bool ResourceManifestValidator::ValidateConstants(const Json& constantInfos)
355+{
356+ std::set<std::string> constantNames;
357+ for (const Json& constant : constantInfos) {
358+ if (!ValidateConstant(constant, constantNames)) {
359+ return false;
360+ }
361+ }
362+ return true;
363+}
364+ 
365+bool ResourceManifestValidator::ValidateObjectOutputs(const Json& outputs)
366+{
367+ for (const Json& output : outputs) {
368+ if (!CheckAndLog(output.is_string(), __LINE__, "invalid object output: expected a string") ||
369+ !ValidateOutputMarker(output.get_ref<const std::string&>())) {
370+ return false;
371+ }
372+ }
373+ return true;
374+}
375+ 
376+bool ResourceManifestValidator::ValidateObjectOutputReferences(
377+ const Json& outputs, const std::set<std::string>& referencedOutputs)
378+{
379+ for (const Json& output : outputs) {
380+ const std::string value = output.get_ref<const std::string&>();
381+ if (!CheckAndLog(
382+ referencedOutputs.count(value) != 0U, __LINE__,
383+ "invalid object output: output is not referenced by its commands, value=" + value)) {
384+ return false;
385+ }
386+ }
387+ ASCENDLOGD(
388+ "Validated object output references: declared=%zu referenced=%zu", outputs.size(), referencedOutputs.size());
389+ return true;
390+}
391+ 
392+bool ResourceManifestValidator::ValidateCommandOptions(
393+ const Json& commandValues, std::set<std::string>& referencedOutputs)
394+{
395+ for (const Json& option : commandValues) {
396+ if (!CheckAndLog(option.is_string(), __LINE__, "invalid command option: expected a string")) {
397+ return false;
398+ }
399+ const std::string value = option.get_ref<const std::string&>();
400+ if (value.find(OPTIONS_MARKER_PREFIX) != std::string::npos) {
401+ if (!ValidateOptionReference(value)) {
402+ return false;
403+ }
404+ continue;
405+ }
406+ if (!ValidateOptionValue(value, referencedOutputs)) {
407+ return false;
408+ }
409+ }
410+ return true;
411+}
412+ 
413+bool ResourceManifestValidator::ValidateCommand(const Json& command, std::set<std::string>& referencedOutputs)
414+{
415+ if (!CheckAndLog(command.is_object(), __LINE__, "invalid command: expected an object") ||
416+ !CheckAndLog(command.contains("type"), __LINE__, "missing required command field: type") ||
417+ !CheckAndLog(command.at("type").is_string(), __LINE__, "invalid command type: expected a string") ||
418+ !CheckAndLog(command.contains("stage"), __LINE__, "missing required command field: stage") ||
419+ !CheckAndLog(
420+ command.at("stage").is_number_unsigned(), __LINE__,
421+ "invalid command stage: expected an unsigned integer") ||
422+ !CheckAndLog(command.contains("cmd"), __LINE__, "missing required command field: cmd") ||
423+ !CheckAndLog(command.at("cmd").is_array(), __LINE__, "invalid command field cmd: expected an array")) {
424+ return false;
425+ }
426+ 
427+ const std::string type = command.at("type").get_ref<const std::string&>();
428+ if (!ValidateCommandOptions(command.at("cmd"), referencedOutputs)) {
429+ return false;
430+ }
431+ ASCENDLOGD(
432+ "Validated command: type=%s stage=%llu cmd=%zu", type.c_str(),
433+ static_cast<unsigned long long>(command.at("stage").get<Json::number_unsigned_t>()), command.at("cmd").size());
434+ return true;
435+}
436+ 
437+bool ResourceManifestValidator::ValidateCommands(const Json& commands, std::set<std::string>& referencedOutputs)
438+{
439+ for (const Json& command : commands) {
440+ if (!ValidateCommand(command, referencedOutputs)) {
441+ return false;
442+ }
443+ }
444+ return true;
445+}
446+ 
447+bool ResourceManifestValidator::ValidateObject(const Json& object, std::set<std::string>& objectNames)
448+{
449+ if (!CheckAndLog(object.is_object(), __LINE__, "invalid object: expected an object") ||
450+ !CheckAndLog(object.contains("object_name"), __LINE__, "missing required object field: object_name") ||
451+ !CheckAndLog(object.at("object_name").is_string(), __LINE__, "invalid object_name: expected a string") ||
452+ !CheckAndLog(
453+ !object.at("object_name").get_ref<const std::string&>().empty(), __LINE__,
454+ "invalid object_name: expected a nonempty string") ||
455+ !CheckAndLog(object.contains("object_type"), __LINE__, "missing required object field: object_type") ||
456+ !CheckAndLog(object.at("object_type").is_string(), __LINE__, "invalid object_type: expected a string") ||
457+ !CheckAndLog(object.contains("commands"), __LINE__, "missing required object field: commands") ||
458+ !CheckAndLog(object.at("commands").is_array(), __LINE__, "invalid commands: expected an array") ||
459+ !CheckAndLog(!object.at("commands").empty(), __LINE__, "invalid commands: expected a nonempty array") ||
460+ !CheckAndLog(object.contains("outputs"), __LINE__, "missing required object field: outputs") ||
461+ !CheckAndLog(object.at("outputs").is_array(), __LINE__, "invalid outputs: expected an array") ||
462+ !CheckAndLog(!object.at("outputs").empty(), __LINE__, "invalid outputs: expected a nonempty array")) {
463+ return false;
464+ }
465+ 
466+ const std::string objectName = object.at("object_name").get_ref<const std::string&>();
467+ const std::string objectType = object.at("object_type").get_ref<const std::string&>();
468+ if (!CheckAndLog(
469+ FileUtils::IsSafeRelativePath(objectName) && FileUtils::FileName(objectName) == objectName, __LINE__,
470+ "invalid object_name: expected a plain name") ||
471+ !CheckAndLog(
472+ objectNames.insert(objectName).second, __LINE__, "duplicate object_name: object_name=" + objectName) ||
473+ !ValidateObjectOutputs(object.at("outputs"))) {
474+ return false;
475+ }
476+ 
477+ std::set<std::string> referencedOutputs;
478+ if (!ValidateCommands(object.at("commands"), referencedOutputs) ||
479+ !ValidateObjectOutputReferences(object.at("outputs"), referencedOutputs)) {
480+ return false;
481+ }
482+ ASCENDLOGD(
483+ "Validated object: object_name=%s object_type=%s commands=%zu outputs=%zu", objectName.c_str(),
484+ objectType.c_str(), object.at("commands").size(), object.at("outputs").size());
485+ return true;
486+}
487+ 
488+bool ResourceManifestValidator::ValidateObjects(const Json& objects)
489+{
490+ std::set<std::string> objectNames;
491+ for (const Json& object : objects) {
492+ if (!ValidateObject(object, objectNames)) {
493+ return false;
494+ }
495+ }
496+ return true;
497+}
498+ 
499+bool ResourceManifestValidator::ValidateKernel(const Json& kernel, std::set<std::string>& kernelNames)
500+{
501+ if (!CheckAndLog(kernel.is_object(), __LINE__, "invalid kernel: expected an object") ||
502+ !CheckAndLog(kernel.contains("kernel_name"), __LINE__, "missing required kernel field: kernel_name") ||
503+ !CheckAndLog(kernel.at("kernel_name").is_string(), __LINE__, "invalid kernel_name: expected a string") ||
504+ !CheckAndLog(
505+ !kernel.at("kernel_name").get_ref<const std::string&>().empty(), __LINE__,
506+ "invalid kernel_name: expected a nonempty string") ||
507+ !CheckAndLog(
508+ !kernel.contains("constant_infos") || kernel.at("constant_infos").is_array(), __LINE__,
509+ "invalid constant_infos: expected an array") ||
510+ !CheckAndLog(kernel.contains("objects"), __LINE__, "missing required kernel field: objects") ||
511+ !CheckAndLog(kernel.at("objects").is_array(), __LINE__, "invalid objects: expected an array") ||
512+ !CheckAndLog(!kernel.at("objects").empty(), __LINE__, "invalid objects: expected a nonempty array") ||
513+ !CheckAndLog(
514+ !kernel.contains("link_options") || kernel.at("link_options").is_array(), __LINE__,
515+ "invalid link_options: expected an array")) {
516+ return false;
517+ }
518+ 
519+ const std::string kernelName = kernel.at("kernel_name").get_ref<const std::string&>();
520+ if (!CheckAndLog(
521+ kernelNames.insert(kernelName).second, __LINE__, "duplicate kernel_name: kernel_name=" + kernelName) ||
522+ (kernel.contains("constant_infos") && !ValidateConstants(kernel.at("constant_infos"))) ||
523+ !ValidateObjects(kernel.at("objects"))) {
524+ return false;
525+ }
526+ ASCENDLOGD(
527+ "Validated kernel: kernel_name=%s constants=%zu objects=%zu link_options=%zu", kernelName.c_str(),
528+ kernel.contains("constant_infos") ? kernel.at("constant_infos").size() : 0U, kernel.at("objects").size(),
529+ kernel.contains("link_options") ? kernel.at("link_options").size() : 0U);
530+ return true;
531+}
532+ 
533+bool ResourceManifestValidator::Validate()
534+{
535+ options_ = nullptr;
536+ validatedOptions_.clear();
537+ hasSourceFile_ = false;
538+ 
539+ if (!ValidateManifestBaseFields()) {
540+ return false;
541+ }
542+ ASCENDLOGD("Manifest JSON and base fields are valid: path=%s", manifestPath_.c_str());
543+ 
544+ resourcePath_ = manifest_.at("resource_path").get_ref<const std::string&>();
545+ if (!ValidateManifestFields()) {
546+ return false;
547+ }
548+ 
549+ std::set<std::string> kernelNames;
550+ for (const Json& kernel : manifest_.at("kernels")) {
551+ if (!ValidateKernel(kernel, kernelNames)) {
552+ return false;
553+ }
554+ }
555+ ASCENDLOGI(
556+ "Manifest validated successfully: resource_id=%s soc_version=%s kernels=%zu",
557+ manifest_.at("resource_id").get_ref<const std::string&>().c_str(),
558+ manifest_.at("soc_version").get_ref<const std::string&>().c_str(), manifest_.at("kernels").size());
559+ return true;
560+}
561+ 
562+bool ValidateResourceManifest(const Json& manifest, const std::string& manifestPath)
563+{
564+ ASCENDLOGI("Validating resource manifest: path=%s", manifestPath.c_str());
565+ const bool valid = ResourceManifestValidator(manifest, manifestPath).Validate();
566+ if (!valid) {
567+ ASCENDLOGE("Resource manifest validation failed: path=%s", manifestPath.c_str());
568+ }
569+ return valid;
570+}
571+ 
572+} // namespace manifest_generator
573+} // namespace ascendc
@@ -0,0 +1,68 @@
1+/**
2+ * Copyright (c) 2025 Huawei Technologies Co., Ltd.
3+ * This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4+ * CANN Open Software License Agreement Version 2.0 (the "License").
5+ * Please refer to the License for details. You may not use this file except in compliance with the License.
6+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7+ * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8+ * See LICENSE in the root of the software repository for the full text of the License.
9+ */
10+ 
11+#ifndef ASCENDC_MANIFEST_GENERATOR_RESOURCE_MANIFEST_VALIDATOR_H
12+#define ASCENDC_MANIFEST_GENERATOR_RESOURCE_MANIFEST_VALIDATOR_H
13+ 
14+#include <set>
15+#include <string>
16+ 
17+#include <nlohmann/json_fwd.hpp>
18+ 
19+namespace ascendc {
20+namespace manifest_generator {
21+ 
22+class ResourceManifestValidator final {
C
Cchenyiyuan25 天前

这个为啥要加final

likedislike
23+public:
24+ ResourceManifestValidator(const nlohmann::json& manifest, const std::string& manifestPath);
25+ 
26+ bool Validate();
27+ 
28+private:
29+ bool ValidateManifestBaseFields();
30+ bool ValidateManifestFields();
31+ bool ValidateKernel(const nlohmann::json& kernel, std::set<std::string>& kernelNames);
32+ bool ValidateConstants(const nlohmann::json& constantInfos);
33+ bool ValidateConstant(const nlohmann::json& constant, std::set<std::string>& constantNames);
34+ bool ValidateObjects(const nlohmann::json& objects);
35+ bool ValidateObject(const nlohmann::json& object, std::set<std::string>& objectNames);
36+ bool ValidateCommands(const nlohmann::json& commands, std::set<std::string>& referencedOutputs);
37+ bool ValidateCommand(const nlohmann::json& command, std::set<std::string>& referencedOutputs);
38+ bool ValidateCommandOptions(const nlohmann::json& commandValues, std::set<std::string>& referencedOutputs);
39+ bool ValidateObjectOutputs(const nlohmann::json& outputs);
40+ bool ValidateObjectOutputReferences(const nlohmann::json& outputs, const std::set<std::string>& referencedOutputs);
41+ bool ValidateOptionValue(const std::string& value);
42+ bool ValidateOptionValue(const std::string& value, std::set<std::string>& referencedOutputs);
43+ bool ValidateMarkerSyntax(const std::string& value);
44+ bool ValidateOptionReference(const std::string& marker);
45+ bool ValidateOptionArray(const std::string& name);
46+ bool ValidateResourceReference(const std::string& value);
47+ bool ValidateResourceTarget(const std::string& relativePath);
48+ bool ValidateOutputMarker(const std::string& value);
49+ 
50+ static bool IsIdentifier(const std::string& value);
51+ static bool IsNamedMarker(const std::string& marker, const std::string& prefix);
52+ static std::string ExtractNamedMarkerName(const std::string& marker, const std::string& prefix);
53+ 
54+ const nlohmann::json& manifest_;
55+ std::string manifestPath_;
56+ std::string manifestRoot_;
57+ std::string resourcePath_;
58+ const nlohmann::json* options_;
59+ std::set<std::string> validatedOptions_;
60+ bool hasSourceFile_;
61+};
62+ 
63+bool ValidateResourceManifest(const nlohmann::json& manifest, const std::string& manifestPath);
64+ 
65+} // namespace manifest_generator
66+} // namespace ascendc
67+ 
68+#endif // ASCENDC_MANIFEST_GENERATOR_RESOURCE_MANIFEST_VALIDATOR_H
@@ -22,11 +22,6 @@ namespace {
22 22 
23namespace fs = boost::filesystem;23namespace fs = boost::filesystem;
24 24 
25-void LogFilesystemError(const char* operation, const std::string& path, const boost::system::error_code& error)
26-{
27- ASCENDLOGW("Failed to %s: path=%s error=%s", operation, path.c_str(), error.message().c_str());
28-}
29- 
30} // namespace25} // namespace
31 26 
32std::string FileUtils::JoinPath(const std::string& left, const std::string& right)27std::string FileUtils::JoinPath(const std::string& left, const std::string& right)
@@ -50,7 +45,7 @@ bool FileUtils::MakeAbsolutePath(const std::string& path, std::string& absolute)
50 boost::system::error_code error;45 boost::system::error_code error;
51 const fs::path result = fs::absolute(fs::path(path), error);46 const fs::path result = fs::absolute(fs::path(path), error);
52 if (error) {47 if (error) {
53- LogFilesystemError("make path absolute", path, error);48+ ASCENDLOGE("Failed to make path absolute: path=%s error=%s", path.c_str(), error.message().c_str());
54 absolute.clear();49 absolute.clear();
55 return false;50 return false;
56 }51 }
@@ -59,6 +54,26 @@ bool FileUtils::MakeAbsolutePath(const std::string& path, std::string& absolute)
59 return true;54 return true;
60}55}
61 56 
57+bool FileUtils::IsSafeRelativePath(const std::string& path)
58+{
59+ if (path.empty() || path.find('\\') != std::string::npos || path.find('\0') != std::string::npos) {
60+ return false;
61+ }
62+ const fs::path filesystemPath(path);
63+ if (!filesystemPath.is_relative()) {
64+ return false;
65+ }
66+ if (filesystemPath.lexically_normal().generic_string() != path) {
67+ return false;
68+ }
69+ for (const fs::path& component : filesystemPath) {
70+ if (component == fs::path(".") || component == fs::path("..")) {
71+ return false;
72+ }
73+ }
74+ return true;
75+}
76+ 
62bool FileUtils::IsPathWithin(const std::string& path, const std::string& root)77bool FileUtils::IsPathWithin(const std::string& path, const std::string& root)
63{78{
64 if (path.empty() || root.empty()) {79 if (path.empty() || root.empty()) {
@@ -113,7 +128,7 @@ bool FileUtils::ResolveCanonicalPath(const std::string& path, std::string& resol
113 boost::system::error_code error;128 boost::system::error_code error;
114 const fs::path canonical = fs::canonical(fs::path(path), error);129 const fs::path canonical = fs::canonical(fs::path(path), error);
115 if (error) {130 if (error) {
116- LogFilesystemError("resolve canonical path", path, error);131+ ASCENDLOGE("Failed to resolve canonical path: path=%s error=%s", path.c_str(), error.message().c_str());
117 resolved.clear();132 resolved.clear();
118 return false;133 return false;
119 }134 }
@@ -128,26 +143,28 @@ bool FileUtils::ResolveDirectory(const std::string& path, std::string& resolved)
128 boost::system::error_code error;143 boost::system::error_code error;
129 const fs::file_status inputStatus = fs::symlink_status(fs::path(path), error);144 const fs::file_status inputStatus = fs::symlink_status(fs::path(path), error);
130 if (error) {145 if (error) {
131- LogFilesystemError("inspect directory", path, error);146+ ASCENDLOGE("Failed to inspect directory: path=%s error=%s", path.c_str(), error.message().c_str());
132 resolved.clear();147 resolved.clear();
133 return false;148 return false;
134 }149 }
135 if (fs::is_symlink(inputStatus)) {150 if (fs::is_symlink(inputStatus)) {
136- ASCENDLOGW("Rejected symlink directory: path=%s", path.c_str());151+ ASCENDLOGE("Rejected symlink directory: path=%s", path.c_str());
137 resolved.clear();152 resolved.clear();
138 return false;153 return false;
139 }154 }
140 const fs::path canonical = fs::canonical(fs::path(path), error);155 const fs::path canonical = fs::canonical(fs::path(path), error);
141 if (error) {156 if (error) {
142- LogFilesystemError("resolve directory", path, error);157+ ASCENDLOGE("Failed to resolve directory: path=%s error=%s", path.c_str(), error.message().c_str());
143 resolved.clear();158 resolved.clear();
144 return false;159 return false;
145 }160 }
146 if (!fs::is_directory(canonical, error) || error) {161 if (!fs::is_directory(canonical, error) || error) {
147 if (error) {162 if (error) {
148- LogFilesystemError("inspect resolved directory", canonical.string(), error);163+ ASCENDLOGE(
164+ "Failed to inspect resolved directory: path=%s error=%s", canonical.string().c_str(),
165+ error.message().c_str());
149 } else {166 } else {
150- ASCENDLOGW("Resolved path is not a directory: path=%s resolved=%s", path.c_str(), canonical.c_str());167+ ASCENDLOGE("Resolved path is not a directory: path=%s resolved=%s", path.c_str(), canonical.c_str());
151 }168 }
152 resolved.clear();169 resolved.clear();
153 return false;170 return false;
@@ -166,7 +183,7 @@ bool FileUtils::ResolveSubdirectory(const std::string& path, const std::string&
166 return false;183 return false;
167 }184 }
168 if (!IsPathWithin(canonical, root)) {185 if (!IsPathWithin(canonical, root)) {
169- ASCENDLOGW(186+ ASCENDLOGE(
170 "Resolved directory is outside root: path=%s resolved=%s root=%s", path.c_str(), canonical.c_str(),187 "Resolved directory is outside root: path=%s resolved=%s root=%s", path.c_str(), canonical.c_str(),
171 root.c_str());188 root.c_str());
172 resolved.clear();189 resolved.clear();
@@ -186,14 +203,14 @@ bool FileUtils::CreateDirectories(const std::string& path)
186 boost::system::error_code error;203 boost::system::error_code error;
187 fs::create_directories(fs::path(path), error);204 fs::create_directories(fs::path(path), error);
188 if (error) {205 if (error) {
189- LogFilesystemError("create directories", path, error);206+ ASCENDLOGE("Failed to create directories: path=%s error=%s", path.c_str(), error.message().c_str());
190 return false;207 return false;
191 }208 }
192 if (!fs::is_directory(fs::path(path), error) || error) {209 if (!fs::is_directory(fs::path(path), error) || error) {
193 if (error) {210 if (error) {
194- LogFilesystemError("inspect created directory", path, error);211+ ASCENDLOGE("Failed to inspect created directory: path=%s error=%s", path.c_str(), error.message().c_str());
195 } else {212 } else {
196- ASCENDLOGW("Created path is not a directory: path=%s", path.c_str());213+ ASCENDLOGE("Created path is not a directory: path=%s", path.c_str());
197 }214 }
198 return false;215 return false;
199 }216 }
@@ -201,13 +218,13 @@ bool FileUtils::CreateDirectories(const std::string& path)
201 return true;218 return true;
202}219}
203 220 
204-bool FileUtils::RemoveAll(const std::string& path)221+bool FileUtils::RemoveAll(const std::string& path) noexcept
205{222{
206 ASCENDLOGD("Removing path recursively: path=%s", path.c_str());223 ASCENDLOGD("Removing path recursively: path=%s", path.c_str());
207 boost::system::error_code error;224 boost::system::error_code error;
208 (void)fs::remove_all(fs::path(path), error);225 (void)fs::remove_all(fs::path(path), error);
209 if (error) {226 if (error) {
210- LogFilesystemError("remove path recursively", path, error);227+ ASCENDLOGE("Failed to remove path recursively: path=%s error=%d", path.c_str(), error.value());
211 return false;228 return false;
212 }229 }
213 ASCENDLOGI("Removed path recursively: path=%s", path.c_str());230 ASCENDLOGI("Removed path recursively: path=%s", path.c_str());
@@ -222,25 +239,25 @@ bool FileUtils::ReadRegularFile(const std::string& path, uintmax_t maximum, std:
222 const fs::file_status inputStatus = fs::symlink_status(fs::path(path), error);239 const fs::file_status inputStatus = fs::symlink_status(fs::path(path), error);
223 if (error || fs::is_symlink(inputStatus) || !fs::is_regular_file(inputStatus)) {240 if (error || fs::is_symlink(inputStatus) || !fs::is_regular_file(inputStatus)) {
224 if (error) {241 if (error) {
225- LogFilesystemError("inspect regular file", path, error);242+ ASCENDLOGE("Failed to inspect regular file: path=%s error=%s", path.c_str(), error.message().c_str());
226 } else {243 } else {
227- ASCENDLOGW("Rejected non-regular file: path=%s", path.c_str());244+ ASCENDLOGE("Rejected non-regular file: path=%s", path.c_str());
228 }245 }
229 return false;246 return false;
230 }247 }
231 const uintmax_t fileSize = fs::file_size(fs::path(path), error);248 const uintmax_t fileSize = fs::file_size(fs::path(path), error);
232 if (error) {249 if (error) {
233- LogFilesystemError("read regular file size", path, error);250+ ASCENDLOGE("Failed to read regular file size: path=%s error=%s", path.c_str(), error.message().c_str());
234 return false;251 return false;
235 }252 }
236 if (fileSize > maximum || fileSize > static_cast<uintmax_t>(std::numeric_limits<size_t>::max()) ||253 if (fileSize > maximum || fileSize > static_cast<uintmax_t>(std::numeric_limits<size_t>::max()) ||
237 fileSize > static_cast<uintmax_t>(std::numeric_limits<std::streamsize>::max())) {254 fileSize > static_cast<uintmax_t>(std::numeric_limits<std::streamsize>::max())) {
238- ASCENDLOGW("Regular file exceeds size limit: path=%s size=%ju maximum=%ju", path.c_str(), fileSize, maximum);255+ ASCENDLOGE("Regular file exceeds size limit: path=%s size=%ju maximum=%ju", path.c_str(), fileSize, maximum);
239 return false;256 return false;
240 }257 }
241 std::ifstream input(path.c_str(), std::ios::binary);258 std::ifstream input(path.c_str(), std::ios::binary);
242 if (!input.is_open()) {259 if (!input.is_open()) {
243- ASCENDLOGW("Failed to open regular file: path=%s", path.c_str());260+ ASCENDLOGE("Failed to open regular file: path=%s", path.c_str());
244 return false;261 return false;
245 }262 }
246 data.resize(static_cast<size_t>(fileSize));263 data.resize(static_cast<size_t>(fileSize));
@@ -249,7 +266,7 @@ bool FileUtils::ReadRegularFile(const std::string& path, uintmax_t maximum, std:
249 }266 }
250 input.close();267 input.close();
251 if (!input) {268 if (!input) {
252- ASCENDLOGW("Failed to read or close regular file: path=%s", path.c_str());269+ ASCENDLOGE("Failed to read or close regular file: path=%s", path.c_str());
253 data.clear();270 data.clear();
254 return false;271 return false;
255 }272 }
@@ -265,7 +282,7 @@ bool FileUtils::FinalizeOutput(std::ofstream& output)
265 output.close();282 output.close();
266 const bool success = writeSucceeded && static_cast<bool>(output);283 const bool success = writeSucceeded && static_cast<bool>(output);
267 if (!success) {284 if (!success) {
268- ASCENDLOGW("Failed to flush or close output stream");285+ ASCENDLOGE("Failed to flush or close output stream");
269 return false;286 return false;
270 }287 }
271 ASCENDLOGD("Finalized output stream");288 ASCENDLOGD("Finalized output stream");
@@ -278,11 +295,11 @@ bool FileUtils::CopyFile(const std::string& source, const std::string& destinati
278 boost::system::error_code error;295 boost::system::error_code error;
279 if (!fs::copy_file(fs::path(source), fs::path(destination), fs::copy_options::none, error)) {296 if (!fs::copy_file(fs::path(source), fs::path(destination), fs::copy_options::none, error)) {
280 if (error) {297 if (error) {
281- ASCENDLOGW(298+ ASCENDLOGE(
282 "Failed to copy file: source=%s destination=%s error=%s", source.c_str(), destination.c_str(),299 "Failed to copy file: source=%s destination=%s error=%s", source.c_str(), destination.c_str(),
283 error.message().c_str());300 error.message().c_str());
284 } else {301 } else {
285- ASCENDLOGW("Failed to copy file: source=%s destination=%s", source.c_str(), destination.c_str());302+ ASCENDLOGE("Failed to copy file: source=%s destination=%s", source.c_str(), destination.c_str());
286 }303 }
287 return false;304 return false;
288 }305 }
@@ -26,6 +26,7 @@ public:
26 static std::string ParentPath(const std::string& path);26 static std::string ParentPath(const std::string& path);
27 static std::string FileName(const std::string& path);27 static std::string FileName(const std::string& path);
28 static bool MakeAbsolutePath(const std::string& path, std::string& absolute);28 static bool MakeAbsolutePath(const std::string& path, std::string& absolute);
29+ static bool IsSafeRelativePath(const std::string& path);
29 static bool IsPathWithin(const std::string& path, const std::string& root);30 static bool IsPathWithin(const std::string& path, const std::string& root);
30 31 
31 static bool PathExists(const std::string& path) noexcept;32 static bool PathExists(const std::string& path) noexcept;
@@ -37,7 +38,7 @@ public:
37 static bool ResolveSubdirectory(const std::string& path, const std::string& root, std::string& resolved);38 static bool ResolveSubdirectory(const std::string& path, const std::string& root, std::string& resolved);
38 39 
39 static bool CreateDirectories(const std::string& path);40 static bool CreateDirectories(const std::string& path);
40- static bool RemoveAll(const std::string& path);41+ static bool RemoveAll(const std::string& path) noexcept;
41 42 
42 static bool ReadRegularFile(const std::string& path, uintmax_t maximum, std::vector<uint8_t>& data);43 static bool ReadRegularFile(const std::string& path, uintmax_t maximum, std::vector<uint8_t>& data);
43 static bool FinalizeOutput(std::ofstream& output);44 static bool FinalizeOutput(std::ofstream& output);