Skip to content

Commit f3f45d9

Browse files
tzuohannclaude
andcommitted
halcompile: warn about, and reject colliding, mangled HAL names
A name declared in a .comp file is a C identifier, but it is exported under a mangled HAL identifier: underscores become dashes and a trailing dash or period is removed (comp.adoc, HALNAME). Nothing said so at compile time, so "pin in float my_input" silently became component.N.my-input. Worse, check_name_ok() compares only declared names. Two declarations that mangle to the same HAL name -- x_y and x_y_, the two rows of the HALNAME table that share a HAL identifier -- therefore compiled cleanly and failed much later, at load time: HAL: ERROR: duplicate variable 'collide.0.x-y' collide: rtapi_app_main: Invalid argument (-22) Add check_hal_name(), which rejects that collision at the offending line, and a once-per-file warning listing the names whose HAL identifier differs from the declaration. Both messages point at the HALNAME documentation. The warning is suppressed by -N (--no-name-warnings), which the in-tree component rules pass, since those names are deliberate. All 119 in-tree .comp files preprocess with no new error, and silently under -N. tests/halcompile/halname covers the warning, -N, and the rejected collision. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 18c5bb5 commit f3f45d9

9 files changed

Lines changed: 117 additions & 6 deletions

File tree

docs/man/man1/halcompile.1

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,14 @@ Install \fB.c\fR and \fB.py\fR files into the proper directory for HAL non-realt
7676
Extract documentation from \fB.comp\fR files into \fB.9\fR manpage files in the proper system directory (the \fB\-\-install\fR flag), which may require \fIsudo\fR to write to system directories.
7777
.IP \(bu 4
7878
Preprocess \fB.comp\fR files into \fB.c\fR files (the \fB\-\-preprocess\fR flag)
79+
.SH NAMES
80+
A name declared in a \fB.comp\fR file is a C identifier, but it is exported under a mangled HAL identifier: underscores become dashes, and a trailing dash or period is removed.
81+
A pin declared \fBpin in float my_input\fR is therefore reached from HAL as \fBcomponent.N.my\-input\fR, not \fBcomponent.N.my_input\fR, and a component loaded with \fBloadrt my_comp\fR exports its pins under \fBmy\-comp.N.\fR
82+
.PP
83+
\fBhalcompile\fR prints one warning per file listing the names this applies to; pass \fB\-N\fR (\fB\-\-no\-name\-warnings\fR) to suppress it.
84+
Two declarations that mangle to the same HAL name (for example \fBx_y\fR and \fBx_y_\fR) are rejected, since they would otherwise be accepted here and fail later at \fBloadrt\fR with "HAL: ERROR: duplicate variable".
85+
.PP
86+
See HALNAME under \fISyntax\fR in the \fIHalcompile HAL Component Generator\fR documentation for the full mangling rules.
7987
.SH "SEE ALSO"
8088
\fIHalcompile HAL Component Generator\fR in the LinuxCNC documentation for a full description of the \fB.comp\fR syntax, along with examples
8189

docs/src/hal/comp.adoc

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,18 @@ A trailing "_" is retained, so that HAL identifiers which would otherwise collid
229229
|x.## | x(MM) | x.MM
230230
|===
231231

232+
[NOTE]
233+
====
234+
The HAL identifier, not the declared HALNAME, is what HAL files, `halcmd` and
235+
`halshow` see. `halcompile` prints one warning per file listing the names that
236+
differ, suppressed by the *-N* (*--no-name-warnings*) option.
237+
238+
Two declarations that produce the same HAL identifier -- 'x_y_z' and 'x_y_z_'
239+
in the table above -- are rejected by `halcompile`, because they would
240+
otherwise compile and then fail at `loadrt` with "HAL: ERROR: duplicate
241+
variable".
242+
====
243+
232244
* 'if CONDITION' - An expression involving the variable 'personality' which is nonzero when the pin or parameter should be created.
233245

234246
* 'SIZE' - A number that gives the size of an array. The array items are numbered from 0 to 'SIZE'-1.

src/hal/components/Submakefile

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,13 @@ obj-m += $(patsubst hal/drivers/%.comp, %.o, $(patsubst hal/components/%.comp, %
3232
$(COMP_MANPAGES): ../docs/man/man9/%.9: hal/components/%.comp ../bin/halcompile
3333
$(ECHO) Making halcompile manpage $(notdir $@)
3434
@mkdir -p $(dir $@)
35-
$(Q)../bin/halcompile -U --document -o $@.new $< && preconv -r < $@.new > $@
35+
$(Q)../bin/halcompile -N -U --document -o $@.new $< && preconv -r < $@.new > $@
3636
$(RM) $@.new
3737

3838
$(COMP_DRIVER_MANPAGES): ../docs/man/man9/%.9: hal/drivers/%.comp ../bin/halcompile
3939
$(ECHO) Making halcompile manpage $(notdir $@)
4040
@mkdir -p $(dir $@)
41-
$(Q)../bin/halcompile -U --document -o $@ $<
41+
$(Q)../bin/halcompile -N -U --document -o $@ $<
4242

4343
objects/%.mak: %.comp hal/components/Submakefile
4444
$(ECHO) "Creating $(notdir $@)"
@@ -47,10 +47,12 @@ objects/%.mak: %.comp hal/components/Submakefile
4747
$(Q)echo ../rtlib/$(notdir $*)$(MODULE_EXT): objects/rtobjects/$*.o >> $@.tmp
4848
$(Q)mv -f $@.tmp $@
4949

50+
# -N silences the reminder that declared names are mangled when exported;
51+
# in-tree HAL names are deliberate. It does not affect the collision check.
5052
objects/%.c: %.comp ../bin/halcompile
5153
$(ECHO) "Preprocessing $(notdir $<)"
5254
@mkdir -p $(dir $@)
53-
$(Q)../bin/halcompile -U -o $@ $<
55+
$(Q)../bin/halcompile -N -U -o $@ $<
5456

5557
modules: $(patsubst %.comp, objects/%.c, $(COMPS) $(COMP_DRIVERS))
5658

src/hal/utils/halcompile.g

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ def parse(filename):
139139
a, b = f.split("\n;;\n", 1)
140140
p = _parse('File', a + "\n\n", filename)
141141
if not p: raise SystemExit(1)
142+
warn_mangled_names(filename)
142143
if require_license:
143144
if not finddoc('license'):
144145
raise SystemExit("%s:0: License not specified" % filename)
@@ -151,13 +152,20 @@ deprecated = ['s32', 'u32']
151152

152153
def initialize():
153154
global functions, params, pins, comp_name, names, docs, variables
154-
global modparams, includes
155+
global modparams, includes, hal_pin_names, hal_funct_names, mangled_names
155156

156157
functions = []; params = []; pins = []; options = {}; variables = []
157158
modparams = []; docs = []; includes = [];
158159
comp_name = None
159160

160161
names = {}
162+
hal_pin_names = {}
163+
hal_funct_names = {}
164+
mangled_names = []
165+
166+
# Cleared by -N (--no-name-warnings). This silences warn_mangled_names() only;
167+
# a HAL name collision is always an error.
168+
warn_hal_names = True
161169

162170
def Warn(msg, *args):
163171
if args:
@@ -215,10 +223,42 @@ def check_name_ok(name):
215223
if name in names:
216224
Error("Duplicate item name %s" % name)
217225

226+
HALNAME_DOC = ("see HALNAME under 'Syntax' in the Halcompile HAL Component "
227+
"Generator documentation, "
228+
"https://linuxcnc.org/docs/html/hal/comp.html")
229+
230+
def to_hal_display(name):
231+
# to_hal() without the array-index expansion, so "x.##" stays readable
232+
return name.replace("_", "-").rstrip("-").rstrip(".")
233+
234+
def check_hal_name(seen, name):
235+
"""A declaration is a C identifier, but it is exported under a mangled HAL
236+
identifier. check_name_ok() only compares declared names, so two
237+
declarations that mangle to one HAL name compile cleanly and fail later, at
238+
loadrt, with "HAL: ERROR: duplicate variable"."""
239+
if name == "_": return # the unnamed singleton function
240+
hal_name = to_hal(name)
241+
if hal_name in seen:
242+
Error("'%s' and '%s' both export the HAL name '%s'; %s"
243+
% (seen[hal_name], name, hal_name, HALNAME_DOC))
244+
seen[hal_name] = name
245+
if to_hal_display(name) != name: # ignore the array-index expansion
246+
mangled_names.append(name)
247+
248+
def warn_mangled_names(filename):
249+
if not mangled_names or not warn_hal_names: return
250+
shown = ", ".join("%s -> %s" % (n, to_hal_display(n)) for n in mangled_names[:3])
251+
if len(mangled_names) > 3:
252+
shown += ", ... (%d more)" % (len(mangled_names) - 3)
253+
print("%s:0: Warning: %d declared name(s) are exported under a different "
254+
"HAL name: %s. Use the HAL name in HAL files, halcmd and halshow; %s"
255+
% (filename, len(mangled_names), shown, HALNAME_DOC), file=sys.stderr)
256+
218257
def pin(name, type, array, dir, doc, value, personality):
219258
checkarray(name, array)
220259
type = type2type(type)
221260
check_name_ok(name)
261+
check_hal_name(hal_pin_names, name)
222262
docs.append(('pin', name, type, array, dir, doc, value, personality))
223263
names[name] = None
224264
pins.append((name, type, array, dir, value, personality))
@@ -227,12 +267,15 @@ def param(name, type, array, dir, doc, value, personality):
227267
checkarray(name, array)
228268
type = type2type(type)
229269
check_name_ok(name)
270+
check_hal_name(hal_pin_names, name) # hal_lib.c: setp cannot tell a pin
271+
# and a param of one name apart
230272
docs.append(('param', name, type, array, dir, doc, value, personality))
231273
names[name] = None
232274
params.append((name, type, array, dir, value, personality))
233275

234276
def function(name, fp, doc):
235277
check_name_ok(name)
278+
check_hal_name(hal_funct_names, name)
236279
docs.append(('funct', name, fp, doc))
237280
names[name] = None
238281
functions.append((name, fp))
@@ -1124,6 +1167,10 @@ Usage:
11241167
Option to set maximum 'personalities' items:
11251168
--personalities=integer_value (default is %(dflt)d)
11261169
1170+
Option to suppress the warning about declared names that are exported under a
1171+
different HAL name:
1172+
-N, --no-name-warnings
1173+
11271174
Options to add compile and link flags (only for userspace, only for .c files)
11281175
--extra-compile-args="-I/usr/include/..."
11291176
--extra-link-args="-l..."
@@ -1138,14 +1185,16 @@ def main():
11381185
require_license = True
11391186
global require_unix_line_endings
11401187
require_unix_line_endings = False
1188+
global warn_hal_names
11411189
mode = PREPROCESS
11421190
outfile = None
11431191
userspace = False
11441192
global options
11451193
options = {}
11461194
try:
1147-
opts, args = getopt.getopt(sys.argv[1:], "Uluijcpdo:h?P:",
1148-
['unix', 'install', 'compile', 'preprocess', 'outfile=',
1195+
opts, args = getopt.getopt(sys.argv[1:], "NUluijcpdo:h?P:",
1196+
['unix', 'no-name-warnings', 'install', 'compile',
1197+
'preprocess', 'outfile=',
11491198
'document', 'help', 'userspace', 'install-doc',
11501199
'view-doc', 'require-license', 'print-modinc',
11511200
'personalities=', "extra-compile-args=",
@@ -1155,6 +1204,8 @@ def main():
11551204
for k, v in opts:
11561205
if k in ("-U", "--unix"):
11571206
require_unix_line_endings = True
1207+
if k in ("-N", "--no-name-warnings"):
1208+
warn_hal_names = False
11581209
if k in ("-u", "--userspace"):
11591210
userspace = True
11601211
if k in ("-i", "--install"):
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
halname_mangled.c

tests/halcompile/halname/expected

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
halname_mangled.comp:0: Warning: 1 declared name(s) are exported under a different HAL name: my_pin -> my-pin. Use the HAL name in HAL files, halcmd and halshow; see HALNAME under 'Syntax' in the Halcompile HAL Component Generator documentation, https://linuxcnc.org/docs/html/hal/comp.html
2+
halname_collision.comp:4:18: 'x_y' and 'x_y_' both export the HAL name 'x-y'; see HALNAME under 'Syntax' in the Halcompile HAL Component Generator documentation, https://linuxcnc.org/docs/html/hal/comp.html
3+
> pin out bit x_y_;
4+
> ^
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
component halname_collision;
2+
license "GPL";
3+
pin in bit x_y;
4+
pin out bit x_y_;
5+
function _;
6+
;;
7+
FUNCTION(_) {}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
component halname_mangled;
2+
license "GPL";
3+
pin in bit my_pin;
4+
function _;
5+
;;
6+
FUNCTION(_) {}

tests/halcompile/halname/test.sh

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#!/bin/bash
2+
set -e
3+
4+
# A declared name that is exported under a different HAL name must warn,
5+
# but must still compile.
6+
rm -f halname_mangled.c
7+
halcompile --preprocess halname_mangled.comp 2>&1
8+
test -f halname_mangled.c || echo 'halcompile failed to produce halname_mangled.c'
9+
10+
# -N silences that warning.
11+
rm -f halname_mangled.c
12+
halcompile -N --preprocess halname_mangled.comp 2>&1
13+
14+
# Two declarations that mangle to one HAL name must be rejected, not left
15+
# to fail at loadrt as "HAL: ERROR: duplicate variable".
16+
rm -f halname_collision.c
17+
if halcompile --preprocess halname_collision.comp 2>&1; then
18+
echo 'halcompile erroneously accepted halname_collision.comp'
19+
fi
20+
test ! -f halname_collision.c || echo 'halcompile erroneously produced halname_collision.c'

0 commit comments

Comments
 (0)