已合并
[feat] add json validator #5068
sjtulxh创建于 26 天前
[feat] add json validator #5068
已合并
共 14 个文件变更+1246-94
| @@ -208,11 +208,13 @@ add_executable(ascendc_ut_asc_compile_exporter | |||
| 208 | asc_compile_exporter/test_file_utils.cpp | 208 | asc_compile_exporter/test_file_utils.cpp |
| 209 | asc_compile_exporter/test_manifest_bundle_types.cpp | 209 | asc_compile_exporter/test_manifest_bundle_types.cpp |
| 210 | asc_compile_exporter/test_process_runner.cpp | 210 | asc_compile_exporter/test_process_runner.cpp |
| 211 | + asc_compile_exporter/test_resource_manifest_validator.cpp | ||
| 211 | asc_compile_exporter/test_ascendc_tool_log.cpp | 212 | asc_compile_exporter/test_ascendc_tool_log.cpp |
| 212 | ${ASCENDC_DIR}/tools/build/asc_compile_exporter/build_workspace.cpp | 213 | ${ASCENDC_DIR}/tools/build/asc_compile_exporter/build_workspace.cpp |
| 213 | ${ASCENDC_DIR}/tools/build/asc_compile_exporter/bundle_source_generator.cpp | 214 | ${ASCENDC_DIR}/tools/build/asc_compile_exporter/bundle_source_generator.cpp |
| 214 | ${ASCENDC_DIR}/tools/build/asc_compile_exporter/collected_manifest_repository.cpp | 215 | ${ASCENDC_DIR}/tools/build/asc_compile_exporter/collected_manifest_repository.cpp |
| 215 | ${ASCENDC_DIR}/tools/build/asc_compile_exporter/manifest_bundle_compiler.cpp | 216 | ${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.cpp | 218 | ${ASCENDC_DIR}/tools/build/common/file_utils.cpp |
| 217 | ${ASCENDC_DIR}/tools/build/common/process_runner.cpp | 219 | ${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") const | 211 | + 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 | 11 | ||
| 12 | 12 | ||
| 13 | + | ||
| 14 | + | ||
| 15 | + | ||
| 13 | 16 | ||
| 14 | 17 | ||
| 15 | 18 | ||
| @@ -21,6 +24,26 @@ | |||
| 21 | 24 | ||
| 22 | 25 | ||
| 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 | + | ||
| 24 | namespace { | 47 | namespace { |
| 25 | 48 | ||
| 26 | using asc_compile_exporter_test::ModuleTest; | 49 | using 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 | + | ||
| 48 | bool ParseCommand(std::vector<std::string> arguments, BuildCollectedBundleRequest& request) | 87 | bool 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 | ||
| 68 | int RunProgramCommand(std::vector<std::string> arguments) | 107 | int 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 | + | ||
| 78 | TEST(CompileExporterCliUnitTest, ParseJobsAcceptsOnlyPositiveUint32Values) | 128 | TEST(CompileExporterCliUnitTest, ParseJobsAcceptsOnlyPositiveUint32Values) |
| 79 | { | 129 | { |
| 80 | uint32_t jobs = 99U; | 130 | uint32_t jobs = 99U; |
| @@ -221,6 +271,7 @@ TEST_F(ModuleTest, CliValidatesArgumentsWhenRead) | |||
| 221 | 271 | ||
| 222 | TEST_F(ModuleTest, RunPassesValidatedArgumentsToCompiler) | 272 | TEST_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 | ||
| 235 | TEST_F(ModuleTest, RunBuildsBundleWithKeywordArguments) | 287 | TEST_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 | } // namespace | 356 | } // namespace |
| @@ -27,6 +27,7 @@ namespace { | |||
| 27 | constexpr size_t TEST_MAX_DIRECTORY_DEPTH = 64U; | 27 | constexpr size_t TEST_MAX_DIRECTORY_DEPTH = 64U; |
| 28 | 28 | ||
| 29 | using asc_compile_exporter_test::ModuleTest; | 29 | using asc_compile_exporter_test::ModuleTest; |
| 30 | +using asc_compile_exporter_test::ReadTestFile; | ||
| 30 | using asc_compile_exporter_test::WriteTestFile; | 31 | using asc_compile_exporter_test::WriteTestFile; |
| 31 | 32 | ||
| 32 | TEST_F(ModuleTest, RepositorySortsManifestsAndResourcesAndSkipsEmptyFiles) | 33 | TEST_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 | ||
| 95 | TEST_F(ModuleTest, RepositoryRejectsNonRegularResource) | 134 | TEST_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 | + | ||
| 12 | + | ||
| 13 | + | ||
| 14 | + | ||
| 15 | + | ||
| 16 | + | ||
| 17 | + | ||
| 18 | + | ||
| 19 | + | ||
| 20 | + | ||
| 21 | + | ||
| 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.cpp | 38 | bundle_source_generator.cpp |
| 39 | collected_manifest_repository.cpp | 39 | collected_manifest_repository.cpp |
| 40 | manifest_bundle_compiler.cpp | 40 | manifest_bundle_compiler.cpp |
| 41 | + resource_manifest_validator.cpp | ||
| 41 | ../common/file_utils.cpp | 42 | ../common/file_utils.cpp |
| 42 | ../common/process_runner.cpp | 43 | ../common/process_runner.cpp |
| 43 | ) | 44 | ) |
| @@ -24,21 +24,36 @@ | |||
| 24 | 24 | ||
| 25 | 25 | ||
| 26 | namespace { | 26 | namespace { |
| 27 | - | 27 | +void PrintUsage(std::ostream& output, const char* program) |
| 28 | -void PrintUsage(const char* program) | ||
| 29 | { | 28 | { |
| 30 | - std::cerr << "Usage:\n " << program | 29 | + 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 | ||
| 44 | bool ReportArgumentError(const char* reason, const std::string& argument) | 59 | bool 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 | } // namespace | 271 | } // 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 | 21 | ||
| 22 | 22 | ||
| 23 | 23 | ||
| 24 | + | ||
| 24 | 25 | ||
| 25 | using Json = nlohmann::json; | 26 | using 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 | + | ||
| 12 | + | ||
| 13 | + | ||
| 14 | + | ||
| 15 | + | ||
| 16 | + | ||
| 17 | + | ||
| 18 | + | ||
| 19 | + | ||
| 20 | + | ||
| 21 | + | ||
| 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 | + | ||
| 12 | + | ||
| 13 | + | ||
| 14 | + | ||
| 15 | + | ||
| 16 | + | ||
| 17 | + | ||
| 18 | + | ||
| 19 | +namespace ascendc { | ||
| 20 | +namespace manifest_generator { | ||
| 21 | + | ||
| 22 | +class ResourceManifestValidator final { | ||
C | |||
| 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 | + | ||
| @@ -22,11 +22,6 @@ namespace { | |||
| 22 | 22 | ||
| 23 | namespace fs = boost::filesystem; | 23 | namespace 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 | } // namespace | 25 | } // namespace |
| 31 | 26 | ||
| 32 | std::string FileUtils::JoinPath(const std::string& left, const std::string& right) | 27 | std::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 | + | ||
| 62 | bool FileUtils::IsPathWithin(const std::string& path, const std::string& root) | 77 | bool 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); |
这个为啥要加final