Skip to content

Commit d7d896d

Browse files
maskri17copybara-github
authored andcommitted
Fix CEL policy YAML parser to use codepoint positions instead of byte positions.
PiperOrigin-RevId: 955506466
1 parent 4f1003d commit d7d896d

5 files changed

Lines changed: 88 additions & 6 deletions

File tree

policy/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ cc_library(
8484
":cel_policy_parser",
8585
"//common:source",
8686
"//internal:status_macros",
87+
"//internal:utf8",
8788
"//policy/internal:yaml_string_element_scanner",
8889
"@com_google_absl//absl/status",
8990
"@com_google_absl//absl/status:statusor",

policy/cel_policy_parse_context.cc

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,13 @@
1414

1515
#include "policy/cel_policy_parse_context.h"
1616

17+
#include <cstddef>
1718
#include <memory>
18-
#include <string>
1919
#include <string_view>
2020
#include <utility>
2121

2222
#include "absl/log/absl_check.h"
23+
#include "common/source.h"
2324
#include "policy/cel_policy.h"
2425
#include "policy/cel_policy_parse_result.h"
2526

@@ -43,7 +44,21 @@ CelPolicyParseResult CelPolicyParseContext::GetResult() {
4344

4445
void CelPolicyParseContext::ReportError(CelPolicyElementId element_id,
4546
std::string_view message) {
46-
issues_.push_back(CelPolicyIssue(element_id, std::string(message)));
47+
issues_.push_back(CelPolicyIssue(element_id, message));
48+
}
49+
50+
SourcePosition CelPolicyParseContext::GetCodepointPosition(
51+
SourcePosition byte_offset) const {
52+
if (byte_offset < 0) {
53+
return -1;
54+
}
55+
if (byte_to_codepoint_mapping_.empty()) {
56+
return byte_offset;
57+
}
58+
if (static_cast<size_t>(byte_offset) >= byte_to_codepoint_mapping_.size()) {
59+
return byte_to_codepoint_mapping_.back();
60+
}
61+
return byte_to_codepoint_mapping_[byte_offset];
4762
}
4863

4964
} // namespace cel

policy/cel_policy_parse_context.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include <vector>
2121

2222
#include "absl/strings/string_view.h"
23+
#include "common/source.h"
2324
#include "policy/cel_policy.h"
2425
#include "policy/cel_policy_parse_result.h"
2526

@@ -53,11 +54,18 @@ class CelPolicyParseContext {
5354

5455
CelPolicyElementId next_element_id() { return next_element_id_++; }
5556

57+
void set_byte_to_codepoint_mapping(std::vector<SourcePosition> mapping) {
58+
byte_to_codepoint_mapping_ = std::move(mapping);
59+
}
60+
61+
SourcePosition GetCodepointPosition(SourcePosition byte_offset) const;
62+
5663
private:
5764
std::shared_ptr<CelPolicySource> policy_source_;
5865
CelPolicyElementId next_element_id_ = 0;
5966
std::vector<CelPolicyIssue> issues_;
6067
std::unique_ptr<CelPolicy> policy_;
68+
std::vector<SourcePosition> byte_to_codepoint_mapping_;
6169
};
6270

6371
} // namespace cel

policy/yaml_policy_parser.cc

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,35 +14,49 @@
1414

1515
#include "policy/yaml_policy_parser.h"
1616

17+
#include <cstddef>
1718
#include <memory>
1819
#include <optional>
1920
#include <string>
2021
#include <utility>
22+
#include <vector>
2123

2224
#include "absl/status/status.h"
2325
#include "absl/status/statusor.h"
2426
#include "absl/strings/str_cat.h"
2527
#include "absl/strings/string_view.h"
2628
#include "common/source.h"
2729
#include "internal/status_macros.h"
30+
#include "internal/utf8.h"
2831
#include "policy/cel_policy.h"
2932
#include "policy/cel_policy_parse_context.h"
3033
#include "policy/cel_policy_parse_result.h"
3134
#include "policy/cel_policy_parser.h"
3235
#include "policy/internal/yaml_string_element_scanner.h"
3336
#include "yaml-cpp/exceptions.h"
37+
#include "yaml-cpp/mark.h"
3438
#include "yaml-cpp/node/node.h"
3539
#include "yaml-cpp/node/parse.h"
3640
#include "yaml-cpp/null.h"
3741
#include "yaml-cpp/yaml.h" // IWYU pragma: keep
3842

