Skip to content

Commit 1188057

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 133 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 caa13ca commit 1188057

9 files changed

Lines changed: 128 additions & 6 deletions

File tree

docs/src/hal/comp.adoc

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

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

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

docs/src/man/man1/halcompile.1.adoc

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ this option and it has no effect when only asciidoc formatted documentation is
5757
requested using the *-a* or *--adoc* option.
5858
*-l*, *--require-license*::
5959
Obsolete. The component is always required to have a *licence* tag.
60+
*-N*, *--no-name-warnings*::
61+
Do not warn about declared names that are exported under a different HAL name.
62+
See *NAMES* below.
6063
*-o* _file_, *--outfile*=_file_::
6164
Write output to _file_. Can _only_ be used with *--preprocess*, *--adoc* and
6265
*--document* processing.
@@ -97,6 +100,22 @@ Extra arguments passed to the linker.
97100
require _sudo_ to write to system directories.
98101
* Preprocess *.comp* files into *.c* files (the *--preprocess* flag)
99102

103+
== NAMES
104+
105+
A name declared in a *.comp* file is a C identifier, but it is exported under a
106+
mangled HAL identifier: underscores become dashes, and a trailing dash or
107+
period is removed. A pin declared *pin in float my_input* is therefore reached
108+
from HAL as *component.N.my-input*, not *component.N.my_input*, and a component
109+
loaded with *loadrt my_comp* exports its pins under *my-comp.N.*.
110+
111+
*halcompile* prints one warning per file listing the names this applies to;
112+
pass *-N* to suppress it. Two declarations that mangle to the same HAL name
113+
(for example *x_y* and *x_y_*) are rejected, since they would otherwise be
114+
accepted here and fail later at *loadrt* with "HAL: ERROR: duplicate variable".
115+
116+
See HALNAME under _Syntax_ in the _Halcompile HAL Component Generator_
117+
documentation for the full mangling rules.
118+
100119
== SEE ALSO
101120

102121
* _Halcompile_ / _HAL Component Generator_ in the LinuxCNC documentation for a

