Skip to content

Commit 5b86d5a

Browse files
author
Luca Toniolo
committed
Error on nested subroutine definitions in named sub files
The parser silently ignored sub definitions inside called subroutines, causing wrong parameters and premature returns. Numbered subs after a named endsub polluted the global offset map and conflicted across files. - Add nesting checks in read and execute phases at call_level > 0 - Block forward-seek for subs at call_level > 0 when no file exists - Let control_back_to errors propagate without generic overwrite - Update o-code docs with nesting example and new error conditions Ref: #3880
1 parent c9e5894 commit 5b86d5a

File tree

19 files changed

+162
-3
lines changed

19 files changed

+162
-3
lines changed

docs/src/gcode/o-code.adoc

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,8 +123,37 @@ o100 endsub
123123
o100 call [100] [2] [325]
124124
----
125125

126-
Subroutine bodies may not be nested.
127-
They may only be called after they are defined.
126+
Subroutine *definitions* may not be nested.
127+
Defining a subroutine inside another subroutine is not allowed and will
128+
produce an error. For example, the following is *invalid*:
129+
130+
.Invalid Nested Subroutine Definition (produces an error)
131+
[source,{ngc}]
132+
----
133+
o<outer> sub
134+
o100 sub (INVALID: nested subroutine definition)
135+
(code here)
136+
o100 endsub
137+
o<outer> endsub
138+
----
139+
140+
However, *calling* a subroutine from within another subroutine is perfectly
141+
valid. It is the `sub`/`endsub` definition that cannot be nested, not the
142+
`call`:
143+
144+
.Valid Nested Subroutine Call
145+
[source,{ngc}]
146+
----
147+
o<outer> sub
148+
(code here)
149+
o<helper> call [1] [2] (OK: calling another sub)
150+
o<outer> endsub
151+
----
152+
153+
Other O-code control flow such as `if`/`endif`, `while`/`endwhile`,
154+
`do`/`while`, and `repeat`/`endrepeat` may be used freely inside subroutines.
155+
156+
Subroutines may only be called after they are defined.
128157
They may be called from other functions, and may call themselves recursively if it makes sense to do so.
129158
The maximum subroutine nesting level is 10.
130159

@@ -391,6 +420,10 @@ your call and include a sub and endsub in the file. The file must be in the
391420
directory pointed to by 'PROGRAM_PREFIX' or 'SUBROUTINE_PATH' in the INI file.
392421
The file name can include *lowercase* letters, numbers, dash, and underscore
393422
only. A named subroutine file can contain only a single subroutine definition.
423+
Do not place additional numbered subroutine definitions (e.g. `o100 sub`)
424+
in the same file as a named subroutine. Numbered subroutines share a global
425+
namespace and can silently conflict across files. If you need helper
426+
subroutines, place each one in its own file.
394427

395428
.Named File Example
396429
[source,{ngc}]
@@ -455,6 +488,9 @@ interpreter:
455488
- a label on `else`, `elseif` or `endif` not pointing to a matching `if`
456489
- a label on `break` or `continue` which does not point to a matching `while` or `do`
457490
- a label on `endrepeat` or `endwhile` no referring to a corresponding `while` or `repeat`
491+
- a subroutine definition (`sub`) inside another subroutine body (nested definitions)
492+
- a numbered subroutine called from within a named subroutine file but not found in the
493+
offset table or as a separate file
458494

459495
To make these errors non-fatal warnings on stderr, set bit 0x20 in
460496
the `[RS274NGC]FEATURE=` mask ini option.

