sieve/compiler/grammar/
instruction.rs

1/*
2 * Copyright (c) 2020-2023, Stalwart Labs Ltd.
3 *
4 * This file is part of the Stalwart Sieve Interpreter.
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Affero General Public License as
8 * published by the Free Software Foundation, either version 3 of
9 * the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Affero General Public License for more details.
15 * in the LICENSE file at the top-level directory of this distribution.
16 * You should have received a copy of the GNU Affero General Public License
17 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18 *
19 * You can be released from the requirements of the AGPLv3 license by
20 * purchasing a commercial license. Please contact licensing@stalw.art
21 * for more details.
22*/
23
24use ahash::{AHashMap, AHashSet};
25use serde::{Deserialize, Serialize};
26
27use crate::{
28    compiler::{
29        grammar::{test::Test, MatchType},
30        lexer::{tokenizer::Tokenizer, word::Word, Token},
31        CompileError, ErrorType, Value, VariableType,
32    },
33    Compiler, Sieve,
34};
35
36use super::{
37    actions::{
38        action_convert::Convert,
39        action_editheader::{AddHeader, DeleteHeader},
40        action_fileinto::FileInto,
41        action_flags::EditFlags,
42        action_include::Include,
43        action_keep::Keep,
44        action_mime::{Enclose, ExtractText, ForEveryPart, Replace},
45        action_notify::Notify,
46        action_redirect::Redirect,
47        action_reject::Reject,
48        action_set::{Let, Set},
49        action_vacation::Vacation,
50    },
51    expr::Expression,
52    Capability, Clear, Invalid, While,
53};
54
55use super::tests::test_ihave::Error;
56
57#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
58pub enum Instruction {
59    Require(Vec<Capability>),
60    Keep(Keep),
61    FileInto(FileInto),
62    Redirect(Redirect),
63    Discard,
64    Stop,
65    Invalid(Invalid),
66    Test(Test),
67    Jmp(usize),
68    Jz(usize),
69    Jnz(usize),
70
71    // RFC 5703
72    ForEveryPartPush,
73    ForEveryPart(ForEveryPart),
74    ForEveryPartPop(usize),
75    Replace(Replace),
76    Enclose(Enclose),
77    ExtractText(ExtractText),
78
79    // RFC 6558
80    Convert(Convert),
81
82    // RFC 5293
83    AddHeader(AddHeader),
84    DeleteHeader(DeleteHeader),
85
86    // RFC 5229
87    Set(Set),
88    Clear(Clear),
89
90    // RFC 5435
91    Notify(Notify),
92
93    // RFC 5429
94    Reject(Reject),
95
96    // RFC 5230
97    Vacation(Vacation),
98
99    // RFC 5463
100    Error(Error),
101
102    // RFC 5232
103    EditFlags(EditFlags),
104
105    // RFC 6609
106    Include(Include),
107    Return,
108
109    // For every line extension
110    While(While),
111
112    // Expression extension
113    Eval(Vec<Expression>),
114    Let(Let),
115
116    // Test only
117    #[cfg(test)]
118    TestCmd(Vec<Value>),
119}
120
121pub(crate) const MAX_PARAMS: usize = 11;
122
123#[derive(Debug)]
124pub(crate) struct Block {
125    pub(crate) btype: Word,
126    pub(crate) label: Option<String>,
127    pub(crate) line_num: usize,
128    pub(crate) line_pos: usize,
129    pub(crate) last_block_start: usize,
130    pub(crate) if_jmps: Vec<usize>,
131    pub(crate) break_jmps: Vec<usize>,
132    pub(crate) match_test_pos: Vec<usize>,
133    pub(crate) match_test_vars: u64,
134    pub(crate) vars_local: AHashMap<String, usize>,
135    pub(crate) capabilities: AHashSet<Capability>,
136    pub(crate) require_pos: usize,
137}
138
139pub(crate) struct CompilerState<'x> {
140    pub(crate) compiler: &'x Compiler,
141    pub(crate) tokens: Tokenizer<'x>,
142    pub(crate) instructions: Vec<Instruction>,
143    pub(crate) block_stack: Vec<Block>,
144    pub(crate) block: Block,
145    pub(crate) last_block_type: Word,
146    pub(crate) vars_global: AHashSet<String>,
147    pub(crate) vars_num: usize,
148    pub(crate) vars_num_max: usize,
149    pub(crate) vars_match_max: usize,
150    pub(crate) vars_local: usize,
151    pub(crate) param_check: [bool; MAX_PARAMS],
152    pub(crate) includes_num: usize,
153}
154
155impl Compiler {
156    pub fn compile(&self, script: &[u8]) -> Result<Sieve, CompileError> {
157        if script.len() > self.max_script_size {
158            return Err(CompileError {
159                line_num: 0,
160                line_pos: 0,
161                error_type: ErrorType::ScriptTooLong,
162            });
163        }
164
165        let mut state = CompilerState {
166            compiler: self,
167            tokens: Tokenizer::new(self, script),
168            instructions: Vec::new(),
169            block_stack: Vec::new(),
170            block: Block::new(Word::Not),
171            last_block_type: Word::Not,
172            vars_global: AHashSet::new(),
173            vars_num: 0,
174            vars_num_max: 0,
175            vars_match_max: 0,
176            vars_local: 0,
177            param_check: [false; MAX_PARAMS],
178            includes_num: 0,
179        };
180
181        while let Some(token_info) = state.tokens.next() {
182            let token_info = token_info?;
183            state.reset_param_check();
184
185            match token_info.token {
186                Token::Identifier(instruction) => {
187                    let mut is_new_block = None;
188
189                    match instruction {
190                        Word::Require => {
191                            state.parse_require()?;
192                        }
193                        Word::If => {
194                            state.parse_test()?;
195                            state.block.if_jmps.clear();
196                            is_new_block = Block::new(Word::If).into();
197                        }
198                        Word::ElsIf => {
199                            if let Word::If | Word::ElsIf = &state.last_block_type {
200                                state.parse_test()?;
201                                is_new_block = Block::new(Word::ElsIf).into();
202                            } else {
203                                return Err(token_info.expected("'if' before 'elsif'"));
204                            }
205                        }
206                        Word::Else => {
207                            if let Word::If | Word::ElsIf = &state.last_block_type {
208                                is_new_block = Block::new(Word::Else).into();
209                            } else {
210                                return Err(token_info.expected("'if' or 'elsif' before 'else'"));
211                            }
212                        }
213                        Word::Keep => {
214                            state.parse_keep()?;
215                        }
216                        Word::FileInto => {
217                            state.validate_argument(
218                                0,
219                                Capability::FileInto.into(),
220                                token_info.line_num,
221                                token_info.line_pos,
222                            )?;
223                            state.parse_fileinto()?;
224                        }
225                        Word::Redirect => {
226                            state.parse_redirect()?;
227                        }
228                        Word::Discard => {
229                            state.instructions.push(Instruction::Discard);
230                        }
231                        Word::Stop => {
232                            state.instructions.push(Instruction::Stop);
233                        }
234
235                        // RFC 5703
236                        Word::ForEveryPart => {
237                            state.validate_argument(
238                                0,
239                                Capability::ForEveryPart.into(),
240                                token_info.line_num,
241                                token_info.line_pos,
242                            )?;
243
244                            if state
245                                .block_stack
246                                .iter()
247                                .filter(|b| matches!(&b.btype, Word::ForEveryPart))
248                                .count()
249                                == self.max_nested_foreverypart
250                            {
251                                return Err(
252                                    token_info.custom(ErrorType::TooManyNestedForEveryParts)
253                                );
254                            }
255
256                            is_new_block = if let Some(Ok(Token::Tag(Word::Name))) =
257                                state.tokens.peek().map(|r| r.map(|t| &t.token))
258                            {
259                                let tag = state.tokens.next().unwrap().unwrap();
260                                let label = state.tokens.expect_static_string()?;
261                                for block in &state.block_stack {
262                                    if block.label.as_ref().map_or(false, |n| n.eq(&label)) {
263                                        return Err(
264                                            tag.custom(ErrorType::LabelAlreadyDefined(label))
265                                        );
266                                    }
267                                }
268                                Block::new(Word::ForEveryPart).with_label(label)
269                            } else {
270                                Block::new(Word::ForEveryPart)
271                            }
272                            .into();
273
274                            state.instructions.push(Instruction::ForEveryPartPush);
275                            state
276                                .instructions
277                                .push(Instruction::ForEveryPart(ForEveryPart {
278                                    jz_pos: usize::MAX,
279                                }));
280                        }
281                        Word::Break => {
282                            if let Some(Ok(Token::Tag(Word::Name))) =
283                                state.tokens.peek().map(|r| r.map(|t| &t.token))
284                            {
285                                state.validate_argument(
286                                    0,
287                                    Capability::ForEveryPart.into(),
288                                    token_info.line_num,
289                                    token_info.line_pos,
290                                )?;
291
292                                let tag = state.tokens.next().unwrap().unwrap();
293                                let label = state.tokens.expect_static_string()?;
294                                let mut label_found = false;
295                                let mut num_pops = 0;
296
297                                for block in [&mut state.block]
298                                    .into_iter()
299                                    .chain(state.block_stack.iter_mut().rev())
300                                {
301                                    if let Word::ForEveryPart = &block.btype {
302                                        num_pops += 1;
303                                        if block.label.as_ref().map_or(false, |n| n.eq(&label)) {
304                                            state
305                                                .instructions
306                                                .push(Instruction::ForEveryPartPop(num_pops));
307                                            block.break_jmps.push(state.instructions.len());
308                                            label_found = true;
309                                            break;
310                                        }
311                                    }
312                                }
313
314                                if !label_found {
315                                    return Err(tag.custom(ErrorType::LabelUndefined(label)));
316                                }
317                            } else {
318                                let mut block_found = None;
319                                if matches!(&state.block.btype, Word::ForEveryPart | Word::While) {
320                                    block_found = Some(&mut state.block);
321                                } else {
322                                    for block in state.block_stack.iter_mut().rev() {
323                                        if matches!(&block.btype, Word::ForEveryPart | Word::While)
324                                        {
325                                            block_found = Some(block);
326                                            break;
327                                        }
328                                    }
329                                }
330
331                                let block = block_found.ok_or_else(|| {
332                                    token_info.custom(ErrorType::BreakOutsideLoop)
333                                })?;
334                                if matches!(block.btype, Word::ForEveryPart) {
335                                    state.instructions.push(Instruction::ForEveryPartPop(1));
336                                }
337
338                                block.break_jmps.push(state.instructions.len());
339                            }
340
341                            state.instructions.push(Instruction::Jmp(usize::MAX));
342                        }
343                        Word::Replace => {
344                            state.validate_argument(
345                                0,
346                                Capability::Replace.into(),
347                                token_info.line_num,
348                                token_info.line_pos,
349                            )?;
350                            state.parse_replace()?;
351                        }
352                        Word::Enclose => {
353                            state.validate_argument(
354                                0,
355                                Capability::Enclose.into(),
356                                token_info.line_num,
357                                token_info.line_pos,
358                            )?;
359                            state.parse_enclose()?;
360                        }
361                        Word::ExtractText => {
362                            state.validate_argument(
363                                0,
364                                Capability::ExtractText.into(),
365                                token_info.line_num,
366                                token_info.line_pos,
367                            )?;
368                            state.parse_extracttext()?;
369                        }
370
371                        // RFC 6558
372                        Word::Convert => {
373                            state.validate_argument(
374                                0,
375                                Capability::Convert.into(),
376                                token_info.line_num,
377                                token_info.line_pos,
378                            )?;
379                            state.parse_convert()?;
380                        }
381
382                        // RFC 5293
383                        Word::AddHeader => {
384                            state.validate_argument(
385                                0,
386                                Capability::EditHeader.into(),
387                                token_info.line_num,
388                                token_info.line_pos,
389                            )?;
390                            state.parse_addheader()?;
391                        }
392                        Word::DeleteHeader => {
393                            state.validate_argument(
394                                0,
395                                Capability::EditHeader.into(),
396                                token_info.line_num,
397                                token_info.line_pos,
398                            )?;
399                            state.parse_deleteheader()?;
400                        }
401
402                        // RFC 5229
403                        Word::Set => {
404                            state.validate_argument(
405                                0,
406                                Capability::Variables.into(),
407                                token_info.line_num,
408                                token_info.line_pos,
409                            )?;
410                            state.parse_set()?;
411                        }
412
413                        // RFC 5435
414                        Word::Notify => {
415                            state.validate_argument(
416                                0,
417                                Capability::Enotify.into(),
418                                token_info.line_num,
419                                token_info.line_pos,
420                            )?;
421                            state.parse_notify()?;
422                        }
423
424                        // RFC 5429
425                        Word::Reject => {
426                            state.validate_argument(
427                                0,
428                                Capability::Reject.into(),
429                                token_info.line_num,
430                                token_info.line_pos,
431                            )?;
432                            state.parse_reject(false)?;
433                        }
434                        Word::Ereject => {
435                            state.validate_argument(
436                                0,
437                                Capability::Ereject.into(),
438                                token_info.line_num,
439                                token_info.line_pos,
440                            )?;
441                            state.parse_reject(true)?;
442                        }
443
444                        // RFC 5230
445                        Word::Vacation => {
446                            state.validate_argument(
447                                0,
448                                Capability::Vacation.into(),
449                                token_info.line_num,
450                                token_info.line_pos,
451                            )?;
452                            state.parse_vacation()?;
453                        }
454
455                        // RFC 5463
456                        Word::Error => {
457                            state.validate_argument(
458                                0,
459                                Capability::Ihave.into(),
460                                token_info.line_num,
461                                token_info.line_pos,
462                            )?;
463                            state.parse_error()?;
464                        }
465
466                        // RFC 5232
467                        Word::SetFlag | Word::AddFlag | Word::RemoveFlag => {
468                            state.validate_argument(
469                                0,
470                                Capability::Imap4Flags.into(),
471                                token_info.line_num,
472                                token_info.line_pos,
473                            )?;
474                            state.parse_flag_action(instruction)?;
475                        }
476
477                        // RFC 6609
478                        Word::Include => {
479                            if state.includes_num < self.max_includes {
480                                state.validate_argument(
481                                    0,
482                                    Capability::Include.into(),
483                                    token_info.line_num,
484                                    token_info.line_pos,
485                                )?;
486                                state.parse_include()?;
487                                state.includes_num += 1;
488                            } else {
489                                return Err(token_info.custom(ErrorType::TooManyIncludes));
490                            }
491                        }
492                        Word::Return => {
493                            state.validate_argument(
494                                0,
495                                Capability::Include.into(),
496                                token_info.line_num,
497                                token_info.line_pos,
498                            )?;
499                            let mut num_pops = 0;
500
501                            for block in [&state.block]
502                                .into_iter()
503                                .chain(state.block_stack.iter().rev())
504                            {
505                                if let Word::ForEveryPart = &block.btype {
506                                    num_pops += 1;
507                                }
508                            }
509
510                            if num_pops > 0 {
511                                state
512                                    .instructions
513                                    .push(Instruction::ForEveryPartPop(num_pops));
514                            }
515
516                            state.instructions.push(Instruction::Return);
517                        }
518                        Word::Global => {
519                            state.validate_argument(
520                                0,
521                                Capability::Include.into(),
522                                token_info.line_num,
523                                token_info.line_pos,
524                            )?;
525                            state.validate_argument(
526                                0,
527                                Capability::Variables.into(),
528                                token_info.line_num,
529                                token_info.line_pos,
530                            )?;
531                            for global in state.parse_static_strings()? {
532                                if !state.is_var_local(&global) {
533                                    if global.len() < self.max_variable_name_size {
534                                        state.register_global_var(&global);
535                                    } else {
536                                        return Err(state
537                                            .tokens
538                                            .unwrap_next()?
539                                            .custom(ErrorType::VariableTooLong));
540                                    }
541                                } else {
542                                    return Err(state
543                                        .tokens
544                                        .unwrap_next()?
545                                        .custom(ErrorType::VariableIsLocal(global)));
546                                }
547                            }
548                        }
549
550                        // Expressions extension
551                        Word::Let => {
552                            state.validate_argument(
553                                0,
554                                Capability::Expressions.into(),
555                                token_info.line_num,
556                                token_info.line_pos,
557                            )?;
558                            state.parse_let()?;
559                        }
560                        Word::Eval => {
561                            state.validate_argument(
562                                0,
563                                Capability::Expressions.into(),
564                                token_info.line_num,
565                                token_info.line_pos,
566                            )?;
567                            let expr = state.parse_expr()?;
568                            state.instructions.push(Instruction::Eval(expr));
569                        }
570
571                        // While extension
572                        Word::While => {
573                            state.validate_argument(
574                                0,
575                                Capability::While.into(),
576                                token_info.line_num,
577                                token_info.line_pos,
578                            )?;
579
580                            is_new_block = Block::new(Word::While).into();
581
582                            let expr = state.parse_expr()?;
583                            state.instructions.push(Instruction::While(While {
584                                expr,
585                                jz_pos: usize::MAX,
586                            }));
587                        }
588                        Word::Continue => {
589                            state.validate_argument(
590                                0,
591                                Capability::While.into(),
592                                token_info.line_num,
593                                token_info.line_pos,
594                            )?;
595                            let mut found_while = 0;
596                            for block in [&state.block]
597                                .into_iter()
598                                .chain(state.block_stack.iter().rev())
599                            {
600                                if let Word::While = &block.btype {
601                                    found_while += 1;
602                                } else if found_while == 1 {
603                                    state
604                                        .instructions
605                                        .push(Instruction::Jmp(block.last_block_start));
606                                    found_while += 1;
607                                    break;
608                                }
609                            }
610                            if found_while != 2 {
611                                return Err(token_info.custom(ErrorType::ContinueOutsideLoop));
612                            }
613                        }
614
615                        _ => {
616                            if state.has_capability(&Capability::Ihave) {
617                                state.ignore_instruction()?;
618                                state.instructions.push(Instruction::Invalid(Invalid {
619                                    name: instruction.to_string(),
620                                    line_num: token_info.line_num,
621                                    line_pos: token_info.line_pos,
622                                }));
623                                continue;
624                            } else {
625                                return Err(CompileError {
626                                    line_num: state.block.line_num,
627                                    line_pos: state.block.line_pos,
628                                    error_type: ErrorType::UnexpectedToken {
629                                        expected: "command".into(),
630                                        found: instruction.to_string(),
631                                    },
632                                });
633                            }
634                        }
635                    }
636
637                    if let Some(mut new_block) = is_new_block {
638                        new_block.line_num = state.tokens.line_num;
639                        new_block.line_pos = state.tokens.pos - state.tokens.line_start;
640
641                        state.tokens.expect_token(Token::CurlyOpen)?;
642                        if state.block_stack.len() < self.max_nested_blocks {
643                            state.block.last_block_start = state.instructions.len() - 1;
644                            state.block_stack.push(state.block);
645                            state.block = new_block;
646                        } else {
647                            return Err(CompileError {
648                                line_num: state.block.line_num,
649                                line_pos: state.block.line_pos,
650                                error_type: ErrorType::TooManyNestedBlocks,
651                            });
652                        }
653                    } else {
654                        state.expect_instruction_end()?;
655                    }
656                }
657                Token::CurlyClose if !state.block_stack.is_empty() => {
658                    state.block_end();
659                    let mut prev_block = state.block_stack.pop().unwrap();
660                    match &state.block.btype {
661                        Word::ForEveryPart => {
662                            state
663                                .instructions
664                                .push(Instruction::Jmp(prev_block.last_block_start));
665                            let cur_pos = state.instructions.len();
666                            if let Instruction::ForEveryPart(fep) =
667                                &mut state.instructions[prev_block.last_block_start]
668                            {
669                                fep.jz_pos = cur_pos;
670                            } else {
671                                debug_assert!(false, "This should not have happened.");
672                            }
673                            for pos in state.block.break_jmps {
674                                if let Instruction::Jmp(jmp_pos) = &mut state.instructions[pos] {
675                                    *jmp_pos = cur_pos;
676                                } else {
677                                    debug_assert!(false, "This should not have happened.");
678                                }
679                            }
680                            state.last_block_type = Word::Not;
681                        }
682                        Word::If | Word::ElsIf => {
683                            let next_is_block = matches!(
684                                state.tokens.peek().map(|r| r.map(|t| &t.token)),
685                                Some(Ok(Token::Identifier(Word::ElsIf | Word::Else)))
686                            );
687                            if next_is_block {
688                                prev_block.if_jmps.push(state.instructions.len());
689                                state.instructions.push(Instruction::Jmp(usize::MAX));
690                            }
691                            let cur_pos = state.instructions.len();
692                            if let Instruction::Jz(jmp_pos) =
693                                &mut state.instructions[prev_block.last_block_start]
694                            {
695                                *jmp_pos = cur_pos;
696                            } else {
697                                debug_assert!(false, "This should not have happened.");
698                            }
699                            if !next_is_block {
700                                for pos in prev_block.if_jmps.drain(..) {
701                                    if let Instruction::Jmp(jmp_pos) = &mut state.instructions[pos]
702                                    {
703                                        *jmp_pos = cur_pos;
704                                    } else {
705                                        debug_assert!(false, "This should not have happened.");
706                                    }
707                                }
708                                state.last_block_type = Word::Not;
709                            } else {
710                                state.last_block_type = state.block.btype;
711                            }
712                        }
713                        Word::Else => {
714                            let cur_pos = state.instructions.len();
715                            for pos in prev_block.if_jmps.drain(..) {
716                                if let Instruction::Jmp(jmp_pos) = &mut state.instructions[pos] {
717                                    *jmp_pos = cur_pos;
718                                } else {
719                                    debug_assert!(false, "This should not have happened.");
720                                }
721                            }
722                            state.last_block_type = Word::Else;
723                        }
724                        Word::While => {
725                            state
726                                .instructions
727                                .push(Instruction::Jmp(prev_block.last_block_start));
728                            let cur_pos = state.instructions.len();
729                            if let Instruction::While(fep) =
730                                &mut state.instructions[prev_block.last_block_start]
731                            {
732                                fep.jz_pos = cur_pos;
733                            } else {
734                                debug_assert!(false, "This should not have happened.");
735                            }
736                            for pos in state.block.break_jmps {
737                                if let Instruction::Jmp(jmp_pos) = &mut state.instructions[pos] {
738                                    *jmp_pos = cur_pos;
739                                } else {
740                                    debug_assert!(false, "This should not have happened.");
741                                }
742                            }
743                            state.last_block_type = Word::Not;
744                        }
745                        _ => {
746                            debug_assert!(false, "This should not have happened.");
747                        }
748                    }
749
750                    state.block = prev_block;
751                }
752
753                #[cfg(test)]
754                Token::Unknown(instruction) if instruction.contains("test") => {
755                    let has_arguments = instruction != "test";
756                    let mut arguments = vec![Value::Text(instruction.into())];
757
758                    if !has_arguments {
759                        arguments.push(state.parse_string()?);
760                        state.instructions.push(Instruction::TestCmd(arguments));
761                        let mut new_block = Block::new(Word::Else);
762                        new_block.line_num = state.tokens.line_num;
763                        new_block.line_pos = state.tokens.pos - state.tokens.line_start;
764                        state.tokens.expect_token(Token::CurlyOpen)?;
765                        state.block.last_block_start = state.instructions.len() - 1;
766                        state.block_stack.push(state.block);
767                        state.block = new_block;
768                    } else {
769                        loop {
770                            arguments.push(match state.tokens.unwrap_next()?.token {
771                                Token::StringConstant(s) => Value::from(s),
772                                Token::StringVariable(s) => state
773                                    .tokenize_string(&s, true)
774                                    .map_err(|error_type| CompileError {
775                                        line_num: 0,
776                                        line_pos: 0,
777                                        error_type,
778                                    })?,
779                                Token::Number(n) => {
780                                    Value::Number(crate::compiler::Number::Integer(n as i64))
781                                }
782                                Token::Identifier(s) => Value::Text(s.to_string().into()),
783                                Token::Tag(s) => Value::Text(format!(":{s}").into()),
784                                Token::Unknown(s) => Value::Text(s.into()),
785                                Token::Semicolon => break,
786                                other => panic!("Invalid test param {other:?}"),
787                            });
788                        }
789                        state.instructions.push(Instruction::TestCmd(arguments));
790                    }
791                }
792
793                Token::Unknown(instruction) => {
794                    if state.has_capability(&Capability::Ihave) {
795                        state.ignore_instruction()?;
796                        state.instructions.push(Instruction::Invalid(Invalid {
797                            name: instruction,
798                            line_num: token_info.line_num,
799                            line_pos: token_info.line_pos,
800                        }));
801                    } else {
802                        return Err(CompileError {
803                            line_num: state.block.line_num,
804                            line_pos: state.block.line_pos,
805                            error_type: ErrorType::UnexpectedToken {
806                                expected: "command".into(),
807                                found: instruction,
808                            },
809                        });
810                    }
811                }
812                _ => {
813                    return Err(token_info.expected("instruction"));
814                }
815            }
816        }
817
818        if !state.block_stack.is_empty() {
819            return Err(CompileError {
820                line_num: state.block.line_num,
821                line_pos: state.block.line_pos,
822                error_type: ErrorType::UnterminatedBlock,
823            });
824        }
825
826        // Map local variables
827        let mut num_vars = std::cmp::max(state.vars_num_max, state.vars_num);
828        if state.vars_local > 0 {
829            state.map_local_vars(num_vars);
830            num_vars += state.vars_local;
831        }
832
833        Ok(Sieve {
834            instructions: state.instructions,
835            num_vars,
836            num_match_vars: state.vars_match_max,
837        })
838    }
839}
840
841impl<'x> CompilerState<'x> {
842    pub(crate) fn is_var_local(&self, name: &str) -> bool {
843        let name = name.to_ascii_lowercase();
844        if self.block.vars_local.contains_key(&name) {
845            true
846        } else {
847            for block in self.block_stack.iter().rev() {
848                if block.vars_local.contains_key(&name) {
849                    return true;
850                }
851            }
852            false
853        }
854    }
855
856    pub(crate) fn is_var_global(&self, name: &str) -> bool {
857        let name = name.to_ascii_lowercase();
858        self.vars_global.contains(&name)
859    }
860
861    pub(crate) fn register_local_var(&mut self, name: String, register_as_local: bool) -> usize {
862        if let Some(var_id) = self.get_local_var(&name) {
863            var_id
864        } else if !register_as_local || self.block_stack.is_empty() {
865            let var_id = self.vars_num;
866            self.block.vars_local.insert(name, var_id);
867            self.vars_num += 1;
868            var_id
869        } else {
870            let var_id = usize::MAX - self.vars_local;
871            self.block_stack
872                .first_mut()
873                .unwrap()
874                .vars_local
875                .insert(name, usize::MAX - self.vars_local);
876            self.vars_local += 1;
877            var_id
878        }
879    }
880
881    pub(crate) fn register_global_var(&mut self, name: &str) {
882        self.vars_global.insert(name.to_ascii_lowercase());
883    }
884
885    pub(crate) fn get_local_var(&self, name: &str) -> Option<usize> {
886        let name = name.to_ascii_lowercase();
887        if let Some(var_id) = self.block.vars_local.get(&name) {
888            Some(*var_id)
889        } else {
890            for block in self.block_stack.iter().rev() {
891                if let Some(var_id) = block.vars_local.get(&name) {
892                    return Some(*var_id);
893                }
894            }
895            None
896        }
897    }
898
899    pub(crate) fn register_match_var(&mut self, num: usize) -> bool {
900        let mut block = &mut self.block;
901
902        if block.match_test_pos.is_empty() {
903            for block_ in self.block_stack.iter_mut().rev() {
904                if !block_.match_test_pos.is_empty() {
905                    block = block_;
906                    break;
907                }
908            }
909        }
910
911        if !block.match_test_pos.is_empty() {
912            debug_assert!(num < 63);
913
914            for pos in &block.match_test_pos {
915                if let Instruction::Test(test) = &mut self.instructions[*pos] {
916                    let match_type = match test {
917                        Test::Address(t) => &mut t.match_type,
918                        Test::Body(t) => &mut t.match_type,
919                        Test::Date(t) => &mut t.match_type,
920                        Test::CurrentDate(t) => &mut t.match_type,
921                        Test::Envelope(t) => &mut t.match_type,
922                        Test::HasFlag(t) => &mut t.match_type,
923                        Test::Header(t) => &mut t.match_type,
924                        Test::Metadata(t) => &mut t.match_type,
925                        Test::NotifyMethodCapability(t) => &mut t.match_type,
926                        Test::SpamTest(t) => &mut t.match_type,
927                        Test::String(t) | Test::Environment(t) => &mut t.match_type,
928                        Test::VirusTest(t) => &mut t.match_type,
929                        _ => {
930                            debug_assert!(false, "This should not have happened: {test:?}");
931                            return false;
932                        }
933                    };
934                    if let MatchType::Matches(positions) | MatchType::Regex(positions) = match_type
935                    {
936                        *positions |= 1 << num;
937                        block.match_test_vars = *positions;
938                    } else {
939                        debug_assert!(false, "This should not have happened");
940                        return false;
941                    }
942                } else {
943                    debug_assert!(false, "This should not have happened");
944                    return false;
945                }
946            }
947            true
948        } else {
949            false
950        }
951    }
952
953    pub(crate) fn block_end(&mut self) {
954        let vars_num_block = self.block.vars_local.len();
955        if vars_num_block > 0 {
956            if self.vars_num > self.vars_num_max {
957                self.vars_num_max = self.vars_num;
958            }
959            self.vars_num -= vars_num_block;
960            self.instructions.push(Instruction::Clear(Clear {
961                match_vars: self.block.match_test_vars,
962                local_vars_idx: self.vars_num as u32,
963                local_vars_num: vars_num_block as u32,
964            }));
965        } else if self.block.match_test_vars != 0 {
966            self.instructions.push(Instruction::Clear(Clear {
967                match_vars: self.block.match_test_vars,
968                local_vars_idx: 0,
969                local_vars_num: 0,
970            }));
971        }
972    }
973
974    fn map_local_vars(&mut self, last_id: usize) {
975        for instruction in &mut self.instructions {
976            match instruction {
977                Instruction::Test(v) => v.map_local_vars(last_id),
978                Instruction::Keep(k) => k.flags.map_local_vars(last_id),
979                Instruction::FileInto(v) => {
980                    v.folder.map_local_vars(last_id);
981                    v.flags.map_local_vars(last_id);
982                    v.mailbox_id.map_local_vars(last_id);
983                    v.special_use.map_local_vars(last_id);
984                }
985                Instruction::Redirect(v) => {
986                    v.address.map_local_vars(last_id);
987                    v.by_time.map_local_vars(last_id);
988                }
989                Instruction::Replace(v) => {
990                    v.subject.map_local_vars(last_id);
991                    v.from.map_local_vars(last_id);
992                    v.replacement.map_local_vars(last_id);
993                }
994                Instruction::Enclose(v) => {
995                    v.subject.map_local_vars(last_id);
996                    v.headers.map_local_vars(last_id);
997                    v.value.map_local_vars(last_id);
998                }
999                Instruction::ExtractText(v) => {
1000                    v.name.map_local_vars(last_id);
1001                }
1002                Instruction::Convert(v) => {
1003                    v.from_media_type.map_local_vars(last_id);
1004                    v.to_media_type.map_local_vars(last_id);
1005                    v.transcoding_params.map_local_vars(last_id);
1006                }
1007                Instruction::AddHeader(v) => {
1008                    v.field_name.map_local_vars(last_id);
1009                    v.value.map_local_vars(last_id);
1010                }
1011                Instruction::DeleteHeader(v) => {
1012                    v.field_name.map_local_vars(last_id);
1013                    v.value_patterns.map_local_vars(last_id);
1014                }
1015                Instruction::Set(v) => {
1016                    v.name.map_local_vars(last_id);
1017                    v.value.map_local_vars(last_id);
1018                }
1019                Instruction::Let(v) => {
1020                    v.name.map_local_vars(last_id);
1021                    v.expr.map_local_vars(last_id);
1022                }
1023                Instruction::While(v) => {
1024                    v.expr.map_local_vars(last_id);
1025                }
1026                Instruction::Eval(v) => {
1027                    v.map_local_vars(last_id);
1028                }
1029                Instruction::Notify(v) => {
1030                    v.from.map_local_vars(last_id);
1031                    v.importance.map_local_vars(last_id);
1032                    v.options.map_local_vars(last_id);
1033                    v.message.map_local_vars(last_id);
1034                    v.fcc.map_local_vars(last_id);
1035                    v.method.map_local_vars(last_id);
1036                }
1037                Instruction::Reject(v) => {
1038                    v.reason.map_local_vars(last_id);
1039                }
1040                Instruction::Vacation(v) => {
1041                    v.subject.map_local_vars(last_id);
1042                    v.from.map_local_vars(last_id);
1043                    v.fcc.map_local_vars(last_id);
1044                    v.reason.map_local_vars(last_id);
1045                }
1046                Instruction::Error(v) => {
1047                    v.message.map_local_vars(last_id);
1048                }
1049                Instruction::EditFlags(v) => {
1050                    v.name.map_local_vars(last_id);
1051                    v.flags.map_local_vars(last_id);
1052                }
1053                Instruction::Include(v) => {
1054                    v.value.map_local_vars(last_id);
1055                }
1056                _ => {}
1057            }
1058        }
1059    }
1060}
1061
1062pub trait MapLocalVars {
1063    fn map_local_vars(&mut self, last_id: usize);
1064}
1065
1066impl MapLocalVars for Test {
1067    fn map_local_vars(&mut self, last_id: usize) {
1068        match self {
1069            Test::Address(v) => {
1070                v.header_list.map_local_vars(last_id);
1071                v.key_list.map_local_vars(last_id);
1072            }
1073            Test::Envelope(v) => {
1074                v.key_list.map_local_vars(last_id);
1075            }
1076            Test::Exists(v) => {
1077                v.header_names.map_local_vars(last_id);
1078            }
1079            Test::Header(v) => {
1080                v.key_list.map_local_vars(last_id);
1081                v.header_list.map_local_vars(last_id);
1082                v.mime_opts.map_local_vars(last_id);
1083            }
1084            Test::Body(v) => {
1085                v.key_list.map_local_vars(last_id);
1086            }
1087            Test::Convert(v) => {
1088                v.from_media_type.map_local_vars(last_id);
1089                v.to_media_type.map_local_vars(last_id);
1090                v.transcoding_params.map_local_vars(last_id);
1091            }
1092            Test::Date(v) => {
1093                v.key_list.map_local_vars(last_id);
1094                v.header_name.map_local_vars(last_id);
1095            }
1096            Test::CurrentDate(v) => {
1097                v.key_list.map_local_vars(last_id);
1098            }
1099            Test::Duplicate(v) => {
1100                v.handle.map_local_vars(last_id);
1101                v.dup_match.map_local_vars(last_id);
1102            }
1103            Test::String(v) => {
1104                v.source.map_local_vars(last_id);
1105                v.key_list.map_local_vars(last_id);
1106            }
1107            Test::Environment(v) => {
1108                v.source.map_local_vars(last_id);
1109                v.key_list.map_local_vars(last_id);
1110            }
1111            Test::NotifyMethodCapability(v) => {
1112                v.key_list.map_local_vars(last_id);
1113                v.notification_capability.map_local_vars(last_id);
1114                v.notification_uri.map_local_vars(last_id);
1115            }
1116            Test::ValidNotifyMethod(v) => {
1117                v.notification_uris.map_local_vars(last_id);
1118            }
1119            Test::ValidExtList(v) => {
1120                v.list_names.map_local_vars(last_id);
1121            }
1122            Test::HasFlag(v) => {
1123                v.variable_list.map_local_vars(last_id);
1124                v.flags.map_local_vars(last_id);
1125            }
1126            Test::MailboxExists(v) => {
1127                v.mailbox_names.map_local_vars(last_id);
1128            }
1129            Test::Metadata(v) => {
1130                v.key_list.map_local_vars(last_id);
1131                v.medatata.map_local_vars(last_id);
1132            }
1133            Test::MetadataExists(v) => {
1134                v.annotation_names.map_local_vars(last_id);
1135                v.mailbox.map_local_vars(last_id);
1136            }
1137            Test::MailboxIdExists(v) => {
1138                v.mailbox_ids.map_local_vars(last_id);
1139            }
1140            Test::SpamTest(v) => {
1141                v.value.map_local_vars(last_id);
1142            }
1143            Test::VirusTest(v) => {
1144                v.value.map_local_vars(last_id);
1145            }
1146            Test::SpecialUseExists(v) => {
1147                v.mailbox.map_local_vars(last_id);
1148                v.attributes.map_local_vars(last_id);
1149            }
1150            Test::Vacation(v) => {
1151                v.addresses.map_local_vars(last_id);
1152                v.handle.map_local_vars(last_id);
1153                v.reason.map_local_vars(last_id);
1154            }
1155            #[cfg(test)]
1156            Test::TestCmd { arguments, .. } => {
1157                arguments.map_local_vars(last_id);
1158            }
1159            _ => (),
1160        }
1161    }
1162}
1163
1164impl MapLocalVars for VariableType {
1165    fn map_local_vars(&mut self, last_id: usize) {
1166        match self {
1167            VariableType::Local(id) if *id > last_id => {
1168                *id = (usize::MAX - *id) + last_id;
1169            }
1170            _ => (),
1171        }
1172    }
1173}
1174
1175impl MapLocalVars for Value {
1176    fn map_local_vars(&mut self, last_id: usize) {
1177        match self {
1178            Value::Variable(var) => var.map_local_vars(last_id),
1179            Value::List(items) => items.map_local_vars(last_id),
1180            _ => (),
1181        }
1182    }
1183}
1184
1185impl<T: MapLocalVars> MapLocalVars for Option<T> {
1186    fn map_local_vars(&mut self, last_id: usize) {
1187        if let Some(value) = self {
1188            value.map_local_vars(last_id);
1189        }
1190    }
1191}
1192
1193impl MapLocalVars for Expression {
1194    fn map_local_vars(&mut self, last_id: usize) {
1195        if let Expression::Variable(var) = self {
1196            var.map_local_vars(last_id)
1197        }
1198    }
1199}
1200
1201impl<T: MapLocalVars> MapLocalVars for Vec<T> {
1202    fn map_local_vars(&mut self, last_id: usize) {
1203        for item in self {
1204            item.map_local_vars(last_id);
1205        }
1206    }
1207}
1208
1209impl Block {
1210    pub fn new(btype: Word) -> Self {
1211        Block {
1212            btype,
1213            label: None,
1214            line_num: 0,
1215            line_pos: 0,
1216            last_block_start: 0,
1217            match_test_pos: vec![],
1218            match_test_vars: 0,
1219            if_jmps: vec![],
1220            break_jmps: vec![],
1221            vars_local: AHashMap::new(),
1222            capabilities: AHashSet::new(),
1223            require_pos: usize::MAX,
1224        }
1225    }
1226
1227    pub fn with_label(mut self, label: String) -> Self {
1228        self.label = label.into();
1229        self
1230    }
1231}