src/hal/components/Submakefile

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,12 +67,12 @@ COMP_DRIVER_MANPAGE_ADOCS := $(patsubst hal/drivers/%.comp, objects/man/man9/%.9
6767
$(COMP_MANPAGE_ADOCS): objects/man/man9/%.9.adoc: hal/components/%.comp ../bin/halcompile
6868
$(ECHO) Extracting adoc manpage $(notdir $@)
6969
@mkdir -p $(dir $@)
70-
$(Q)../bin/halcompile -U --adoc -o $@ $<
70+
$(Q)../bin/halcompile -N -U --adoc -o $@ $<
7171

7272
$(COMP_DRIVER_MANPAGE_ADOCS): objects/man/man9/%.9.adoc: hal/drivers/%.comp ../bin/halcompile
7373
$(ECHO) Extracting adoc manpage $(notdir $@)
7474
@mkdir -p $(dir $@)
75-
$(Q)../bin/halcompile -U --adoc -o $@ $<
75+
$(Q)../bin/halcompile -N -U --adoc -o $@ $<
7676

7777
# Build troff from the adoc via asciidoctor. Used to be halcompile
7878
# emitting troff directly with sed post-processing to escape .als / .URL
@@ -99,10 +99,12 @@ objects/%.mak: %.comp hal/components/Submakefile
9999
$(Q)echo ../rtlib/$(notdir $*)$(MODULE_EXT): objects/rtobjects/$*.o >> $@.tmp
100100
$(Q)mv -f $@.tmp $@
101101

102+
# -N silences the reminder that declared names are mangled when exported;
103+
# in-tree HAL names are deliberate. It does not affect the collision check.
102104
objects/%.c: %.comp ../bin/halcompile
103105
$(ECHO) "Preprocessing $(notdir $<)"
104106
@mkdir -p $(dir $@)
105-
$(Q)../bin/halcompile -U -o $@ $<
107+
$(Q)../bin/halcompile -N -U -o $@ $<
106108

107109
modules: $(patsubst %.comp, objects/%.c, $(COMPS) $(COMP_DRIVERS))
108110

src/hal/utils/halcompile.g

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ def parse(filename):
140140
a, b = f.split("\n;;\n", 1)
141141
p = _parse('File', a + "\n\n", filename)
142142
if not p: raise SystemExit(1)
143+
warn_mangled_names(filename)
143144
if require_license:
144145
if not finddoc('license'):
145146
raise SystemExit("%s:0: License not specified" % filename)
@@ -157,13 +158,20 @@ newtypes = ['bool', 'sint', 'uint', 'si32', 'ui32', 'real']
157158

158159
def initialize():
159160
global functions, params, pins, comp_name, names, docs, variables
160-
global modparams, includes
161+
global modparams, includes, hal_pin_names, hal_funct_names, mangled_names
161162

162163
functions = []; params = []; pins = []; options = {}; variables = []
163164
modparams = []; docs = []; includes = [];
164165
comp_name = None
165166

166167
names = {}
168+
hal_pin_names = {}
169+
hal_funct_names = {}
170+
mangled_names = []
171+
172+
# Cleared by -N (--no-name-warnings). This silences warn_mangled_names() only;
173+
# a HAL name collision is always an error.
174+
warn_hal_names = True
167175

168176
def Warn(msg, *args):
169177
if args:
@@ -225,10 +233,42 @@ def check_name_ok(name):
225233
if name in names:
226234
Error("Duplicate item name %s" % name)
227235

236+
HALNAME_DOC = ("see HALNAME under 'Syntax' in the Halcompile HAL Component "
237+
"Generator documentation, "
238+
"https://linuxcnc.org/docs/html/hal/comp.html")
239+
240+
def to_hal_display(name):
241+
# to_hal() without the array-index expansion, so "x.##" stays readable
242+
return name.replace("_", "-").rstrip("-").rstrip(".")
243+
244+
def check_hal_name(seen, name):
245+
"""A declaration is a C identifier, but it is exported under a mangled HAL
246+
identifier. check_name_ok() only compares declared names, so two
247+
declarations that mangle to one HAL name compile cleanly and fail later, at
248+
loadrt, with "HAL: ERROR: duplicate variable"."""
249+
if name == "_": return # the unnamed singleton function
250+
hal_name = to_hal(name)
251+
if hal_name in seen:
252+
Error("'%s' and '%s' both export the HAL name '%s'; %s"
253+
% (seen[hal_name], name, hal_name, HALNAME_DOC))
254+
seen[hal_name] = name
255+
if to_hal_display(name) != name: # ignore the array-index expansion
256+
mangled_names.append(name)
257+
258+
def warn_mangled_names(filename):
259+
if not mangled_names or not warn_hal_names: return
260+
shown = ", ".join("%s -> %s" % (n, to_hal_display(n)) for n in mangled_names[:3])
261+
if len(mangled_names) > 3:
262+
shown += ", ... (%d more)" % (len(mangled_names) - 3)
263+
print("%s:0: Warning: %d declared name(s) are exported under a different "
264+
"HAL name: %s. Use the HAL name in HAL files, halcmd and halshow; %s"
265+
% (filename, len(mangled_names), shown, HALNAME_DOC), file=sys.stderr)
266+
228267
def pin(name, type_, array, dir_, doc, value, personality):
229268
checkarray(name, array)
230269
type_ = type2type(type_)
231270
check_name_ok(name)
271+
check_hal_name(hal_pin_names, name)
232272
docs.append(('pin', name, type_, array, dir_, doc, value, personality))
233273
names[name] = None
234274
pins.append((name, type_, array, dir_, value, personality))
@@ -237,12 +277,15 @@ def param(name, type_, array, dir_, doc, value, personality):
237277
checkarray(name, array)
238278
type_ = type2type(type_)
239279
check_name_ok(name)
280+
check_hal_name(hal_pin_names, name) # hal_lib.c: setp cannot tell a pin
281+
# and a param of one name apart
240282
docs.append(('param', name, type_, array, dir_, doc, value, personality))
241283
names[name] = None
242284
params.append((name, type_, array, dir_, value, personality))
243285

244286
def function(name, fp, doc):
245287
check_name_ok(name)
288+
check_hal_name(hal_funct_names, name)
246289
docs.append(('funct', name, fp, doc))
247290
names[name] = None
248291
functions.append((name, fp))
@@ -1238,6 +1281,10 @@ Usage:
12381281
Option to set maximum 'personalities' items:
12391282
--personalities=integer_value (default is %(dflt)d)
12401283
1284+
Option to suppress the warning about declared names that are exported under a
1285+
different HAL name:
1286+
-N, --no-name-warnings
1287+
12411288
Options to add compile and link flags (only for userspace, only for .c files)
12421289
--extra-compile-args="-I/usr/include/..."
12431290
--extra-link-args="-l..."
@@ -1254,6 +1301,7 @@ def main():
12541301
require_license = True
12551302
global require_unix_line_endings
12561303
require_unix_line_endings = False
1304+
global warn_hal_names
12571305
mode = PREPROCESS
12581306
adoc = False
12591307
keepadoc = None
@@ -1262,8 +1310,9 @@ def main():
12621310
global options
12631311
options = {}
12641312
try:
1265-
opts, args = getopt.getopt(sys.argv[1:], "UluijJcpdak:o:h?P:",
1266-
['unix', 'install', 'compile', 'preprocess', 'outfile=',
1313+
opts, args = getopt.getopt(sys.argv[1:], "NUluijJcpdak:o:h?P:",
1314+
['unix', 'no-name-warnings', 'install', 'compile',
1315+
'preprocess', 'outfile=',
12671316
'document', 'adoc', 'keep-adoc=', 'help', 'userspace', 'install-doc',
12681317
'view-doc', 'require-license', 'print-modinc',
12691318
'personalities=', "extra-compile-args=",
@@ -1273,6 +1322,8 @@ def main():
12731322
for k, v in opts:
12741323
if k in ("-U", "--unix"):
12751324
require_unix_line_endings = True
1325+
if k in ("-N", "--no-name-warnings"):
1326+
warn_hal_names = False
12761327
if k in ("-u", "--userspace"):
12771328
userspace = True
12781329
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)