src/emc/rs274ngc/interp_o_word.cc

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,6 @@ int Interp::execute_call(setup_pointer settings,
277277
logOword("M98 L0 instruction; skipping call");
278278
else if (control_back_to(eblock, settings) == INTERP_ERROR) {
279279
settings->call_level--;
280-
ERS(NCE_UNABLE_TO_OPEN_FILE,eblock->o_name);
281280
return INTERP_ERROR;
282281
}
283282

@@ -605,6 +604,20 @@ int Interp::control_back_to( block_pointer block, // pointer to block
605604
}
606605
strncpy(settings->filename, newFileName, sizeof(settings->filename));
607606
} else {
607+
// No file found for this sub name.
608+
// Block forward-seek if the current file is not the main program.
609+
// Numbered subs after a named endsub pollute the global offset
610+
// map and silently conflict across files. Forward-seek in the
611+
// main program's own file (call_level 0) is still allowed.
612+
if (settings->call_level > 0) {
613+
context_pointer main_frame = &settings->sub_context[0];
614+
if (main_frame->filename == NULL ||
615+
strcmp(settings->filename, main_frame->filename) != 0) {
616+
ERS(_("Subroutine 'O%s' not found -- "
617+
"not in offset table and no file '%s' found"),
618+
block->o_name, newFileName);
619+
}
620+
}
608621
char *dirname = getcwd(NULL, 0);
609622
logOword("fopen: |%s| failed CWD:|%s|", newFileName,
610623
dirname);
@@ -792,6 +805,14 @@ int Interp::convert_control_functions(block_pointer block, // pointer to a block
792805
// if the level is not zero, this is a call
793806
// not the definition
794807
if (settings->call_level != 0) {
808+
// Check for nested or extra sub definitions inside a called sub.
809+
// Only the entry sub (matching the call) is allowed; any other
810+
// 'sub' keyword is an error (nested def or numbered sub in file).
811+
context_pointer cf = &settings->sub_context[settings->call_level];
812+
CHKS((cf->subName && strcmp(cf->subName, block->o_name) != 0),
813+
_("Nested subroutine definition: 'O%s sub' found inside "
814+
"called subroutine 'O%s'"),
815+
block->o_name, cf->subName);
795816
logOword("call:%f:%f:%f",
796817
settings->parameters[1],
797818
settings->parameters[2],

src/emc/rs274ngc/interp_read.cc

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1716,6 +1716,18 @@ int Interp::read_o( /* ARGUMENTS */
17161716
{
17171717
// Check we're not already defining a main- or sub-program
17181718
CHKS((_setup.defining_sub == 1), NCE_NESTED_SUBROUTINE_DEFN);
1719+
1720+
// Check for sub definition inside a called subroutine.
1721+
// When call_level > 0 and not seeking (skipping_o == NULL),
1722+
// hitting a sub that doesn't match the current call is an error.
1723+
if (_setup.call_level > 0 && _setup.skipping_o == NULL) {
1724+
const char *current_sub =
1725+
_setup.sub_context[_setup.call_level].subName;
1726+
CHKS((current_sub && strcmp(current_sub, block->o_name) != 0),
1727+
_("Nested subroutine definition: 'O%s sub' found inside "
1728+
"called subroutine 'O%s'"),
1729+
block->o_name, current_sub);
1730+
}
17191731
}
17201732
// in terms of execution endsub and return do the same thing
17211733
else if ((block->o_type == O_endsub) || (block->o_type == O_return) ||
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
1 N..... USE_LENGTH_UNITS(CANON_UNITS_MM)
2+
2 N..... SET_G5X_OFFSET(1, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000)
3+
3 N..... SET_G92_OFFSET(0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000)
4+
4 N..... SET_XY_ROTATION(0.0000)
5+
5 N..... SET_FEED_REFERENCE(CANON_XYZ)
6+
6 N..... ON_RESET()
7+
7 N..... COMMENT("Test: nested sub definition inside named sub should error")
8+
8 N..... ON_RESET()
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
o<nested> sub
2+
o100 sub
3+
(DEBUG, inner o100: #1 #2 #3)
4+
o100 endsub
5+
o100 call [4] [5] [6]
6+
o<nested> endsub
7+
M2
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[RS274NGC]
2+
SUBROUTINE_PATH=subs
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
(Test: nested sub definition inside named sub should error)
2+
o<nested> call [7] [8] [9]
3+
M2
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#!/bin/bash -e
2+
# Nested sub definition inside a called named sub must produce an error.
3+
# Regression test for LinuxCNC/linuxcnc#3880.
4+
! rs274 -g -i test.ini test.ngc
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
1 N..... USE_LENGTH_UNITS(CANON_UNITS_MM)
2+
2 N..... SET_G5X_OFFSET(1, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000)
3+
3 N..... SET_G92_OFFSET(0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000)
4+
4 N..... SET_XY_ROTATION(0.0000)
5+
5 N..... SET_FEED_REFERENCE(CANON_XYZ)
6+
6 N..... ON_RESET()
7+
7 N..... COMMENT("Test: numbered sub after named endsub in same file should error")
8+
8 N..... MESSAGE(" sequential main: 7.000000 8.000000 9.000000")
9+
9 N..... ON_RESET()
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
o<sequential> sub
2+
(DEBUG, sequential main: #1 #2 #3)
3+
o200 call [4] [5] [6]
4+
o<sequential> endsub
5+
6+
o200 sub
7+
(DEBUG, o200 helper: #1 #2 #3)
8+
o200 endsub
9+
M2

0 commit comments

Comments
 (0)