3943
namespace cel {
44+
namespace {
45+
46+
SourcePosition GetMarkCodepointPosition(const CelPolicyParseContext& ctx,
47+
const YAML::Mark& mark) {
48+
if (mark.is_null() || mark.pos < 0) return -1;
49+
return ctx.GetCodepointPosition(mark.pos);
50+
}
51+
52+
} // namespace
4053

4154
CelPolicyElementId YamlPolicyParser::CollectMetadata(
4255
CelPolicyParseContext& ctx, const YAML::Node& node) const {
4356
CelPolicyElementId element_id = ctx.next_element_id();
4457
if (!node.Mark().is_null()) {
45-
ctx.policy_source().NoteSourcePosition(element_id, node.Mark().pos);
58+
ctx.policy_source().NoteSourcePosition(
59+
element_id, GetMarkCodepointPosition(ctx, node.Mark()));
4660
}
4761
return element_id;
4862
}
@@ -62,9 +76,10 @@ std::optional<ValueString> YamlPolicyParser::GetValueString(
6276
}
6377

6478
if (!node.Mark().is_null() && ctx.policy_source().content() != nullptr) {
79+
SourcePosition codepoint_pos = GetMarkCodepointPosition(ctx, node.Mark());
6580
policy_internal::YamlStringElement element =
6681
policy_internal::ScanYamlStringElement(
67-
ctx.policy_source().content()->content(), node.Mark().pos,
82+
ctx.policy_source().content()->content(), codepoint_pos,
6883
node.as<std::string>());
6984

7085
ctx.policy_source().NoteSourcePosition(id, element.starting_position);
@@ -87,14 +102,40 @@ absl::Status YamlPolicyParser::ParsePolicy(CelPolicyParseContext& ctx) const {
87102
return absl::OkStatus();
88103
}
89104

90-
ctx.policy().set_description(ValueString(-1, source->description()));
105+
// TODO(b/542282964): Fold this mapping into cel::Source decoding happens
106+
// once.
91107
std::string text = source->content().ToString();
108+
std::vector<SourcePosition> mapping;
109+
mapping.resize(text.size() + 1, 0);
110+
size_t byte_offset = 0;
111+
SourcePosition codepoint = 0;
112+
absl::string_view view = text;
113+
while (!view.empty()) {
114+
auto [code_point, code_units] = cel::internal::Utf8Decode(view);
115+
if (code_units == 0) break;
116+
for (size_t i = 0; i < code_units; ++i) {
117+
if (byte_offset + i < mapping.size()) {
118+
mapping[byte_offset + i] = codepoint;
119+
}
120+
}
121+
byte_offset += code_units;
122+
view.remove_prefix(code_units);
123+
codepoint++;
124+
}
125+
for (size_t i = byte_offset; i < mapping.size(); ++i) {
126+
mapping[i] = codepoint;
127+
}
128+
129+
ctx.set_byte_to_codepoint_mapping(std::move(mapping));
130+
131+
ctx.policy().set_description(ValueString(-1, source->description()));
92132
YAML::Node node;
93133
try {
94134
node = YAML::Load(text);
95135
} catch (YAML::Exception& e) {
96136
if (!e.mark.is_null()) {
97-
ctx.policy_source().NoteSourcePosition(0, e.mark.pos);
137+
ctx.policy_source().NoteSourcePosition(
138+
0, GetMarkCodepointPosition(ctx, e.mark));
98139
}
99140
ctx.ReportError(0, "Invalid CEL policy YAML syntax");
100141
return absl::OkStatus();

policy/yaml_policy_parser_test.cc

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,12 @@ TEST_P(YamlPolicyParseErrorTest, YamlSyntaxError) {
150150

151151
std::vector<ParseTestCase> GetParseTestCases() {
152152
return {
153+
ParseTestCase{
154+
.yaml = "name: \"unclosed",
155+
.expected_error = "1:16: Invalid CEL policy YAML syntax\n"
156+
" | name: \"unclosed\n"
157+
" | ...............^",
158+
},
153159
ParseTestCase{
154160
.yaml = R"yaml( ? [ John, Doe ]: age: 30 )yaml",
155161
.expected_error = "1:22: Invalid CEL policy YAML syntax\n"
@@ -198,6 +204,17 @@ std::vector<ParseTestCase> GetParseTestCases() {
198204
" | - cel.expr.conformance\n"
199205
" | ....................^",
200206
},
207+
ParseTestCase{
208+
.yaml = R"yaml(
209+
# Comment with multi-byte char: €
210+
imports:
211+
- name:
212+
- cel.expr.conformance
213+
)yaml",
214+
.expected_error = "5:21: Import name is not a string\n"
215+
" | - cel.expr.conformance\n"
216+
" | ....................^",
217+
},
201218
ParseTestCase{
202219
.yaml = R"yaml(
203220
rule: do something

0 commit comments

Comments
 (0)