Skip to content

Commit 1d949d6

Browse files
committed
Detect setting locals with subshell commands
The return status of "local" is always 0, so something like local foo=$(failing command) doesn't trigger a failure under "set -e". Missing "failing command"'s failure has been a source of problems within devstack. This is added as a warning Change-Id: Ia0957b47187c3dcadd46154b17022c4213781112
1 parent e04cb43 commit 1d949d6

5 files changed

Lines changed: 40 additions & 3 deletions

File tree

README.rst

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,14 @@ unrolling that is kind of "interesting"
4040
- E012: heredoc didn't end before EOF
4141
- E020: Function declaration not in format ``^function name {$``
4242

43-
Obsolete and deprecated syntax
44-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
43+
Obsolete, deprecated or unsafe syntax
44+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4545

46-
Rules to identify obsolete and deprecated syntax that should not be used
46+
Rules to identify obsolete, deprecated or unsafe syntax that should
47+
not be used
4748

4849
- E041: Usage of $[ for arithmetic is deprecated for $((
50+
- E042: local declaration hides errors
4951

5052
See also
5153
~~~~~~~~

bashate/bashate.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,12 @@ def check_arithmetic(line, report):
9898
report.print_error(MESSAGES['E041'].msg, line)
9999

100100

101+
def check_local_subshell(line, report):
102+
if line.lstrip().startswith('local ') and \
103+
("=$(" in line or "=`" in line):
104+
report.print_error(MESSAGES['E042'].msg, line)
105+
106+
101107
def check_hashbang(line, filename, report):
102108
# this check only runs on the first line
103109
# maybe this should check for shell?
@@ -249,6 +255,7 @@ def check_files(self, files, verbose):
249255
check_if_then(logical_line, report)
250256
check_function_decl(logical_line, report)
251257
check_arithmetic(logical_line, report)
258+
check_local_subshell(logical_line, report)
252259

253260
prev_line = logical_line
254261
prev_lineno = fileinput.filelineno()

bashate/messages.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,16 @@ def msg(self):
154154
""",
155155
'default': 'E'
156156
},
157+
'E042': {
158+
'msg': 'local declaration hides errors',
159+
'long_msg':
160+
"""
161+
The return value of "local" is always 0; errors in subshells
162+
used for declaration are thus hidden and will not trigger "set -e".
163+
""",
164+
'default': 'W',
165+
},
166+
157167
}
158168

159169
MESSAGES = {}

bashate/tests/samples/E042_bad.sh

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
function hello {
2+
local foo=$(ls)
3+
}
4+
5+
function hello_too {
6+
local foo=`ls`
7+
}
8+
9+
hello
10+
hello_too

bashate/tests/test_bashate.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,14 @@ def test_sample_E041(self):
196196

197197
self.assert_error_found('E041', 4)
198198

199+
def test_sample_E042(self):
200+
test_files = ['bashate/tests/samples/E042_bad.sh']
201+
self.run.register_errors('E042')
202+
self.run.check_files(test_files, False)
203+
204+
self.assert_error_found('E042', 2)
205+
self.assert_error_found('E042', 6)
206+
199207
def test_sample_for_loops(self):
200208
test_files = ['bashate/tests/samples/for_loops.sh']
201209
self.run.check_files(test_files, False)

0 commit comments

Comments
 (0)