go_types/check/
label.rs

1// Copyright 2022 The Goscript Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4//
5//
6// This code is adapted from the offical Go code written in Go
7// with license as follows:
8// Copyright 2013 The Go Authors. All rights reserved.
9// Use of this source code is governed by a BSD-style
10// license that can be found in the LICENSE file.
11
12#![allow(dead_code)]
13use crate::SourceRead;
14
15use super::super::objects::ScopeKey;
16use super::super::scope::Scope;
17use super::check::Checker;
18use go_parser::ast::{BlockStmt, BranchStmt, Decl, Node, Stmt};
19use go_parser::{AstObjects, LabeledStmtKey, Map, Pos, Token};
20use std::cell::RefCell;
21use std::rc::Rc;
22
23#[derive(Debug)]
24struct Block {
25    parent: Option<Rc<RefCell<Block>>>, // enclosing block
26    lstmt: Option<LabeledStmtKey>,      // labeled statement to which this block belongs
27    labels: Map<String, LabeledStmtKey>,
28}
29
30impl Block {
31    fn new(parent: Option<Rc<RefCell<Block>>>, stmt: Option<LabeledStmtKey>) -> Block {
32        Block {
33            parent: parent,
34            lstmt: stmt,
35            labels: Map::new(),
36        }
37    }
38
39    /// insert records a new label declaration for the current block.
40    /// The label must not have been declared before in any block.
41    fn insert(&mut self, s: LabeledStmtKey, objs: &AstObjects) {
42        let name = objs.idents[objs.l_stmts[s].label].name.clone();
43        debug_assert!(self.goto_target(&name).is_none());
44        self.labels.insert(name, s);
45    }
46
47    fn search(
48        &self,
49        name: &str,
50        objs: Option<&AstObjects>,
51        f: Box<dyn Fn(&Block, &str, Option<&AstObjects>) -> Option<LabeledStmtKey>>,
52    ) -> Option<LabeledStmtKey> {
53        if let Some(l) = f(self, name, objs) {
54            return Some(l);
55        }
56        let mut block_op = self.parent.clone();
57        loop {
58            if let Some(b) = block_op {
59                if let Some(l) = f(&b.borrow(), name, objs) {
60                    return Some(l);
61                }
62                block_op = b.borrow().parent.clone();
63            } else {
64                return None;
65            }
66        }
67    }
68
69    /// goto_target returns the labeled statement in the current
70    /// or an enclosing block with the given label name, or None.
71    fn goto_target(&self, name: &str) -> Option<LabeledStmtKey> {
72        let f = |b: &Block, n: &str, _: Option<&AstObjects>| b.labels.get(n).map(|x| *x);
73        self.search(name, None, Box::new(f))
74    }
75
76    /// enclosing_target returns the innermost enclosing labeled
77    /// statement with the given label name, or None.
78    fn enclosing_target(&self, name: &str, objs: &AstObjects) -> Option<LabeledStmtKey> {
79        let f = |b: &Block, n: &str, o: Option<&AstObjects>| {
80            let objs = o.unwrap();
81            if let Some(s) = b.lstmt {
82                if &objs.idents[objs.l_stmts[s].label].name == n {
83                    return Some(s);
84                }
85            }
86            None
87        };
88        self.search(name, Some(objs), Box::new(f))
89    }
90}
91
92struct StmtBranchesContext {
93    var_decl_pos: Option<Pos>,
94    fwd_jumps: Vec<Rc<BranchStmt>>,
95    bad_jumps: Vec<Rc<BranchStmt>>,
96    lstmt: Option<LabeledStmtKey>,
97}
98
99impl StmtBranchesContext {
100    fn new(lstmt: Option<LabeledStmtKey>) -> StmtBranchesContext {
101        StmtBranchesContext {
102            var_decl_pos: None,
103            fwd_jumps: vec![],
104            bad_jumps: vec![],
105            lstmt: lstmt,
106        }
107    }
108
109    /// All forward jumps jumping over a variable declaration are possibly
110    /// invalid (they may still jump out of the block and be ok).
111    /// record_var_decl records them for the given position.
112    fn record_var_decl(&mut self, p: Pos) {
113        self.var_decl_pos = Some(p);
114        self.bad_jumps = self.fwd_jumps.clone();
115    }
116
117    fn jumps_over_var_decl(&self, bs: &Rc<BranchStmt>) -> bool {
118        if let Some(_) = self.var_decl_pos {
119            self.bad_jumps.iter().find(|&x| Rc::ptr_eq(x, bs)).is_some()
120        } else {
121            false
122        }
123    }
124}
125
126impl<'a, S: SourceRead> Checker<'a, S> {
127    pub fn labels(&mut self, body: &Rc<BlockStmt>) {
128        let (pos, end) = (body.pos(), body.end());
129        let comment = "label".to_owned();
130        let all = self.tc_objs.new_scope(None, pos, end, comment, false);
131        let fwd_jumps = self.block_branches(all, None, None, &body.list);
132
133        // If there are any forward jumps left, no label was found for
134        // the corresponding goto statements. Either those labels were
135        // never defined, or they are inside blocks and not reachable
136        // for the respective gotos.
137        let scope = &self.tc_objs.scopes[all];
138        for jump in fwd_jumps.iter() {
139            let ident = self.ast_ident(jump.label.unwrap());
140            let (pos, name) = (ident.pos, ident.name.clone());
141            let msg = if let Some(alt) = scope.lookup(&name) {
142                self.tc_objs.lobjs[*alt]
143                    .entity_type_mut()
144                    .label_set_used(true); // avoid another error
145                format!("goto {} jumps into block", name)
146            } else {
147                format!("label {} not declared", name)
148            };
149            self.error(pos, msg);
150        }
151
152        // spec: "It is illegal to define a label that is never used."
153        for (_, okey) in scope.elems().iter() {
154            let lobj = self.lobj(*okey);
155            if !lobj.entity_type().label_used() {
156                self.soft_error(
157                    lobj.pos(),
158                    format!("label {} declared but not used", lobj.name()),
159                );
160            }
161        }
162    }
163
164    /// block_branches processes a block's statement list and returns the set of outgoing forward jumps.
165    /// all is the scope of all declared labels, parent the set of labels declared in the immediately
166    /// enclosing block, and lstmt is the labeled statement this block is associated with.
167    fn block_branches(
168        &mut self,
169        all: ScopeKey,
170        parent: Option<Rc<RefCell<Block>>>,
171        lstmt: Option<LabeledStmtKey>,
172        list: &Vec<Stmt>,
173    ) -> Vec<Rc<BranchStmt>> {
174        let b = Rc::new(RefCell::new(Block::new(parent, lstmt)));
175        let mut ctx = StmtBranchesContext::new(lstmt);
176
177        for s in list.iter() {
178            self.stmt_branches(all, b.clone(), s, &mut ctx);
179        }
180
181        ctx.fwd_jumps
182    }
183
184    fn stmt_branches(
185        &mut self,
186        all: ScopeKey,
187        block: Rc<RefCell<Block>>,
188        stmt: &Stmt,
189        ctx: &mut StmtBranchesContext,
190    ) {
191        let mut block_branches = |lstmt: Option<LabeledStmtKey>,
192                                  list: &Vec<Stmt>,
193                                  ctx: &mut StmtBranchesContext| {
194            // Unresolved forward jumps inside the nested block
195            // become forward jumps in the current block.
196            ctx.fwd_jumps
197                .append(&mut self.block_branches(all, Some(block.clone()), lstmt, list));
198        };
199
200        match stmt {
201            Stmt::Decl(d) => match &**d {
202                Decl::Gen(gd) => {
203                    if gd.token == Token::VAR {
204                        ctx.record_var_decl(gd.token_pos)
205                    }
206                }
207                _ => {}
208            },
209            Stmt::Labeled(lkey) => {
210                let ls = &self.ast_objs.l_stmts[*lkey];
211                let lable_stmt = ls.stmt.clone();
212                let label = &self.ast_objs.idents[ls.label];
213                let name = label.name.clone();
214                if name != "_" {
215                    let lb = self
216                        .tc_objs
217                        .new_label(label.pos, Some(self.pkg), name.to_string());
218                    if let Some(alt) = Scope::insert(all, lb, self.tc_objs) {
219                        // ok to continue
220                        self.soft_error(label.pos, format!("label {} already declared", name));
221                        self.report_alt_decl(alt);
222                    } else {
223                        block.borrow_mut().insert(*lkey, self.ast_objs);
224                        self.result.record_def(ls.label, Some(lb));
225                    }
226                    // resolve matching forward jumps and remove them from fwd_jumps
227                    ctx.fwd_jumps = ctx
228                        .fwd_jumps
229                        .iter()
230                        .filter(|&x| {
231                            let ikey = x.label.unwrap();
232                            let ident = &self.ast_objs.idents[ikey];
233                            let found = ident.name == name;
234                            if found {
235                                self.tc_objs.lobjs[lb]
236                                    .entity_type_mut()
237                                    .label_set_used(true);
238                                self.result.record_use(ikey, lb);
239                                if ctx.jumps_over_var_decl(x) {
240                                    self.soft_error(
241                                        ident.pos,
242                                        format!(
243                                            "goto {} jumps over variable declaration at line {}",
244                                            name,
245                                            self.position(ctx.var_decl_pos.unwrap()).line
246                                        ),
247                                    )
248                                }
249                            }
250                            !found
251                        })
252                        .map(|x| x.clone())
253                        .collect();
254
255                    ctx.lstmt = Some(*lkey);
256                }
257                self.stmt_branches(all, block, &lable_stmt, ctx);
258            }
259            Stmt::Branch(bs) => {
260                if let Some(label) = bs.label {
261                    let ident = &self.ast_ident(label);
262                    let name = &ident.name;
263                    // determine and validate target
264                    match &bs.token {
265                        Token::BREAK => {
266                            // spec: "If there is a label, it must be that of an enclosing
267                            // "for", "switch", or "select" statement, and that is the one
268                            // whose execution terminates."
269                            let valid = match block.borrow().enclosing_target(name, self.ast_objs) {
270                                Some(t) => match &self.ast_objs.l_stmts[t].stmt {
271                                    Stmt::Switch(_)
272                                    | Stmt::TypeSwitch(_)
273                                    | Stmt::Select(_)
274                                    | Stmt::For(_)
275                                    | Stmt::Range(_) => true,
276                                    _ => false,
277                                },
278                                None => false,
279                            };
280                            if !valid {
281                                self.error(ident.pos, format!("invalid break label {}", name));
282                                return;
283                            }
284                        }
285                        Token::CONTINUE => {
286                            let valid = match block.borrow().enclosing_target(name, self.ast_objs) {
287                                Some(t) => match &self.ast_objs.l_stmts[t].stmt {
288                                    Stmt::For(_) | Stmt::Range(_) => true,
289                                    _ => false,
290                                },
291                                None => false,
292                            };
293                            if !valid {
294                                self.error(ident.pos, format!("invalid continue label {}", name));
295                                return;
296                            }
297                        }
298                        Token::GOTO => {
299                            if block.borrow().goto_target(name).is_none() {
300                                ctx.fwd_jumps.push(bs.clone());
301                                return;
302                            }
303                        }
304                        _ => {
305                            self.invalid_ast(
306                                stmt.pos(self.ast_objs),
307                                &format!("branch statement: {} {}", &bs.token, name),
308                            );
309                            return;
310                        }
311                    }
312
313                    // record label use
314                    let scope = &self.tc_objs.scopes[all];
315                    let lobj = *scope.lookup(name).unwrap();
316                    self.tc_objs.lobjs[lobj]
317                        .entity_type_mut()
318                        .label_set_used(true);
319                    self.result.record_use(label, lobj);
320                }
321            }
322            Stmt::Assign(akey) => {
323                let astmt = &self.ast_objs.a_stmts[*akey];
324                if astmt.token == Token::DEFINE {
325                    ctx.record_var_decl(astmt.pos(self.ast_objs));
326                }
327            }
328            Stmt::Block(bs) => {
329                block_branches(ctx.lstmt, &bs.list, ctx);
330            }
331            Stmt::If(ifstmt) => {
332                let body = Stmt::Block(ifstmt.body.clone());
333                self.stmt_branches(all, block.clone(), &body, ctx);
334                if let Some(els) = &ifstmt.els {
335                    self.stmt_branches(all, block, els, ctx);
336                }
337            }
338            Stmt::Case(cc) => {
339                block_branches(None, &cc.body, ctx);
340            }
341            Stmt::Switch(ss) => {
342                let body = Stmt::Block(ss.body.clone());
343                self.stmt_branches(all, block, &body, ctx);
344            }
345            Stmt::TypeSwitch(tss) => {
346                let body = Stmt::Block(tss.body.clone());
347                self.stmt_branches(all, block, &body, ctx);
348            }
349            Stmt::Comm(cc) => {
350                block_branches(None, &cc.body, ctx);
351            }
352            Stmt::Select(ss) => {
353                let body = Stmt::Block(ss.body.clone());
354                self.stmt_branches(all, block, &body, ctx);
355            }
356            Stmt::For(fs) => {
357                let body = Stmt::Block(fs.body.clone());
358                self.stmt_branches(all, block, &body, ctx);
359            }
360            Stmt::Range(rs) => {
361                let body = Stmt::Block(rs.body.clone());
362                self.stmt_branches(all, block, &body, ctx);
363            }
364            _ => {}
365        }
366    }
367}