/
expand.rs
1592 lines (1471 loc) · 61.9 KB
/
expand.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
//! String expansion functions. These functions perform several kinds of parameter expansion. There
//! are a lot of issues with regards to memory allocation. Overall, these functions would benefit
//! from using a more clever memory allocation scheme, perhaps an evil combination of talloc,
//! string buffers and reference counting.
use crate::builtins::shared::{
STATUS_CMD_ERROR, STATUS_CMD_UNKNOWN, STATUS_EXPAND_ERROR, STATUS_ILLEGAL_CMD,
STATUS_INVALID_ARGS, STATUS_NOT_EXECUTABLE, STATUS_READ_TOO_MUCH, STATUS_UNMATCHED_WILDCARD,
};
use crate::common::{
char_offset, charptr2wcstring, escape, escape_string, escape_string_for_double_quotes,
unescape_string, valid_var_name_char, wcs2zstring, EscapeFlags, EscapeStringStyle,
UnescapeFlags, UnescapeStringStyle, EXPAND_RESERVED_BASE, EXPAND_RESERVED_END,
};
use crate::complete::{CompleteFlags, Completion, CompletionList, CompletionReceiver};
use crate::env::{EnvVar, Environment};
use crate::exec::exec_subshell_for_expand;
use crate::future_feature_flags::{feature_test, FeatureFlag};
use crate::history::{history_session_id, History};
use crate::operation_context::OperationContext;
use crate::parse_constants::{ParseError, ParseErrorCode, ParseErrorList, SOURCE_LOCATION_UNKNOWN};
use crate::parse_util::{parse_util_expand_variable_error, parse_util_locate_cmdsubst_range};
use crate::path::path_apply_working_directory;
use crate::util::wcsfilecmp_glob;
use crate::wchar::prelude::*;
use crate::wcstringutil::{join_strings, trim};
use crate::wildcard::{wildcard_expand_string, wildcard_has_internal};
use crate::wildcard::{WildcardResult, ANY_CHAR, ANY_STRING, ANY_STRING_RECURSIVE};
use crate::wutil::{normalize_path, wcstoi_partial, Options};
use bitflags::bitflags;
bitflags! {
/// Set of flags controlling expansions.
#[derive(Copy, Clone, Default)]
pub struct ExpandFlags : u16 {
/// Fail expansion if there is a command substitution.
const FAIL_ON_CMDSUBST = 1 << 0;
/// Skip command substitutions.
const SKIP_CMDSUBST = 1 << 14;
/// Skip variable expansion.
const SKIP_VARIABLES = 1 << 1;
/// Skip wildcard expansion.
const SKIP_WILDCARDS = 1 << 2;
/// The expansion is being done for tab or auto completions. Returned completions may have the
/// wildcard as a prefix instead of a match.
const FOR_COMPLETIONS = 1 << 3;
/// Only match files that are executable by the current user.
const EXECUTABLES_ONLY = 1 << 4;
/// Only match directories.
const DIRECTORIES_ONLY = 1 << 5;
/// Generate descriptions, stored in the description field of completions.
const GEN_DESCRIPTIONS = 1 << 6;
/// Un-expand home directories to tildes after.
const PRESERVE_HOME_TILDES = 1 << 7;
/// Allow fuzzy matching.
const FUZZY_MATCH = 1 << 8;
/// Disallow directory abbreviations like /u/l/b for /usr/local/bin. Only applicable if
/// fuzzy_match is set.
const NO_FUZZY_DIRECTORIES = 1 << 9;
/// Allows matching a leading dot even if the wildcard does not contain one.
/// By default, wildcards only match a leading dot literally; this is why e.g. '*' does not
/// match hidden files.
const ALLOW_NONLITERAL_LEADING_DOT = 1 << 10;
/// Do expansions specifically to support cd. This means using CDPATH as a list of potential
/// working directories, and to use logical instead of physical paths.
const SPECIAL_FOR_CD = 1 << 11;
/// Do expansions specifically for cd autosuggestion. This is to differentiate between cd
/// completions and cd autosuggestions.
const SPECIAL_FOR_CD_AUTOSUGGESTION = 1 << 12;
/// Do expansions specifically to support external command completions. This means using PATH as
/// a list of potential working directories.
const SPECIAL_FOR_COMMAND = 1 << 13;
}
}
/// Character representing a home directory.
pub const HOME_DIRECTORY: char = char_offset(EXPAND_RESERVED_BASE, 0);
/// Character representing process expansion for %self.
pub const PROCESS_EXPAND_SELF: char = char_offset(EXPAND_RESERVED_BASE, 1);
/// Character representing variable expansion.
pub const VARIABLE_EXPAND: char = char_offset(EXPAND_RESERVED_BASE, 2);
/// Character representing variable expansion into a single element.
pub const VARIABLE_EXPAND_SINGLE: char = char_offset(EXPAND_RESERVED_BASE, 3);
/// Character representing the start of a bracket expansion.
pub const BRACE_BEGIN: char = char_offset(EXPAND_RESERVED_BASE, 4);
/// Character representing the end of a bracket expansion.
pub const BRACE_END: char = char_offset(EXPAND_RESERVED_BASE, 5);
/// Character representing separation between two bracket elements.
pub const BRACE_SEP: char = char_offset(EXPAND_RESERVED_BASE, 6);
/// Character that takes the place of any whitespace within non-quoted text in braces
pub const BRACE_SPACE: char = char_offset(EXPAND_RESERVED_BASE, 7);
/// Separate subtokens in a token with this character.
pub const INTERNAL_SEPARATOR: char = char_offset(EXPAND_RESERVED_BASE, 8);
/// Character representing an empty variable expansion. Only used transitively while expanding
/// variables.
pub const VARIABLE_EXPAND_EMPTY: char = char_offset(EXPAND_RESERVED_BASE, 9);
const _: () = assert!(
EXPAND_RESERVED_END as u32 > VARIABLE_EXPAND_EMPTY as u32,
"Characters used in expansions must stay within private use area"
);
impl ExpandResult {
pub fn new(result: ExpandResultCode) -> Self {
Self { result, status: 0 }
}
pub fn ok() -> Self {
Self::new(ExpandResultCode::ok)
}
/// Make an error value with the given status.
pub fn make_error(status: libc::c_int) -> Self {
assert!(status != 0, "status cannot be 0 for an error result");
Self {
result: ExpandResultCode::error,
status,
}
}
}
impl PartialEq<ExpandResultCode> for ExpandResult {
fn eq(&self, other: &ExpandResultCode) -> bool {
self.result == *other
}
}
/// The string represented by PROCESS_EXPAND_SELF
pub const PROCESS_EXPAND_SELF_STR: &wstr = L!("%self");
/// Perform various forms of expansion on in, such as tilde expansion (\~USER becomes the users home
/// directory), variable expansion (\$VAR_NAME becomes the value of the environment variable
/// VAR_NAME), cmdsubst expansion and wildcard expansion. The results are inserted into the list
/// out.
///
/// If the parameter does not need expansion, it is copied into the list out.
///
/// \param input The parameter to expand
/// \param output The list to which the result will be appended.
/// \param ctx The parser, variables, and cancellation checker for this operation. The parser may
/// be null. \param errors Resulting errors, or nullptr to ignore
///
/// \return An expand_result_t.
/// wildcard_no_match and wildcard_match are normal exit conditions used only on
/// strings containing wildcards to tell if the wildcard produced any matches.
pub fn expand_string(
input: WString,
out_completions: &mut CompletionList,
flags: ExpandFlags,
ctx: &OperationContext,
errors: Option<&mut ParseErrorList>,
) -> ExpandResult {
let mut completions = vec![];
std::mem::swap(&mut completions, out_completions);
let mut recv = CompletionReceiver::from_list(completions, ctx.expansion_limit);
let result = expand_to_receiver(input, &mut recv, flags, ctx, errors);
*out_completions = recv.take();
result
}
/// Variant of string that inserts its results into a completion_receiver_t.
pub fn expand_to_receiver(
input: WString,
out_completions: &mut CompletionReceiver,
flags: ExpandFlags,
ctx: &OperationContext,
errors: Option<&mut ParseErrorList>,
) -> ExpandResult {
Expander::expand_string(input, out_completions, flags, ctx, errors)
}
/// expand_one is identical to expand_string, except it will fail if in expands to more than one
/// string. This is used for expanding command names.
///
/// \param inout_str The parameter to expand in-place
/// \param ctx The parser, variables, and cancellation checker for this operation. The parser may be
/// null.
/// \param errors Resulting errors, or nullptr to ignore
///
/// \return Whether expansion succeeded.
pub fn expand_one(
s: &mut WString,
flags: ExpandFlags,
ctx: &OperationContext,
errors: Option<&mut ParseErrorList>,
) -> bool {
let mut completions = CompletionList::new();
if !flags.contains(ExpandFlags::FOR_COMPLETIONS) && expand_is_clean(s) {
return true;
}
let mut tmp = WString::new();
std::mem::swap(s, &mut tmp);
if expand_string(tmp, &mut completions, flags, ctx, errors) == ExpandResultCode::ok
&& completions.len() == 1
{
std::mem::swap(s, &mut completions[0].completion);
return true;
}
false
}
/// Expand a command string like $HOME/bin/cmd into a command and list of arguments.
/// Return the command and arguments by reference.
/// If the expansion resulted in no or an empty command, the command will be an empty string. Note
/// that API does not distinguish between expansion resulting in an empty command (''), and
/// expansion resulting in no command (e.g. unset variable).
/// If \p skip_wildcards is true, then do not do wildcard expansion
/// \return an expand error.
pub fn expand_to_command_and_args(
instr: &wstr,
ctx: &OperationContext<'_>,
out_cmd: &mut WString,
mut out_args: Option<&mut Vec<WString>>,
errors: Option<&mut ParseErrorList>,
skip_wildcards: bool,
) -> ExpandResult {
// Fast path.
if expand_is_clean(instr) {
*out_cmd = instr.to_owned();
return ExpandResult::ok();
}
let mut eflags = ExpandFlags::FAIL_ON_CMDSUBST;
if skip_wildcards {
eflags |= ExpandFlags::SKIP_WILDCARDS;
}
let mut completions = CompletionList::new();
let expand_err = expand_string(instr.to_owned(), &mut completions, eflags, ctx, errors);
if expand_err == ExpandResultCode::ok {
// The first completion is the command, any remaining are arguments.
let mut completions = completions.into_iter();
if let Some(comp) = completions.next() {
*out_cmd = comp.completion;
}
if let Some(ref mut out_args) = out_args {
for comp in completions {
out_args.push(comp.completion);
}
}
}
expand_err
}
/// Convert the variable value to a human readable form, i.e. escape things, handle arrays, etc.
/// Suitable for pretty-printing.
pub fn expand_escape_variable(var: &EnvVar) -> WString {
let mut buff = WString::new();
let lst = var.as_list();
for el in lst {
if !buff.is_empty() {
buff.push_str(" ");
}
// We want to use quotes if we have more than one string, or the string contains a space.
let prefer_quotes = lst.len() > 1 || el.contains(' ');
if prefer_quotes && is_quotable(el) {
buff.push('\'');
buff.push_utfstr(el);
buff.push('\'');
} else {
buff.push_utfstr(&escape(el));
}
}
buff
}
/// Convert a string value to a human readable form, i.e. escape things, handle arrays, etc.
/// Suitable for pretty-printing.
pub fn expand_escape_string(el: &wstr) -> WString {
let mut buff = WString::new();
let prefer_quotes = el.contains(' ');
if prefer_quotes && is_quotable(el) {
buff.push('\'');
buff.push_utfstr(el);
buff.push('\'');
} else {
buff.push_utfstr(&escape(el));
}
buff
}
/// Perform tilde expansion and nothing else on the specified string, which is modified in place.
///
/// \param input the string to tilde expand
pub fn expand_tilde(input: &mut WString, vars: &dyn Environment) {
if input.chars().next() == Some('~') {
input.replace_range(0..1, wstr::from_char_slice(&[HOME_DIRECTORY]));
expand_home_directory(input, vars);
}
}
/// Perform the opposite of tilde expansion on the string, which is modified in place.
pub fn replace_home_directory_with_tilde(s: &wstr, vars: &dyn Environment) -> WString {
let mut result = s.to_owned();
// Only absolute paths get this treatment.
if result.starts_with(L!("/")) {
let mut home_directory = L!("~").to_owned();
expand_tilde(&mut home_directory, vars);
// If we can't get a home directory, don't replace anything.
// This is the case e.g. with --no-execute
if home_directory.is_empty() {
return result;
}
if !home_directory.ends_with(L!("/")) {
home_directory.push('/');
}
// Now check if the home_directory prefixes the string.
if result.starts_with(&home_directory) {
// Success
result.replace_range(0..home_directory.len(), L!("~/"));
}
}
result
}
/// Characters which make a string unclean if they are the first character of the string. See \c
/// expand_is_clean().
const UNCLEAN_FIRST: &wstr = L!("~%");
/// Unclean characters. See \c expand_is_clean().
const UNCLEAN: &wstr = L!("$*?\\\"'({})");
/// Test if the specified argument is clean, i.e. it does not contain any tokens which need to be
/// expanded or otherwise altered. Clean strings can be passed through expand_string and expand_one
/// without changing them. About two thirds of all strings are clean, so skipping expansion on them
/// actually does save a small amount of time, since it avoids multiple memory allocations during
/// the expansion process.
///
/// \param in the string to test
fn expand_is_clean(input: &wstr) -> bool {
if input.is_empty() {
return true;
}
// Test characters that have a special meaning in the first character position.
if UNCLEAN_FIRST.contains(input.as_char_slice()[0]) {
return false;
}
// Test characters that have a special meaning in any character position.
!input.chars().any(|c| UNCLEAN.contains(c))
}
/// Append a syntax error to the given error list.
macro_rules! append_syntax_error {
(
$errors:expr, $source_start:expr,
$fmt:expr $(, $arg:expr )* $(,)?
) => {
if let Some(ref mut errors) = $errors {
let mut error = ParseError::default();
error.source_start = $source_start;
error.source_length = 0;
error.code = ParseErrorCode::syntax;
error.text = wgettext_maybe_fmt!($fmt $(, $arg)*);
errors.push(error);
}
}
}
/// Append a cmdsub error to the given error list. But only do so if the error hasn't already been
/// recorded. This is needed because command substitution is a recursive process and some errors
/// could consequently be recorded more than once.
macro_rules! append_cmdsub_error {
(
$errors:expr, $source_start:expr, $source_end:expr,
$fmt:expr $(, $arg:expr )* $(,)?
) => {
append_cmdsub_error_formatted!(
$errors, $source_start, $source_end,
wgettext_maybe_fmt!($fmt $(, $arg)*));
}
}
macro_rules! append_cmdsub_error_formatted {
(
$errors:expr, $source_start:expr, $source_end:expr,
$text:expr $(,)?
) => {
if let Some(ref mut errors) = $errors {
let mut error = ParseError::default();
error.source_start = $source_start;
error.source_length = $source_end - $source_start + 1;
error.code = ParseErrorCode::cmdsubst;
error.text = $text;
if !errors.iter().any(|e| e.text == error.text) {
errors.push(error);
}
}
};
}
/// Append an overflow error, when expansion produces too much data.
fn append_overflow_error(
errors: &mut Option<&mut ParseErrorList>,
source_start: Option<usize>,
) -> ExpandResult {
if let Some(ref mut errors) = errors {
let mut error = ParseError::default();
error.source_start = source_start.unwrap_or(SOURCE_LOCATION_UNKNOWN);
error.source_length = 0;
error.code = ParseErrorCode::generic;
error.text = wgettext!("Expansion produced too many results").to_owned();
errors.push(error);
}
ExpandResult::make_error(STATUS_EXPAND_ERROR.unwrap())
}
/// Test if the specified string does not contain character which can not be used inside a quoted
/// string.
fn is_quotable(s: &wstr) -> bool {
!s.chars().any(|c| "\n\t\r\x08\x1B".contains(c))
}
enum ParseSliceError {
zero_index,
invalid_index,
}
/// Parse an array slicing specification Returns 0 on success. If a parse error occurs, returns the
/// index of the bad token. Note that 0 can never be a bad index because the string always starts
/// with [.
fn parse_slice(
input: &wstr,
idx: &mut Vec<i64>,
array_size: usize,
) -> Result<usize, (usize, ParseSliceError)> {
let size = i64::try_from(array_size).unwrap();
let mut pos = 1; // skip past the opening square bracket
loop {
while input.char_at(pos).is_whitespace() || input.char_at(pos) == INTERNAL_SEPARATOR {
pos += 1;
}
if input.char_at(pos) == ']' {
pos += 1;
break;
}
let tmp = if idx.is_empty() && input.char_at(pos) == '.' && input.char_at(pos + 1) == '.' {
// If we are at the first index expression, a missing start-index means the range starts
// at the first item.
1 // first index
} else {
let mut consumed = 0;
match wcstoi_partial(&input[pos..], Options::default(), &mut consumed) {
Ok(tmp) => {
if tmp == 0 {
// Explicitly refuse $foo[0] as valid syntax, regardless of whether or
// not we're going to show an error if the index ultimately evaluates
// to zero. This will help newcomers to fish avoid a common off-by-one
// error. See #4862.
return Err((pos, ParseSliceError::zero_index));
}
pos += consumed;
// Skip trailing whitespace.
pos += input[pos..]
.chars()
.take_while(|c| c.is_whitespace())
.count();
tmp
}
Err(_error) => {
// We don't test `*end` as is typically done because we expect it to not
// be the null char. Ignore the case of errno==-1 because it means the end
// char wasn't the null char.
return Err((pos, ParseSliceError::invalid_index));
}
}
};
let mut i1 = if tmp > -1 { tmp } else { size + tmp + 1 };
while input.char_at(pos) == INTERNAL_SEPARATOR {
pos += 1;
}
if input.char_at(pos) == '.' && input.char_at(pos + 1) == '.' {
pos += 2;
while input.char_at(pos) == INTERNAL_SEPARATOR {
pos += 1;
}
while input.char_at(pos).is_whitespace() {
pos += 1; // Allow the space in "[.. ]".
}
// If we are at the last index range expression then a missing end-index means the
// range spans until the last item.
let tmp1 = if input.char_at(pos) == ']' {
-1 // last index
} else {
let mut consumed = 0;
match wcstoi_partial(&input[pos..], Options::default(), &mut consumed) {
Ok(tmp) => {
if tmp == 0 {
return Err((pos, ParseSliceError::zero_index));
}
pos += consumed;
// Skip trailing whitespace.
pos += input[pos..]
.chars()
.take_while(|c| c.is_whitespace())
.count();
tmp
}
Err(_error) => {
return Err((pos, ParseSliceError::invalid_index));
}
}
};
let mut i2 = if tmp1 > -1 { tmp1 } else { size + tmp1 + 1 };
// Skip sequences that are entirely outside.
// This means "17..18" expands to nothing if there are less than 17 elements.
if i1 > size && i2 > size {
continue;
}
let mut direction = if i2 < i1 { -1 } else { 1 };
// If only the beginning is negative, always go reverse.
// If only the end, always go forward.
// Prevents `[x..-1]` from going reverse if less than x elements are there.
if (tmp1 > -1) != (tmp > -1) {
direction = if tmp1 > -1 { -1 } else { 1 };
} else {
// Clamp to array size when not forcing direction
// - otherwise "2..-1" clamps both to 1 and then becomes "1..1".
i1 = i1.min(size);
i2 = i2.min(size);
}
let mut jjj = i1;
while jjj * direction <= i2 * direction {
idx.push(jjj);
jjj += direction;
}
continue;
}
idx.push(i1);
}
Ok(pos)
}
/// Expand all environment variables in the string *ptr.
///
/// This function is slow, fragile and complicated. There are lots of little corner cases, like
/// $$foo should do a double expansion, $foo$bar should not double expand bar, etc.
///
/// This function operates on strings backwards, starting at last_idx.
///
/// Note: last_idx is considered to be where it previously finished processing. This means it
/// actually starts operating on last_idx-1. As such, to process a string fully, pass string.size()
/// as last_idx instead of string.size()-1.
///
/// \return the result of expansion.
fn expand_variables(
instr: WString,
out: &mut CompletionReceiver,
last_idx: usize,
vars: &dyn Environment,
errors: &mut Option<&mut ParseErrorList>,
) -> ExpandResult {
// last_idx may be 1 past the end of the string, but no further.
assert!(last_idx <= instr.len(), "Invalid last_idx");
if last_idx == 0 {
if !out.add(instr) {
return append_overflow_error(errors, None);
}
return ExpandResult::ok();
}
// Locate the last VARIABLE_EXPAND or VARIABLE_EXPAND_SINGLE
let mut is_single = false;
let mut varexp_char_idx = last_idx;
loop {
let done = varexp_char_idx == 0;
varexp_char_idx = varexp_char_idx.wrapping_sub(1);
if done {
break;
}
let c = instr.as_char_slice()[varexp_char_idx];
if [VARIABLE_EXPAND, VARIABLE_EXPAND_SINGLE].contains(&c) {
is_single = c == VARIABLE_EXPAND_SINGLE;
break;
}
}
if varexp_char_idx == usize::MAX {
// No variable expand char, we're done.
if !out.add(instr) {
return append_overflow_error(errors, None);
}
return ExpandResult::ok();
}
// Get the variable name.
let var_name_start = varexp_char_idx + 1;
let mut var_name_stop = var_name_start;
while var_name_stop < instr.len() {
let nc = instr.as_char_slice()[var_name_stop];
if nc == VARIABLE_EXPAND_EMPTY {
var_name_stop += 1;
break;
}
if !valid_var_name_char(nc) {
break;
}
var_name_stop += 1;
}
assert!(
var_name_stop >= var_name_start,
"Bogus variable name indexes"
);
// Get the variable name as a string, then try to get the variable from env.
let var_name = &instr[var_name_start..var_name_stop];
// It's an error if the name is empty.
if var_name.is_empty() {
if let Some(ref mut errors) = errors {
parse_util_expand_variable_error(
&instr,
0, /* global_token_pos */
varexp_char_idx,
errors,
);
}
return ExpandResult::make_error(STATUS_EXPAND_ERROR.unwrap());
}
// Do a dirty hack to make sliced history fast (#4650). We expand from either a variable, or a
// history_t. Note that "history" is read only in env.cpp so it's safe to special-case it in
// this way (it cannot be shadowed, etc).
let mut history = None;
let mut var = None;
if var_name == "history" {
history = Some(History::with_name(&history_session_id(vars)));
} else if var_name.as_char_slice() != [VARIABLE_EXPAND_EMPTY] {
var = vars.get(var_name);
}
// Parse out any following slice.
// Record the end of the variable name and any following slice.
let mut var_name_and_slice_stop = var_name_stop;
let mut all_values = true;
let slice_start = var_name_stop;
let mut var_idx_list = vec![];
if instr.as_char_slice().get(slice_start) == Some(&'[') {
all_values = false;
// If a variable is missing, behave as though we have one value, so that $var[1] always
// works.
let mut effective_val_count = 1;
if let Some(ref var) = var {
effective_val_count = var.as_list().len();
} else if let Some(ref history) = history {
effective_val_count = history.size();
}
match parse_slice(
&instr[slice_start..],
&mut var_idx_list,
effective_val_count,
) {
Ok(offset) => {
var_name_and_slice_stop = slice_start + offset;
}
Err((bad_pos, error)) => {
match error {
ParseSliceError::zero_index => {
append_syntax_error!(
errors,
slice_start + bad_pos,
"array indices start at 1, not 0."
);
}
ParseSliceError::invalid_index => {
append_syntax_error!(errors, slice_start + bad_pos, "Invalid index value");
}
}
return ExpandResult::make_error(STATUS_EXPAND_ERROR.unwrap());
}
}
}
let var_idx_list = var_idx_list.iter().filter_map(|&n| n.try_into().ok());
if var.is_none() && history.is_none() {
// Expanding a non-existent variable.
if !is_single {
// Normal expansions of missing variables successfully expand to nothing.
return ExpandResult::ok();
} else {
// Expansion to single argument.
// Replace the variable name and slice with VARIABLE_EXPAND_EMPTY.
let mut res = instr[..varexp_char_idx].to_owned();
if res.as_char_slice().last() == Some(&VARIABLE_EXPAND_SINGLE) {
res.push(VARIABLE_EXPAND_EMPTY);
}
res.push_utfstr(&instr[var_name_and_slice_stop..]);
return expand_variables(res, out, varexp_char_idx, vars, errors);
}
}
// Ok, we have a variable or a history. Let's expand it.
// Start by respecting the sliced elements.
assert!(
var.is_some() || history.is_some(),
"Should have variable or history here",
);
let mut var_item_list = vec![];
if all_values {
var_item_list = if let Some(ref history) = history {
history.get_history()
} else {
var.as_ref().unwrap().as_list().to_vec()
};
} else {
// We have to respect the slice.
if let Some(ref history) = history {
// Ask history to map indexes to item strings.
// Note this may have missing entries for out-of-bounds.
let item_map = history.items_at_indexes(var_idx_list.clone());
for item_index in var_idx_list {
if let Some(item) = item_map.get(&item_index) {
var_item_list.push(item.clone());
}
}
} else {
let all_var_items = var.as_ref().unwrap().as_list();
for item_index in var_idx_list {
// Check that we are within array bounds. If not, skip the element. Note:
// Negative indices (`echo $foo[-1]`) are already converted to positive ones
// here, So tmp < 1 means it's definitely not in.
// Note we are 1-based.
if item_index >= 1 && item_index <= all_var_items.len() {
var_item_list.push(all_var_items[item_index - 1].to_owned());
}
}
}
}
if is_single {
// Quoted expansion. Here we expect the variable's delimiter.
// Note history always has a space delimiter.
let delimit = if history.is_some() {
' '
} else {
var.as_ref().unwrap().get_delimiter()
};
let mut res = instr[..varexp_char_idx].to_owned();
if !res.is_empty() {
if res.as_char_slice().last() != Some(&VARIABLE_EXPAND_SINGLE) {
res.push(INTERNAL_SEPARATOR);
} else if var_item_list.is_empty() || var_item_list[0].is_empty() {
// First expansion is empty, but we need to recursively expand.
res.push(VARIABLE_EXPAND_EMPTY);
}
}
// Append all entries in var_item_list, separated by the delimiter.
res.push_utfstr(&join_strings(&var_item_list, delimit));
res.push_utfstr(&instr[var_name_and_slice_stop..]);
return expand_variables(res, out, varexp_char_idx, vars, errors);
} else {
// Normal cartesian-product expansion.
for item in var_item_list {
if varexp_char_idx == 0 && var_name_and_slice_stop == instr.len() {
if !out.add(item) {
return append_overflow_error(errors, None);
}
} else {
let mut new_in = instr[..varexp_char_idx].to_owned();
if !new_in.is_empty() {
if new_in.as_char_slice().last() != Some(&VARIABLE_EXPAND) {
new_in.push(INTERNAL_SEPARATOR);
} else if item.is_empty() {
new_in.push(VARIABLE_EXPAND_EMPTY);
}
}
new_in.push_utfstr(&item);
new_in.push_utfstr(&instr[var_name_and_slice_stop..]);
let res = expand_variables(new_in, out, varexp_char_idx, vars, errors);
if res.result != ExpandResultCode::ok {
return res;
}
}
}
}
ExpandResult::ok()
}
/// Perform brace expansion, placing the expanded strings into \p out.
fn expand_braces(
input: WString,
flags: ExpandFlags,
out: &mut CompletionReceiver,
errors: &mut Option<&mut ParseErrorList>,
) -> ExpandResult {
let mut syntax_error = false;
let mut brace_count = 0;
let mut brace_begin = None;
let mut brace_end = None;
let mut last_sep = None;
// Locate the first non-nested brace pair.
for (pos, c) in input.chars().enumerate() {
match c {
BRACE_BEGIN => {
if brace_count == 0 {
brace_begin = Some(pos);
}
brace_count += 1;
}
BRACE_END => {
brace_count -= 1;
#[allow(clippy::comparison_chain)]
if brace_count < 0 {
syntax_error = true;
} else if brace_count == 0 {
brace_end = Some(pos);
}
}
BRACE_SEP => {
if brace_count == 1 {
last_sep = Some(pos);
}
}
_ => {
// we ignore all other characters here
}
}
}
if brace_count > 0 {
if !flags.contains(ExpandFlags::FOR_COMPLETIONS) {
syntax_error = true;
} else {
// The user hasn't typed an end brace yet; make one up and append it, then expand
// that.
let mut synth = WString::new();
if let Some(last_sep) = last_sep {
synth.push_utfstr(&input[..brace_begin.unwrap() + 1]);
synth.push_utfstr(&input[last_sep + 1..]);
synth.push(BRACE_END);
} else {
synth.push_utfstr(&input);
synth.push(BRACE_END);
}
// Note: this code looks very fishy, apparently it has never worked.
return expand_braces(synth, ExpandFlags::FAIL_ON_CMDSUBST, out, errors);
}
}
if syntax_error {
append_syntax_error!(errors, SOURCE_LOCATION_UNKNOWN, "Mismatched braces");
return ExpandResult::make_error(STATUS_EXPAND_ERROR.unwrap());
}
let Some(brace_begin) = brace_begin else {
// No more brace expansions left; we can return the value as-is.
if !out.add(input) {
return append_overflow_error(errors, None);
}
return ExpandResult::ok();
};
let brace_end = brace_end.unwrap();
let length_preceding_braces = brace_begin;
let length_following_braces = input.len() - brace_end - 1;
let tot_len = length_preceding_braces + length_following_braces;
let mut item_begin = brace_begin + 1;
for (pos, c) in input.chars().enumerate().skip(brace_begin + 1) {
if brace_count == 0 && (c == BRACE_SEP || pos == brace_end) {
assert!(pos >= item_begin);
let item_len = pos - item_begin;
let item = input[item_begin..pos].to_owned();
let mut item = trim(item, Some(wstr::from_char_slice(&[BRACE_SPACE, '\0'])));
for c in item.as_char_slice_mut() {
if *c == BRACE_SPACE {
*c = ' ';
}
}
// `whole_item` is a whitespace- and brace-stripped member of a single pass of brace
// expansion, e.g. in `{ alpha , b,{c, d }}`, `alpha`, `b`, and `c, d` will, in the
// first round of expansion, each in turn be a `whole_item` (with recursive commas
// replaced by special placeholders).
// We recursively call `expand_braces` with each item until it's been fully expanded.
let mut whole_item = WString::new();
whole_item.reserve(tot_len + item_len + 2);
whole_item.push_utfstr(&input[..length_preceding_braces]);
whole_item.push_utfstr(&item);
whole_item.push_utfstr(&input[brace_end + 1..]);
let _ = expand_braces(whole_item, flags, out, errors);
item_begin = pos + 1;
if pos == brace_end {
break;
}
}
if c == BRACE_BEGIN {
brace_count += 1;
}
if c == BRACE_END {
brace_count -= 1;
}
}
ExpandResult::ok()
}
/// Expand a command substitution \p input, executing on \p ctx, and inserting the results into
/// \p out_list, or any errors into \p errors. \return an expand result.
pub fn expand_cmdsubst(
input: WString,
ctx: &OperationContext,
out: &mut CompletionReceiver,
errors: &mut Option<&mut ParseErrorList>,
) -> ExpandResult {
assert!(ctx.has_parser(), "Cannot expand without a parser");
let mut cursor = 0;
let mut paren_begin = 0;
let mut paren_end = 0;
let mut subcmd = L!("");
let mut is_quoted = false;
let mut has_dollar = false;
match parse_util_locate_cmdsubst_range(
&input,
&mut cursor,
Some(&mut subcmd),
&mut paren_begin,
&mut paren_end,
false,
Some(&mut is_quoted),
Some(&mut has_dollar),
) {
-1 => {
append_syntax_error!(errors, SOURCE_LOCATION_UNKNOWN, "Mismatched parenthesis");
return ExpandResult::make_error(STATUS_EXPAND_ERROR.unwrap());
}
0 => {
if !out.add(input) {
return append_overflow_error(errors, None);
}
return ExpandResult::ok();
}
1 => {}
_ => panic!(),
}
let mut sub_res = vec![];
let job_group = ctx.job_group.clone();
let subshell_status =
exec_subshell_for_expand(subcmd, ctx.parser(), job_group.as_ref(), &mut sub_res);
if subshell_status != 0 {
// TODO: Ad-hoc switch, how can we enumerate the possible errors more safely?
let err = match subshell_status {
_ if subshell_status == STATUS_READ_TOO_MUCH.unwrap() => {
wgettext!("Too much data emitted by command substitution so it was discarded")
}
// TODO: STATUS_CMD_ERROR is overused and too generic. We shouldn't have to test things
// to figure out what error to show after we've already been given an error code.
_ if subshell_status == STATUS_CMD_ERROR.unwrap() => {
if ctx.parser().is_eval_depth_exceeded() {
wgettext!("Unable to evaluate string substitution")
} else {
wgettext!("Too many active file descriptors")
}
}
_ if subshell_status == STATUS_CMD_UNKNOWN.unwrap() => {
wgettext!("Unknown command")
}
_ if subshell_status == STATUS_ILLEGAL_CMD.unwrap() => {
wgettext!("Commandname was invalid")
}
_ if subshell_status == STATUS_NOT_EXECUTABLE.unwrap() => {
wgettext!("Command not executable")
}
_ if subshell_status == STATUS_INVALID_ARGS.unwrap() => {
// TODO: Also overused
// This is sent for:
// invalid redirections or pipes (like `<&foo`),
// invalid variables (invalid name or read-only) for for-loops,
// switch $foo if $foo expands to more than one argument
// time in a background job.
wgettext!("Invalid arguments")
}
_ if subshell_status == STATUS_EXPAND_ERROR.unwrap() => {
// Sent in `for $foo in ...` if $foo expands to more than one word
wgettext!("Expansion error")
}
_ if subshell_status == STATUS_UNMATCHED_WILDCARD.unwrap() => {
// Sent in `for $foo in ...` if $foo expands to more than one word
wgettext!("Unmatched wildcard")