cedar_policy_core/parser/
text_to_cst.rs

1/*
2 * Copyright 2022-2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      https://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17//! This module contains step one of the parser for the Cedar language.
18//! It converts text to a CST
19
20lalrpop_mod!(
21    #[allow(warnings, unused)]
22    pub grammar,
23    "/src/parser/grammar.rs"
24);
25
26use super::*;
27
28// "Global" data per-thread, initialized at first use
29thread_local!(static POLICIES_PARSER: grammar::PoliciesParser = grammar::PoliciesParser::new());
30thread_local!(static POLICY_PARSER: grammar::PolicyParser = grammar::PolicyParser::new());
31thread_local!(static EXPR_PARSER: grammar::ExprParser = grammar::ExprParser::new());
32thread_local!(static REF_PARSER: grammar::RefParser = grammar::RefParser::new());
33thread_local!(static PRIMARY_PARSER: grammar::PrimaryParser = grammar::PrimaryParser::new());
34thread_local!(static NAME_PARSER: grammar::NameParser = grammar::NameParser::new());
35thread_local!(static IDENT_PARSER: grammar::IdentParser = grammar::IdentParser::new());
36
37// This macro calls a generated parser (specified by the argument to the macro),
38// collects errors that could be generated multiple ways, and returns a single
39// Result where the error type is `err::ParseError`.
40macro_rules! parse_collect_errors {
41    ($parser:ident, $text:ident) => {
42        $parser.with(|parser| {
43            // call generated parser
44            let mut errs = Vec::new();
45            let result = parser.parse(&mut errs, $text);
46
47            // convert both parser error types to the local error type
48            let mut errors: Vec<err::ParseError> =
49                errs.into_iter().map(err::ParseError::from).collect();
50            let result = result.map_err(err::ParseError::from);
51
52            // decide to return errors or success
53            match result {
54                Ok(parsed) => {
55                    if !errors.is_empty() {
56                        // In this case, `parsed` contains internal errors but could
57                        // still be used. However, for now, we do not use `parsed` --
58                        // we just return the errors from this parsing phase and stop.
59                        Err(errors)
60                    } else {
61                        Ok(parsed)
62                    }
63                }
64                Err(e) => {
65                    errors.push(e);
66                    Err(errors)
67                }
68            }
69        })
70    };
71}
72
73/// Create CST for multiple policies from text
74pub fn parse_policies(
75    text: &str,
76) -> Result<node::ASTNode<Option<cst::Policies>>, Vec<err::ParseError>> {
77    parse_collect_errors!(POLICIES_PARSER, text)
78}
79
80/// Create CST for one policy statement from text
81pub fn parse_policy(
82    text: &str,
83) -> Result<node::ASTNode<Option<cst::Policy>>, Vec<err::ParseError>> {
84    parse_collect_errors!(POLICY_PARSER, text)
85}
86
87/// Create CST for one Expression from text
88pub fn parse_expr(text: &str) -> Result<node::ASTNode<Option<cst::Expr>>, Vec<err::ParseError>> {
89    parse_collect_errors!(EXPR_PARSER, text)
90}
91
92/// Create CST for one Entity Ref (i.e., UID) from text
93pub fn parse_ref(text: &str) -> Result<node::ASTNode<Option<cst::Ref>>, Vec<err::ParseError>> {
94    parse_collect_errors!(REF_PARSER, text)
95}
96
97/// Create CST for one Primary value from text
98pub fn parse_primary(
99    text: &str,
100) -> Result<node::ASTNode<Option<cst::Primary>>, Vec<err::ParseError>> {
101    parse_collect_errors!(PRIMARY_PARSER, text)
102}
103
104/// Parse text as a Name, or fail if it does not parse as a Name
105pub fn parse_name(text: &str) -> Result<node::ASTNode<Option<cst::Name>>, Vec<err::ParseError>> {
106    parse_collect_errors!(NAME_PARSER, text)
107}
108
109/// Parse text as an identifier, or fail if it does not parse as an identifier
110pub fn parse_ident(text: &str) -> Result<node::ASTNode<Option<cst::Ident>>, Vec<err::ParseError>> {
111    parse_collect_errors!(IDENT_PARSER, text)
112}
113
114#[cfg(test)]
115mod tests {
116    use super::*;
117
118    #[test]
119    fn expr1() {
120        assert!(parse_expr(
121            r#"
122        1
123        "#
124        )
125        .expect("parser fail")
126        .node
127        .is_some());
128    }
129
130    #[test]
131    fn expr2() {
132        assert!(parse_expr(
133            r#"
134        "string"
135        "#
136        )
137        .expect("parser fail")
138        .node
139        .is_some());
140    }
141
142    #[test]
143    fn expr3() {
144        assert!(parse_expr(
145            r#"
146        "string".foo == !7
147        "#
148        )
149        .expect("parser fail")
150        .node
151        .is_some());
152    }
153
154    #[test]
155    fn expr4() {
156        let result = parse_expr(
157            r#"
158        5 < 3 || -7 == 2 && 3 >= 6
159        "#,
160        )
161        .expect("parser fail")
162        .node;
163        assert!(result.is_some());
164    }
165
166    #[test]
167    fn expr5() {
168        assert!(parse_expr(
169            r#"
170        if 7 then 6 > 5 else !5 || "thursday"
171        "#
172        )
173        .expect("parser fail")
174        .node
175        .is_some());
176    }
177
178    #[test]
179    fn expr6() {
180        let result = parse_expr(
181            r#"
182        if 7 then 6 > 5 else !5 || "thursday" && ((8) >= "fish")
183        "#,
184        )
185        .expect("parser fail")
186        .node;
187        assert!(result.is_some());
188    }
189
190    #[test]
191    fn expr_overflow() {
192        // an error is not a crash!
193        assert!(parse_expr(
194            r#"
195            principal == -5555555555555555555555
196        "#
197        )
198        .is_err());
199        assert!(parse_expr(
200            r#"
201            principal == 5555555555555555555555
202        "#
203        )
204        .is_err());
205    }
206
207    #[test]
208    fn variable1() {
209        let policy = parse_policy(
210            r#"
211                permit(principal, action, var:h in 1);
212            "#,
213        );
214        assert!(policy.is_ok());
215    }
216
217    #[test]
218    fn variable2() {
219        let policy = parse_policy(
220            r#"
221                permit(principal, action, more in 2);
222            "#,
223        );
224        assert!(policy.is_ok());
225    }
226
227    #[test]
228    fn variable3() {
229        let policy = parse_policy(
230            r#"
231                permit(principal, action:a_name, resource);
232            "#,
233        );
234        assert!(policy.is_ok());
235    }
236
237    #[test]
238    fn variable4() {
239        let policy = parse_policy(
240            r#"
241                permit(principalorsomeotherident, action, resource);
242            "#,
243        );
244        assert!(policy.is_ok());
245    }
246
247    #[test]
248    fn variable6() {
249        let policy = parse_policy(
250            r#"
251                permit(var : in 6, action, resource);
252            "#,
253        );
254        assert!(policy.is_err());
255    }
256
257    #[test]
258    fn member1() {
259        let policy = parse_policy(
260            r#"
261                permit(principal, action, resource)
262                when{
263                    2._field // oh, look, comments!
264                };
265            "#,
266        );
267        assert!(policy.is_ok());
268    }
269
270    #[test]
271    fn member2() {
272        let policy = parse_policy(
273            r#"
274                permit(principal, action, resource)
275                when{
276                    "first".some_ident()
277                };
278            "#,
279        );
280        assert!(policy.is_ok());
281    }
282
283    #[test]
284    fn member3() {
285        let policy = parse_policy(
286            r#"
287                permit(principal, action, resource)
288                when{
289                    [2,3,4].foo[2]
290                };
291            "#,
292        );
293        assert!(policy.is_ok());
294    }
295
296    #[test]
297    fn member4() {
298        let policy = parse_policy(
299            r#"
300                permit(principal, action, resource)
301                when{
302                    {3<-4:"what?","ok then":-5>4}
303                };
304            "#,
305        );
306        assert!(policy.is_ok());
307    }
308
309    #[test]
310    fn member5() {
311        let policy = parse_policy(
312            r#"
313                permit(principal, action, resource)
314                when{
315                    [3<4,"ok then",17,("none")]
316                };
317            "#,
318        );
319        assert!(policy.is_ok());
320    }
321
322    #[test]
323    fn member6() {
324        let policy = parse_policy(
325            r#"
326                permit(principal, action, resource)
327                when{
328                    one.two
329                };
330            "#,
331        );
332        assert!(policy.is_ok());
333    }
334
335    #[test]
336    #[should_panic] // we no longer support structs
337    fn member7() {
338        let policy = parse_policy(
339            r#"
340                permit(principal, action, resource)
341                when{
342                    one{num:true,trivia:"first!"}
343                };
344            "#,
345        );
346        assert!(policy.is_ok());
347    }
348
349    #[test]
350    fn member8() {
351        let policy = parse_policy(
352            r#"
353                permit(principal, action, resource)
354                when{
355                    {2:true,4:me}.with["pizza"]
356                };
357            "#,
358        );
359        assert!(policy.is_ok());
360    }
361
362    #[test]
363    fn member9() {
364        let policy = parse_policy(
365            r#"
366                permit(principal, action, resource)
367                when{
368                    AllRects({two:2,four:3+5/5})
369                };
370            "#,
371        );
372        assert!(policy.is_ok());
373    }
374
375    #[test]
376    fn ident1() {
377        let ident = parse_ident(
378            r#"
379                principal
380            "#,
381        );
382        assert!(ident.is_ok());
383    }
384
385    #[test]
386    fn ident2() {
387        let ident = parse_ident(
388            r#"
389                if
390            "#,
391        );
392        // specialized parser for idents has no limits
393        assert!(ident.is_ok());
394        let ident = parse_ident(
395            r#"
396                false
397            "#,
398        );
399        // specialized parser for idents has no limits
400        assert!(ident.is_ok());
401    }
402
403    #[test]
404    fn ident3() {
405        let ident = parse_expr(
406            r#"
407                if
408            "#,
409        );
410        // Not a valid variable name (but then, only the 4 special vars make it to AST)
411        assert!(ident.is_err());
412        let name = parse_expr(
413            r#"
414                if::then::else
415            "#,
416        );
417        // is a valid path component
418        assert!(name.is_ok());
419        let names = parse_expr(
420            r#"
421                if::true::then::false::else::true
422            "#,
423        );
424        // is a valid path component
425        assert!(names.is_ok());
426    }
427
428    #[test]
429    fn ident4() {
430        let ident = parse_expr(
431            r#"
432                true(true)
433            "#,
434        );
435        // can be used as a function
436        assert!(ident.is_ok());
437        let ident = parse_expr(
438            r#"
439                if(true)
440            "#,
441        );
442        // but this on cannot because of parse confusion
443        assert!(ident.is_err());
444    }
445
446    #[test]
447    fn ident5() {
448        let ident = parse_expr(
449            r#"
450                {true : false}
451            "#,
452        );
453        // can be used as record init, but this may not parse to AST
454        // because true is a value, not an identifier
455        assert!(ident.is_ok());
456        let ident = parse_expr(
457            r#"
458                { if : true }
459            "#,
460        );
461        // special case allows this one to be an identifier
462        assert!(ident.is_ok());
463    }
464
465    #[test]
466    fn ident6() {
467        let ident = parse_expr(
468            r#"
469                {true : false} has false
470            "#,
471        );
472        // can be used as record init, but this may not parse to AST
473        // because true is a value, not an identifier
474        assert!(ident.is_ok());
475        let ident = parse_expr(
476            r#"
477                { if : true } has if
478            "#,
479        );
480        // special case allows this one to be an identifier
481        assert!(ident.is_ok());
482    }
483
484    #[test]
485    fn comments_has() {
486        // single line comments (`// ...`) are valid anywhere
487        let policy_text = r#"
488                permit(principal, action,resource)
489                when{ principal //comment p
490                has //comment has
491                age //comment
492                };
493            "#;
494        let policy = parse_policy(policy_text);
495        assert!(policy.is_ok());
496    }
497
498    #[test]
499    fn comments_like() {
500        // single line comments (`// ...`) are valid anywhere
501        let policy_text = r#"
502                permit(principal, action,resource)
503                when{ principal //comment p
504                like //comment like
505
506                age //comment
507                };
508            "#;
509        let policy = parse_policy(policy_text);
510        assert!(policy.is_ok());
511    }
512
513    #[test]
514    fn comments_and() {
515        // single line comments (`// ...`) are valid anywhere
516        let policy_text = r#"
517                permit(principal, action,resource)
518                when{ 1 //comment p
519                &&  //comment &&
520                    //comment &&
521                "hello" //comment
522                };
523            "#;
524        let policy = parse_policy(policy_text);
525        assert!(policy.is_ok());
526    }
527
528    #[test]
529    fn comments_or() {
530        // single line comments (`// ...`) are valid anywhere
531        let policy_text = r#"
532                permit(principal, action,resource)
533                when{ 1 //comment 1
534                      //  comment 1
535                ||  //comment ||
536                    //comments ||
537                "hello" //comment
538                        //comment hello
539                };
540            "#;
541        let policy = parse_policy(policy_text);
542        assert!(policy.is_ok());
543    }
544
545    #[test]
546    fn comments_add() {
547        // single line comments (`// ...`) are valid anywhere
548        let policy_text = r#"
549                permit(principal, action,resource)
550                when{ 1 //comment 1
551                        //comment 1_2
552                + //comment +
553                   //comment +
554                 2 //comment 2
555                    //comment 2
556                };
557            "#;
558        let policy = parse_policy(policy_text);
559        assert!(policy.is_ok());
560    }
561
562    #[test]
563    fn comments_paren() {
564        // single line comments (`// ...`) are valid anywhere
565        let policy_text = r#"
566                permit(principal, action,resource)
567                when{
568                ( //comment 1
569                    ( //comment 2
570                 1
571                    ) //comment 3
572                ) //comment 4
573                };
574            "#;
575        let policy = parse_policy(policy_text);
576        assert!(policy.is_ok());
577    }
578
579    #[test]
580    fn comments_set() {
581        // single line comments (`// ...`) are valid anywhere
582        let policy_text = r#"
583                permit(principal, action,resource)
584                when{
585                [ // comment 1
586                "hello" //comment 2
587                , // comment 3
588                 // comment 3-2
589                1 //comment 4
590                    //comment 5
591                ]  //comment 5-0
592
593                .  //comment 5-1
594
595                contains //comment 5-2
596
597                ( //comment 6
598
599                "a"  //comment 7
600
601                ) //comment 20
602                };
603            "#;
604        let policy = parse_policy(policy_text);
605        assert!(policy.is_ok());
606    }
607
608    #[test]
609    fn comments_if() {
610        // single line comments (`// ...`) are valid anywhere
611        let policy_text = r#"
612                permit(principal, action,resource)
613                when{
614                ( //comment open outer
615                ( //comment open inner
616                 if //comment if
617                  1             //comment
618                  < //comment <
619                  2 //commment 2
620                  then // comment then
621                  "hello" //comment hello
622                else  //comment else
623                    1 //comment 1
624                    ) //comment close inner
625                    ) //comment close outer
626                };
627            "#;
628        let policy = parse_policy(policy_text);
629        assert!(policy.is_ok());
630    }
631
632    #[test]
633    fn comments_member_access() {
634        // single line comments (`// ...`) are valid anywhere
635        let policy_text = r#"
636                permit(principal, action,resource)
637                when{ principal. //comment .
638                age // comment age
639                };
640            "#;
641        let policy = parse_policy(policy_text);
642        assert!(policy.is_ok());
643    }
644
645    #[test]
646    fn comments_principal() {
647        // single line comments (`// ...`) are valid anywhere
648        let policy_text = r#"
649                permit(principal //comment 1
650                 ==
651                  User::"alice" //comment 3
652                  ,  //comment 4
653                   action,resource);
654            "#;
655        let policy = parse_policy(policy_text);
656        assert!(policy.is_ok());
657    }
658
659    #[test]
660    fn comments_annotation() {
661        // single line comments (`// ...`) are valid anywhere
662        let policy_text = r#"
663        //comment policy
664        // comment policy 2
665        @anno("good annotation")  // comments after annotation 
666        // comments after annotation 2
667                permit(principal //comment 1
668                 ==
669                  User::"alice" //comment 3
670                  ,  //comment 4
671                   action,resource);
672            "#;
673        let policy = parse_policy(policy_text);
674        assert!(policy.is_ok());
675    }
676
677    #[test]
678    fn comments_policy() {
679        // single line comments (`// ...`) are valid anywhere
680        let policy_text = r#"
681                //comment policy 1
682                //comment policy 2
683                permit( //comment 3
684                   // comment 4
685                principal //comment principal
686                == //comment == 1
687                   //comment == 2
688                User::"alice" //comment alice
689                , //comment comma 1
690                            //comment comma 2
691                action //comment action 1
692                //comment action 2
693                , //comment comma action
694                resource // comment resource
695                )
696                //comment 5
697                //comment 6
698                ;
699            "#;
700        let policy = parse_policy(policy_text);
701        assert!(policy.is_ok());
702        //multi-line comments (`/* ... */`) are not allowed
703        let policy = parse_policy(
704            r#" /* multi-line
705            comment */
706                permit(principal, action, resource)
707                when{
708                    one.two
709                };
710            "#,
711        );
712        assert!(policy.is_err());
713        let expr = parse_expr(
714            r#"
715            1 /* multi-line
716            comment */d
717            "#,
718        );
719        assert!(expr.is_err());
720    }
721
722    #[test]
723    fn no_comments_policy() {
724        // single line comments (`// ...`) are valid anywhere
725        let policy_text = r#"
726               permit(
727                principal
728                ==
729                User::"alice"
730                ,
731                action
732
733                ,
734                resource
735                )
736                ;
737            "#;
738        let policy = parse_policy(policy_text);
739        assert!(policy.is_ok());
740    }
741
742    #[test]
743    fn no_comments_policy2() {
744        let policy_text = r#"permit (
745    principal == IAM::Principal::"arn:aws:iam::12345678901:user/Dave",
746    action == S3::Action::"GetAccountPublicAccessBlock",
747    resource == Account::"12345678901"
748    );"#;
749        let policy = parse_policy(policy_text);
750        assert!(policy.is_ok());
751    }
752
753    #[test]
754    fn no_comments_policy4() {
755        let policy_text = r#"
756    permit(principal,action,resource,context)
757    when {
758    context.contains(3,"four",five(6,7))
759};"#;
760        let policy = parse_policy(policy_text);
761        assert!(policy.is_ok());
762    }
763    #[test]
764    fn no_comments_policy5() {
765        let policy_text = r#"
766    permit (
767    principal,
768    action,
769    resource == Album::{uid: "772358b3-de11-42dc-8681-f0a32e34aab8",
770    displayName: "vacation_photos"}
771);"#;
772        let policy = parse_policy(policy_text);
773        assert!(policy.is_ok());
774    }
775
776    #[test]
777    fn policies1() {
778        let policy = parse_policy(
779            r#"
780                permit(principal:p,action:a,resource:r)when{w}unless{u}advice{"doit"};
781            "#,
782        );
783        assert!(policy.is_ok());
784    }
785
786    #[test]
787    fn policies2() {
788        let result = parse_policies(
789            r#"
790                permit(
791                    principal in Group::"jane_friends",  // Policy c1
792                    action in [PhotoOp::"view", PhotoOp::"comment"],
793                    resource in Album::"jane_trips",
794                    context:Group
795                );
796            "#,
797        );
798        assert!(result.is_ok());
799    }
800
801    #[test]
802    fn policies3() {
803        assert!(parse_policies(
804            r#"
805            forbid(principal, action, resource)           // Policy c2
806            when   { "private" in resource.tags }  // resource.tags is a set of strings
807            unless { resource in user.account };
808        "#
809        )
810        // check that all policy statements are successful
811        .expect("parse fail")
812        .node
813        .expect("no data")
814        .0
815        .iter()
816        .all(|p| p.node.is_some()));
817    }
818
819    #[test]
820    // repeat of prior test but with a typo
821    // typos are not caught by the cst parser
822    fn policies3p() {
823        assert!(parse_policies(
824            r#"
825            forbid(principality, action, resource)           // Policy c2
826            when   { "private" in resource.tags }  // resource.tags is a set of strings
827            unless { resource in user.account };
828        "#
829        )
830        // check that all policy statements are successful
831        .expect("parse fail")
832        .node
833        .expect("no data")
834        .0
835        .iter()
836        .all(|p| p.node.is_some()));
837    }
838
839    #[test]
840    fn policies4() {
841        let result = parse_policies(
842            r#"
843            permit(principal:p,action:a,resource:r)when{w}unless{u}advice{"doit"};
844
845            permit(principal in Group::"jane_friends",  // Policy c1
846            action in [PhotoOp::"view", PhotoOp::"comment"],
847            resource in Album::"jane_trips");
848
849            forbid(principal, action, resource)           // Policy c2
850            when   { "private" in resource.tags }  // resource.tags is a set of strings
851            unless { resource in user.account };
852        "#,
853        )
854        .expect("parse fail")
855        .node
856        .expect("no data");
857        assert!(result.0.iter().all(|p| p.node.is_some()));
858    }
859
860    #[test]
861    fn policies5() {
862        assert!(parse_policies(
863            r#"
864            permit (
865                principal == User::"alice",
866                action in PhotoflashRole::"viewer",
867                resource in Account::"jane"
868            )
869            advice {
870                "{\"type\":\"PhotoFilterInstruction\", \"anonymize\":true}"
871            };
872        "#
873        )
874        .expect("parse fail")
875        .node
876        .expect("no data")
877        .0
878        .into_iter()
879        .all(|p| p.node.is_some()));
880    }
881
882    #[test]
883    fn policies6() {
884        // test that an error doesn't stop the parser
885        let policies = POLICIES_PARSER.with(|p| {
886            p.parse(
887                &mut Vec::new(),
888                r#"
889                // use a number to error
890                3(principal:p,action:a,resource:r)when{w}unless{u}advice{"doit"};
891                permit(principal:p,action:a,resource:r)when{w}unless{u}advice{"doit"};
892                permit(principal:p,action:a,resource:r)when{w}unless{u}advice{"doit"};
893            "#,
894            )
895            .expect("parser error")
896            .node
897            .expect("no data")
898        });
899        let success = policies
900            .0
901            .into_iter()
902            .filter_map(|p| p.node)
903            .collect::<Vec<_>>();
904        assert!(success.len() == 2);
905    }
906
907    #[test]
908    fn policy_annotations() {
909        let policies = parse_policies(
910            r#"
911            @anno("good annotation") permit (principal, action, resource);
912            @anno1("good")@anno2("annotation") permit (principal, action, resource);
913            @long6wordphraseisident007("good annotation") permit (principal, action, resource);
914            @   spacy  (  "  good  annotation  "  )   permit (principal, action, resource);
915        "#,
916        )
917        .expect("parse fail")
918        .node
919        .expect("no data");
920        let success = policies
921            .0
922            .into_iter()
923            .filter_map(|p| p.node)
924            .collect::<Vec<_>>();
925        assert!(success.len() == 4);
926
927        let _policy = parse_policy(
928            r#"
929            @bad-annotation("bad") permit (principal, action, resource);
930        "#,
931        )
932        .expect_err("should fail on dash");
933
934        let _policy = parse_policy(
935            r#"
936            @bad_annotation("bad","annotation") permit (principal, action, resource);
937        "#,
938        )
939        .expect_err("should fail on list");
940
941        let _policy = parse_policy(
942            r#"
943            @bad_annotation(bad_annotation) permit (principal, action, resource);
944        "#,
945        )
946        .expect_err("should fail without string");
947
948        let _policy = parse_policy(
949            r#"
950            permit (@comment("your name here") principal, action, resource);
951        "#,
952        )
953        .expect_err("should fail with poor placement");
954    }
955
956    #[test]
957    fn parse_idempotent() {
958        let many_policies =
959            std::fs::read_to_string("src/parser/testfiles/policies.txt").expect("missing file");
960        let cst1 = parse_policies(&many_policies)
961            .expect("parse fail")
962            .node
963            .expect("no data");
964        let revert = format!("{}", cst1);
965        //println!("{:#}", cst1);
966        let cst2 = parse_policies(&revert)
967            .expect("parse fail")
968            .node
969            .expect("no data");
970        assert!(cst1 == cst2);
971    }
972}