skrifa/outline/glyf/hint/engine/
control_flow.rs

1//! Managing the flow of control.
2//!
3//! Implements 6 instructions.
4//!
5//! See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#managing-the-flow-of-control>
6
7use read_fonts::tables::glyf::bytecode::Opcode;
8
9use super::{Engine, HintErrorKind, OpResult};
10
11impl Engine<'_> {
12    /// If test.
13    ///
14    /// IF[] (0x58)
15    ///
16    /// Pops: e: stack element
17    ///
18    /// Tests the element popped off the stack: if it is zero (FALSE), the
19    /// instruction pointer is jumped to the next ELSE or EIF instruction
20    /// in the instruction stream. If the element at the top of the stack is
21    /// nonzero (TRUE), the next instruction in the instruction stream is
22    /// executed. Execution continues until an ELSE instruction is encountered
23    /// or an EIF instruction ends the IF. If an else statement is found before
24    /// the EIF, the instruction pointer is moved to the EIF statement.
25    ///
26    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#if-test>
27    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L3334>
28    pub(super) fn op_if(&mut self) -> OpResult {
29        if self.value_stack.pop()? == 0 {
30            // The condition variable is false so we jump to the next
31            // ELSE or EIF but we have to skip intermediate IF/ELSE/EIF
32            // instructions.
33            let mut nest_depth = 1;
34            let mut out = false;
35            while !out {
36                let opcode = self.decode_next_opcode()?;
37                match opcode {
38                    Opcode::IF => nest_depth += 1,
39                    Opcode::ELSE => out = nest_depth == 1,
40                    Opcode::EIF => {
41                        nest_depth -= 1;
42                        out = nest_depth == 0;
43                    }
44                    _ => {}
45                }
46            }
47        }
48        Ok(())
49    }
50
51    /// Else.
52    ///
53    /// ELSE[] (0x1B)
54    ///
55    /// Marks the start of the sequence of instructions that are to be executed
56    /// if an IF instruction encounters a FALSE value on the stack. This
57    /// sequence of instructions is terminated with an EIF instruction.
58    ///
59    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#else>
60    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L3378>
61    pub(super) fn op_else(&mut self) -> OpResult {
62        let mut nest_depth = 1;
63        while nest_depth != 0 {
64            let opcode = self.decode_next_opcode()?;
65            match opcode {
66                Opcode::IF => nest_depth += 1,
67                Opcode::EIF => nest_depth -= 1,
68                _ => {}
69            }
70        }
71        Ok(())
72    }
73
74    /// End if.
75    ///
76    /// EIF[] (0x59)
77    ///
78    /// Marks the end of an IF[] instruction.
79    ///
80    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#end-if>
81    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L3411>
82    pub(super) fn op_eif(&mut self) -> OpResult {
83        // Nothing
84        Ok(())
85    }
86
87    /// Jump relative on true.
88    ///
89    /// JROT[] (0x78)
90    ///
91    /// Pops: e: stack element
92    ///       offset: number of bytes to move the instruction pointer
93    ///
94    /// Pops and tests the element value, and then pops the offset. If the
95    /// element value is non-zero (TRUE), the signed offset will be added
96    /// to the instruction pointer and execution will be resumed at the address
97    /// obtained. Otherwise, the jump is not taken and the next instruction in
98    /// the instruction stream is executed. The jump is relative to the position
99    /// of the instruction itself. That is, the instruction pointer is still
100    /// pointing at the JROT[ ] instruction when offset is added to obtain the
101    /// new address.
102    ///
103    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#jump-relative-on-true>
104    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L3459>
105    pub(super) fn op_jrot(&mut self) -> OpResult {
106        let e = self.value_stack.pop()?;
107        self.do_jump(e != 0)
108    }
109
110    /// Jump.
111    ///
112    /// JMPR[] (0x1C)
113    ///
114    /// Pops: offset: number of bytes to move the instruction pointer
115    ///
116    /// The signed offset is added to the instruction pointer and execution
117    /// is resumed at the new location in the instruction steam. The jump is
118    /// relative to the position of the instruction itself. That is, the
119    /// instruction pointer is still pointing at the JROT[] instruction when
120    /// offset is added to obtain the new address.
121    ///
122    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#jump>
123    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L3424>
124    pub(super) fn op_jmpr(&mut self) -> OpResult {
125        self.do_jump(true)
126    }
127
128    /// Jump relative on false.
129    ///
130    /// JROF[] (0x78)
131    ///
132    /// Pops: e: stack element
133    ///       offset: number of bytes to move the instruction pointer
134    ///
135    /// Pops and tests the element value, and then pops the offset. If the
136    /// element value is non-zero (TRUE), the signed offset will be added
137    /// to the instruction pointer and execution will be resumed at the address
138    /// obtained. Otherwise, the jump is not taken and the next instruction in
139    /// the instruction stream is executed. The jump is relative to the position
140    /// of the instruction itself. That is, the instruction pointer is still
141    /// pointing at the JROT[ ] instruction when offset is added to obtain the
142    /// new address.
143    ///
144    /// Pops and tests the element value, and then pops the offset. If the
145    /// element value is zero (FALSE), the signed offset will be added to the
146    /// nstruction pointer and execution will be resumed at the address
147    /// obtainted. Otherwise, the jump is not taken and the next instruction
148    /// in the instruction stream is executed. The jump is relative to the
149    /// position of the instruction itself. That is, the instruction pointer is
150    /// still pointing at the JROT[ ] instruction when the offset is added to
151    /// obtain the new address.
152    ///
153    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#jump-relative-on-false>
154    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L3474>
155    pub(super) fn op_jrof(&mut self) -> OpResult {
156        let e = self.value_stack.pop()?;
157        self.do_jump(e == 0)
158    }
159
160    /// Common code for jump instructions.
161    ///
162    /// See <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L3424>
163    fn do_jump(&mut self, test: bool) -> OpResult {
164        // Offset is relative to previous jump instruction and decoder is
165        // already pointing to next instruction, so subtract one
166        let jump_offset = self.value_stack.pop()?.wrapping_sub(1);
167        if test {
168            if jump_offset < 0 {
169                if jump_offset == -1 {
170                    // If the offset is -1, we'll just loop in place... forever
171                    return Err(HintErrorKind::InvalidJump);
172                }
173                self.loop_budget.doing_backward_jump()?;
174            }
175            self.program.decoder.pc = self
176                .program
177                .decoder
178                .pc
179                .wrapping_add_signed(jump_offset as isize);
180        }
181        Ok(())
182    }
183
184    fn decode_next_opcode(&mut self) -> Result<Opcode, HintErrorKind> {
185        Ok(self
186            .program
187            .decoder
188            .decode()
189            .ok_or(HintErrorKind::UnexpectedEndOfBytecode)??
190            .opcode)
191    }
192}
193
194#[cfg(test)]
195mod tests {
196    use super::{super::MockEngine, HintErrorKind, Opcode};
197
198    #[test]
199    fn if_else() {
200        use Opcode::*;
201        let mut mock = MockEngine::new();
202        let mut engine = mock.engine();
203        // Some code with nested ifs
204        #[rustfmt::skip]
205        let ops = [
206            IF,
207                ADD, // 1
208                SUB,
209                IF,
210                    MUL, // 4
211                    DIV,
212                ELSE, // 8
213                    IUP0, // 7
214                    IUP1,
215                EIF,
216            ELSE, // 10
217                RUTG, // 11
218                IF,
219                EIF,
220            EIF // 14
221        ];
222        let bytecode = ops.map(|op| op as u8);
223        engine.program.decoder.bytecode = bytecode.as_slice();
224        // Outer if
225        {
226            // push a true value to enter the first branch
227            engine.program.decoder.pc = 1;
228            engine.value_stack.push(1).unwrap();
229            engine.op_if().unwrap();
230            assert_eq!(engine.program.decoder.pc, 1);
231            // false enters the else branch
232            engine.program.decoder.pc = 1;
233            engine.value_stack.push(0).unwrap();
234            engine.op_if().unwrap();
235            assert_eq!(engine.program.decoder.pc, 11);
236        }
237        // Inner if
238        {
239            // push a true value to enter the first branch
240            engine.program.decoder.pc = 4;
241            engine.value_stack.push(1).unwrap();
242            engine.op_if().unwrap();
243            assert_eq!(engine.program.decoder.pc, 4);
244            // false enters the else branch
245            engine.program.decoder.pc = 4;
246            engine.value_stack.push(0).unwrap();
247            engine.op_if().unwrap();
248            assert_eq!(engine.program.decoder.pc, 7);
249        }
250        // Else with nested if
251        {
252            // This jumps to the instruction after the next EIF, skipping any
253            // nested conditional blocks
254            engine.program.decoder.pc = 10;
255            engine.op_else().unwrap();
256            assert_eq!(engine.program.decoder.pc, 15);
257            engine.program.decoder.pc = 8;
258            engine.op_else().unwrap();
259            assert_eq!(engine.program.decoder.pc, 10);
260        }
261    }
262
263    #[test]
264    fn jumps() {
265        let mut mock = MockEngine::new();
266        let mut engine = mock.engine();
267        // Unconditional jump
268        {
269            engine.program.decoder.pc = 1000;
270            engine.value_stack.push(100).unwrap();
271            engine.op_jmpr().unwrap();
272            assert_eq!(engine.program.decoder.pc, 1099);
273        }
274        // Jump if true
275        {
276            engine.program.decoder.pc = 1000;
277            // first test false condition, pc shouldn't change
278            engine.value_stack.push(100).unwrap();
279            engine.value_stack.push(0).unwrap();
280            engine.op_jrot().unwrap();
281            assert_eq!(engine.program.decoder.pc, 1000);
282            // then true condition
283            engine.value_stack.push(100).unwrap();
284            engine.value_stack.push(1).unwrap();
285            engine.op_jrot().unwrap();
286            assert_eq!(engine.program.decoder.pc, 1099);
287        }
288        // Jump if false
289        {
290            engine.program.decoder.pc = 1000;
291            // first test true condition, pc shouldn't change
292            engine.value_stack.push(-100).unwrap();
293            engine.value_stack.push(1).unwrap();
294            engine.op_jrof().unwrap();
295            assert_eq!(engine.program.decoder.pc, 1000);
296            // then false condition
297            engine.value_stack.push(-100).unwrap();
298            engine.value_stack.push(0).unwrap();
299            engine.op_jrof().unwrap();
300            assert_eq!(engine.program.decoder.pc, 899);
301        }
302        // Exhaust backward jump loop budget
303        {
304            engine.loop_budget.limit = 40;
305            for i in 0..45 {
306                engine.value_stack.push(-5).unwrap();
307                let result = engine.op_jmpr();
308                if i < 39 {
309                    result.unwrap();
310                } else {
311                    assert!(matches!(
312                        result,
313                        Err(HintErrorKind::ExceededExecutionBudget)
314                    ));
315                }
316            }
317        }
318    }
319}