@codyswann/lisa 4.71.13 → 4.72.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/all/copy-overwrite/scripts/lisa-hooks/parity-safety-net.sh +769 -37
- package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
- package/dist/core/lisa-owned-hash-ledger.js +4 -0
- package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
- package/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
- package/dist/core/nightly-e2e-guard-behavior-certificate.js.map +1 -1
- package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +13 -3
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/dist/opencode/plugin-templates/parity-safety-net.sh +769 -37
- package/package.json +4 -4
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa/hooks/parity-safety-net.sh +769 -37
- package/plugins/lisa-agy/hooks/parity-safety-net.sh +769 -37
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-agy/plugin.json +1 -1
- package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/hooks/parity-safety-net.sh +769 -37
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/hooks/parity-safety-net.sh +769 -37
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-agy/plugin.json +1 -1
- package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-agy/plugin.json +1 -1
- package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-agy/plugin.json +1 -1
- package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-agy/plugin.json +1 -1
- package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-agy/plugin.json +1 -1
- package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-agy/plugin.json +1 -1
- package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-agy/plugin.json +1 -1
- package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/src/base/hooks/parity-safety-net.sh +769 -37
- package/scripts/two-channel-couplings.json +4 -4
|
@@ -512,6 +512,8 @@ readonly FOLLOW_MAX_BYTES=262144
|
|
|
512
512
|
readonly FOLLOW_MAX_TOTAL_BYTES=524288
|
|
513
513
|
readonly FOLLOW_MAX_FILES=8
|
|
514
514
|
readonly FOLLOW_MAX_DEPTH=3
|
|
515
|
+
readonly FOLLOW_MAX_FUNCTION_DEPTH=16
|
|
516
|
+
readonly FOLLOW_MAX_FUNCTION_CALLS=128
|
|
515
517
|
|
|
516
518
|
# Effective working directory established by a literal `cd` in the command, and
|
|
517
519
|
# the flag that says a `cd` happened whose target could NOT be read literally.
|
|
@@ -523,6 +525,8 @@ follow_paths=$'\n'
|
|
|
523
525
|
follow_names=""
|
|
524
526
|
follow_count=0
|
|
525
527
|
follow_bytes=0
|
|
528
|
+
follow_function_calls=0
|
|
529
|
+
follow_function_bytes=0
|
|
526
530
|
followed_text=""
|
|
527
531
|
followed_rm_text=""
|
|
528
532
|
# Generated from the canonical reviewed supervisor, never from a host manifest.
|
|
@@ -724,8 +728,9 @@ follow_may_invoke() {
|
|
|
724
728
|
# `;` or `|` inside a quoted string is not a statement boundary, and splitting
|
|
725
729
|
# there would invent a command position in the middle of a quoted argument and
|
|
726
730
|
# make prose look like an invocation — the #3604 failure mode, arrived at from
|
|
727
|
-
# the other side.
|
|
728
|
-
#
|
|
731
|
+
# the other side. Literal subshell parentheses carry scope events, while quoted
|
|
732
|
+
# parentheses and substitutions stay in their original statement so `eval
|
|
733
|
+
# "$(cat <file>)"` retains its command position and executed-file inspection.
|
|
729
734
|
#
|
|
730
735
|
# awk rather than a bash char loop because this runs on EVERY intercepted Bash
|
|
731
736
|
# command, and it addresses the text by index instead of accumulating it a
|
|
@@ -733,24 +738,229 @@ follow_may_invoke() {
|
|
|
733
738
|
# arrives through the ENVIRONMENT: `awk -v` processes backslash escapes in the
|
|
734
739
|
# value it is given and would rewrite the command before it was ever read.
|
|
735
740
|
follow_split() {
|
|
736
|
-
SN_FOLLOW_TEXT="$1" awk '
|
|
741
|
+
LC_ALL=C SN_FOLLOW_TEXT="$1" awk '
|
|
742
|
+
# Lexical lookahead keeps an asynchronous return from ending its parent
|
|
743
|
+
# scan before the trailing & event. Aggregate work is capped linearly in
|
|
744
|
+
# input size, including overlap from nested scopes; no partial parse passes.
|
|
745
|
+
function lookahead_step() {
|
|
746
|
+
if (++lookahead_work > 8 * n + 1024) { exit 2 }
|
|
747
|
+
}
|
|
748
|
+
function brace_pipe(from, opening, closing, j, c, quote, escape, braces, nesting, tail, tail_cases, tail_header, word, word_end) {
|
|
749
|
+
quote = ""; escape = 0; braces = 1; nesting = 0; brace_redirect = ""; tail_cases = 0; tail_header = 0;
|
|
750
|
+
for (j = from; j <= n; j++) {
|
|
751
|
+
lookahead_step(); c = substr(s, j, 1);
|
|
752
|
+
if (escape) { escape = 0; continue }
|
|
753
|
+
if (c == "\\" && quote != "\047") { escape = 1; continue }
|
|
754
|
+
if (quote != "") { if (c == quote) { quote = "" } continue }
|
|
755
|
+
if (c == "\"" || c == "\047" || c == "`") { quote = c; continue }
|
|
756
|
+
if (braces > 0) {
|
|
757
|
+
# A case-pattern ) is not the closing parenthesis of its shell.
|
|
758
|
+
if (opening == "(" && c ~ /[A-Za-z_]/) {
|
|
759
|
+
word_end = j + 1;
|
|
760
|
+
while (word_end <= n && substr(s, word_end, 1) ~ /[A-Za-z_0-9-]/) { lookahead_step(); word_end++ }
|
|
761
|
+
word = substr(s, j, word_end - j); j = word_end - 1;
|
|
762
|
+
if (word == "case" && (tail_cases == 0 || !tail_patterns[tail_cases])) { tail_header++ }
|
|
763
|
+
if (word == "in" && tail_header > 0) {
|
|
764
|
+
tail_header--; tail_patterns[++tail_cases] = 1; tail_levels[tail_cases] = braces;
|
|
765
|
+
}
|
|
766
|
+
if (word == "esac" && tail_cases > 0) { tail_cases-- }
|
|
767
|
+
continue;
|
|
768
|
+
}
|
|
769
|
+
if (tail_cases > 0 && tail_levels[tail_cases] == braces) {
|
|
770
|
+
if (c == ")" && tail_patterns[tail_cases]) { tail_patterns[tail_cases] = 0; continue }
|
|
771
|
+
if (c == ";" && substr(s, j + 1, 1) == ";") { tail_patterns[tail_cases] = 1; j++; continue }
|
|
772
|
+
}
|
|
773
|
+
if (c == opening) { braces++ }
|
|
774
|
+
if (c == closing && --braces == 0) { tail = j + 1 }
|
|
775
|
+
continue;
|
|
776
|
+
}
|
|
777
|
+
# Redirections belong to the compound command too. Quoted operator
|
|
778
|
+
# bytes and nested substitutions are operands, not the pipeline edge.
|
|
779
|
+
if (c == "(") { nesting++; continue }
|
|
780
|
+
if (c == ")") { if (nesting == 0) { brace_redirect = substr(s, tail, j - tail); return 0 } nesting--; continue }
|
|
781
|
+
if (nesting > 0) { continue }
|
|
782
|
+
if (c == "|") { brace_redirect = substr(s, tail, j - tail); return substr(s, j + 1, 1) != "|" }
|
|
783
|
+
if (c == ";" || c == "\n") { brace_redirect = substr(s, tail, j - tail); return 0 }
|
|
784
|
+
if (c == "&" && substr(s, j - 1, 1) !~ /[<>]/ && substr(s, j + 1, 1) != ">") { brace_redirect = substr(s, tail, j - tail); return 0 }
|
|
785
|
+
}
|
|
786
|
+
if (tail > 0) { brace_redirect = substr(s, tail) }
|
|
787
|
+
return 0;
|
|
788
|
+
}
|
|
789
|
+
function list_async(from, j, c, quote, escape, nesting, braces) {
|
|
790
|
+
quote = ""; escape = 0; nesting = 0; braces = 0;
|
|
791
|
+
for (j = from; j <= n; j++) {
|
|
792
|
+
lookahead_step();
|
|
793
|
+
c = substr(s, j, 1);
|
|
794
|
+
if (escape) { escape = 0; continue }
|
|
795
|
+
if (c == "\\" && quote != "\047") { escape = 1; continue }
|
|
796
|
+
if (quote != "") { if (c == quote) { quote = "" } continue }
|
|
797
|
+
if (c == "\"" || c == "\047" || c == "`") { quote = c; continue }
|
|
798
|
+
if (c == "(") { nesting++; continue }
|
|
799
|
+
if (c == ")") { if (nesting == 0) { return 0 } nesting--; continue }
|
|
800
|
+
if (c == "{") { braces++; continue }
|
|
801
|
+
if (c == "}") { if (braces > 0) { braces-- } continue }
|
|
802
|
+
if (nesting > 0 || braces > 0) { continue }
|
|
803
|
+
if (c == ";" || c == "\n") { return 0 }
|
|
804
|
+
if (c == "&" && substr(s, j - 1, 1) !~ /[&<>]/ && substr(s, j + 1, 1) !~ /[&>]/) { return 1 }
|
|
805
|
+
}
|
|
806
|
+
return 0;
|
|
807
|
+
}
|
|
737
808
|
BEGIN {
|
|
738
809
|
s = ENVIRON["SN_FOLLOW_TEXT"]; n = length(s);
|
|
739
|
-
q = ""; esc = 0; start = 1; sep = "o";
|
|
810
|
+
q = ""; esc = 0; start = 1; sep = "o"; parens = 0;
|
|
811
|
+
functions = 0; cases = 0; backtick = 0; brace_depth = 0; post_brace = 0;
|
|
812
|
+
printf "L\t%d\n", list_async(1);
|
|
740
813
|
for (i = 1; i <= n; i++) {
|
|
741
814
|
c = substr(s, i, 1);
|
|
742
|
-
if (esc == 1) { esc = 0; continue }
|
|
815
|
+
if (esc == 1) { if (c == "$") { literal_dollars[i] = 1 } esc = 0; continue }
|
|
743
816
|
if (c == "\\" && q != "\047") { esc = 1; continue }
|
|
744
|
-
if (
|
|
817
|
+
if (functions > 0) {
|
|
818
|
+
if (q != "") { if (c == q) { q = "" } continue }
|
|
819
|
+
if (c == "\"" || c == "\047") { q = c; continue }
|
|
820
|
+
if (c == "{") { functions++ }
|
|
821
|
+
if (c == "}" && --functions == 0) {
|
|
822
|
+
printf "N\t%s\t%s\n", sep, function_name;
|
|
823
|
+
body = substr(s, function_start, i - function_start);
|
|
824
|
+
count = split(body, body_lines, "\n");
|
|
825
|
+
for (j = 1; j <= count; j++) { printf "D\t%s\n", body_lines[j] }
|
|
826
|
+
printf "F\t\n"; start = i + 1; sep = "o";
|
|
827
|
+
}
|
|
828
|
+
continue;
|
|
829
|
+
}
|
|
830
|
+
quoted_expression = q == "\"" && ((c == "(" && substr(s, i - 1, 1) == "$" && !literal_dollars[i - 1]) || c == "`");
|
|
831
|
+
if (q != "" && !quoted_expression) { if (c == q) { q = "" } continue }
|
|
745
832
|
if (c == "\"" || c == "\047") { q = c; continue }
|
|
833
|
+
if (c == "`") {
|
|
834
|
+
if (backtick) {
|
|
835
|
+
piece = substr(s, start, i - start);
|
|
836
|
+
if (piece ~ /[^[:space:]]/) { printf "%s\t%s\n", sep, piece; sep = "o" }
|
|
837
|
+
printf "]\t\n"; start = backtick_start; q = backtick_quote; backtick = 0;
|
|
838
|
+
} else {
|
|
839
|
+
backtick_start = start; backtick_quote = q; q = ""; backtick = 1;
|
|
840
|
+
printf "[\t\nL\t%d\n", list_async(i + 1); start = i + 1;
|
|
841
|
+
}
|
|
842
|
+
continue;
|
|
843
|
+
}
|
|
844
|
+
if (i == start && c ~ /[[:space:]]/) { start = i + 1; continue }
|
|
845
|
+
if (c ~ /[[:space:]]/ && substr(s, i - 1, 1) !~ /[[:space:]]/ && substr(s, start, 4) == "case") {
|
|
846
|
+
piece = substr(s, start, i - start);
|
|
847
|
+
if (piece ~ /^case[[:space:]].*[[:space:]]in$/) {
|
|
848
|
+
case_scopes[++cases] = parens;
|
|
849
|
+
case_patterns[cases] = 1; printf "C\t\n"; start = i + 1; continue;
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
if (cases > 0 && c ~ /[[:space:];&|)]/ && substr(s, start, 4) == "esac") {
|
|
853
|
+
piece = substr(s, start, i - start);
|
|
854
|
+
if (piece ~ /^esac[[:space:]]*$/) { cases--; printf "K\t\n" }
|
|
855
|
+
}
|
|
856
|
+
if (cases > 0 && case_scopes[cases] == parens && case_patterns[cases]) {
|
|
857
|
+
if (c == ")") { case_patterns[cases] = 0; start = i + 1 }
|
|
858
|
+
continue;
|
|
859
|
+
}
|
|
860
|
+
if (c == "{") {
|
|
861
|
+
piece = substr(s, start, i - start);
|
|
862
|
+
definition = piece ~ /^[[:space:]]*([A-Za-z_][A-Za-z_0-9]*[[:space:]]*\([[:space:]]*\)|function[[:space:]]+[A-Za-z_][A-Za-z_0-9]*([[:space:]]*\([[:space:]]*\))?)[[:space:]]*$/;
|
|
863
|
+
if (definition) {
|
|
864
|
+
function_name = piece;
|
|
865
|
+
sub(/^[[:space:]]*(function[[:space:]]+)?/, "", function_name);
|
|
866
|
+
sub(/[[:space:]\(].*$/, "", function_name);
|
|
867
|
+
functions = 1; function_start = i + 1;
|
|
868
|
+
continue;
|
|
869
|
+
}
|
|
870
|
+
previous = i > 1 ? substr(s, i - 1, 1) : "";
|
|
871
|
+
group = (i == 1 || previous ~ /[[:space:];&|()]/) && substr(s, i + 1, 1) ~ /[[:space:];]/ &&
|
|
872
|
+
piece ~ /^[[:space:]]*((then|do|else|!|time)[[:space:]]+)*$/;
|
|
873
|
+
brace_groups[++brace_depth] = group;
|
|
874
|
+
if (group) {
|
|
875
|
+
brace_sep = sep;
|
|
876
|
+
if (piece ~ /[^[:space:]]/) { printf "%s\t%s\n", sep, piece; sep = "o" }
|
|
877
|
+
# A pipeline owns the whole compound command, not just the final
|
|
878
|
+
# statement before |. Snapshot before any brace-body cd/return.
|
|
879
|
+
outgoing = brace_pipe(i + 1, "{", "}");
|
|
880
|
+
brace_pipes[brace_depth] = brace_sep == "|" || outgoing;
|
|
881
|
+
if (brace_pipes[brace_depth]) { printf "(\t%s%s\nL\t%d\n", (brace_sep == "|" ? "|" : ""), (outgoing ? "+" : ""), list_async(i + 1) }
|
|
882
|
+
printf "G\t%s\nL\t%d\n", brace_sep, list_async(i + 1);
|
|
883
|
+
if (brace_redirect ~ /[^[:space:]]/) { printf "I\t%s\n", brace_redirect }
|
|
884
|
+
# P retains incoming stdin provenance without wrapping each body
|
|
885
|
+
# statement in another child: all statements share the brace cwd.
|
|
886
|
+
if (brace_sep == "|") { sep = "P" }
|
|
887
|
+
start = i + 1; continue;
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
if (c == "}" && brace_depth > 0) {
|
|
891
|
+
group = brace_groups[brace_depth--];
|
|
892
|
+
if (group) {
|
|
893
|
+
piece = substr(s, start, i - start);
|
|
894
|
+
if (piece ~ /[^[:space:]]/) { printf "%s\t%s\n", sep, piece; sep = "o" }
|
|
895
|
+
printf "H\t\n";
|
|
896
|
+
if (brace_pipes[brace_depth + 1]) { printf ")\t\n" }
|
|
897
|
+
start = i + 1; post_brace = 1; continue;
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
if (c == "(") {
|
|
901
|
+
previous = i > 1 ? substr(s, i - 1, 1) : "";
|
|
902
|
+
piece = substr(s, start, i - start);
|
|
903
|
+
header = piece ~ /^[[:space:]]*(function[[:space:]]+)?[A-Za-z_][A-Za-z_0-9]*[[:space:]]*$/ && substr(s, i + 1) ~ /^[[:space:]]*\)/;
|
|
904
|
+
expression = previous ~ /[$<>]/ && !literal_dollars[i - 1] && substr(s, i + 1, 1) != "(";
|
|
905
|
+
group = (parens == 0 || scopes[parens] != 0) &&
|
|
906
|
+
(i == 1 || previous ~ /[[:space:];&|()]/) && substr(s, i + 1, 1) != "(" && !header;
|
|
907
|
+
scopes[++parens] = expression ? 2 : group;
|
|
908
|
+
if (expression) {
|
|
909
|
+
expression_starts[parens] = start;
|
|
910
|
+
expression_quotes[parens] = q; q = "";
|
|
911
|
+
printf "[\t\nL\t%d\n", list_async(i + 1); start = i + 1; continue;
|
|
912
|
+
}
|
|
913
|
+
if (group) {
|
|
914
|
+
piece = substr(s, start, i - start);
|
|
915
|
+
if (piece ~ /[^[:space:]]/) { printf "%s\t%s\n", sep, piece; sep = "o" }
|
|
916
|
+
outgoing = brace_pipe(i + 1, "(", ")");
|
|
917
|
+
printf "(\t%s%s\nL\t%d\n", sep, (outgoing ? "+" : ""), list_async(i + 1);
|
|
918
|
+
if (brace_redirect ~ /[^[:space:]]/) { printf "I\t%s\n", brace_redirect }
|
|
919
|
+
if (sep == "|") { sep = "P" }
|
|
920
|
+
start = i + 1; continue;
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
if (c == ")" && parens > 0) {
|
|
924
|
+
piece = substr(s, start, i - start);
|
|
925
|
+
group = scopes[parens--];
|
|
926
|
+
if (group == 2) {
|
|
927
|
+
if (piece ~ /[^[:space:]]/) { printf "%s\t%s\n", sep, piece; sep = "o" }
|
|
928
|
+
printf "]\t\n"; start = expression_starts[parens + 1]; q = expression_quotes[parens + 1]; continue;
|
|
929
|
+
}
|
|
930
|
+
if (group) {
|
|
931
|
+
piece = substr(s, start, i - start);
|
|
932
|
+
if (piece ~ /[^[:space:]]/) { printf "%s\t%s\n", sep, piece; sep = "o" }
|
|
933
|
+
printf ")\t\n"; start = i + 1; post_brace = 1; continue;
|
|
934
|
+
}
|
|
935
|
+
}
|
|
746
936
|
if (c == ";" || c == "&" || c == "|" || c == "\n") {
|
|
747
|
-
|
|
937
|
+
if (parens > 0 && scopes[parens] == 0) { continue }
|
|
938
|
+
if (c == "&" && (substr(s, i - 1, 1) ~ /[<>]/ || substr(s, i + 1, 1) == ">")) { continue }
|
|
939
|
+
piece = substr(s, start, i - start);
|
|
940
|
+
pipe = c == "|" && substr(s, i + 1, 1) != "|";
|
|
941
|
+
if (pipe || sep == "|") { printf "(\t%s%s\nL\t%d\n", (sep == "|" ? "|" : ""), (pipe ? "+" : ""), list_async(start) }
|
|
942
|
+
if (piece ~ /[^[:space:]]/) { printf "%s\t%s\n", post_brace ? "R" : sep, piece }
|
|
943
|
+
post_brace = 0;
|
|
944
|
+
if (pipe || sep == "|") { printf ")\t\n" }
|
|
945
|
+
if (c == ";" && substr(s, i + 1, 1) == ";" && cases > 0) {
|
|
946
|
+
case_patterns[cases] = 1; printf "A\t\n"; i++;
|
|
947
|
+
}
|
|
948
|
+
if (c == "|" && substr(s, i + 1, 1) == "|") {
|
|
949
|
+
i++; sep = "?";
|
|
950
|
+
} else if (c == "&" && substr(s, i + 1, 1) == "&") {
|
|
951
|
+
i++; sep = "&";
|
|
952
|
+
} else if (c == "|") { sep = "|" } else { sep = "o" }
|
|
953
|
+
if (c == "&" && sep == "o" && substr(s, i - 1, 1) !~ /[&<>]/ && substr(s, i + 1, 1) != ">") {
|
|
954
|
+
printf "B\t\n";
|
|
955
|
+
}
|
|
956
|
+
if ((c == ";" || c == "\n" || c == "&") && sep == "o") { printf "L\t%d\n", list_async(i + 1) }
|
|
748
957
|
start = i + 1;
|
|
749
|
-
if (c == "|") { sep = "|" } else { sep = "o" }
|
|
750
958
|
continue
|
|
751
959
|
}
|
|
752
960
|
}
|
|
961
|
+
if (sep == "|") { printf "(\t|\nL\t%d\n", list_async(start) }
|
|
753
962
|
printf "%s\t%s\n", sep, substr(s, start, n - start + 1);
|
|
963
|
+
if (sep == "|") { printf ")\t\n" }
|
|
754
964
|
}
|
|
755
965
|
'
|
|
756
966
|
}
|
|
@@ -815,7 +1025,12 @@ follow_cd() {
|
|
|
815
1025
|
esac
|
|
816
1026
|
case "$d" in
|
|
817
1027
|
/*) ;;
|
|
818
|
-
*)
|
|
1028
|
+
*)
|
|
1029
|
+
# A relative cd cannot recover an unknown base. Falling back to this
|
|
1030
|
+
# hook's PWD would confidently inspect another directory (#4299).
|
|
1031
|
+
[ "$follow_cwd_unknown" -eq 0 ] || return 0
|
|
1032
|
+
d="${follow_cwd:-$PWD}/$d"
|
|
1033
|
+
;;
|
|
819
1034
|
esac
|
|
820
1035
|
follow_normalize "$d"
|
|
821
1036
|
d="$follow_normalized"
|
|
@@ -899,8 +1114,14 @@ follow_locate() {
|
|
|
899
1114
|
# is never executed, so scanning it can only manufacture a false refusal.
|
|
900
1115
|
follow_take() {
|
|
901
1116
|
local path="$1" depth="$2" size="" content="" rm_content="${3-}"
|
|
1117
|
+
local invocation_kind=child
|
|
1118
|
+
if [ "$#" -eq 3 ]; then invocation_kind="$3"; fi
|
|
902
1119
|
case "$follow_paths" in
|
|
903
|
-
*$'\n'"$path"$'\n'*)
|
|
1120
|
+
*$'\n'"$path"$'\n'*)
|
|
1121
|
+
# Sourcing executes in the caller each time; a read cache cannot erase
|
|
1122
|
+
# its directory changes or newly defined functions.
|
|
1123
|
+
[ "$invocation_kind" = source ] || return 0
|
|
1124
|
+
;;
|
|
904
1125
|
esac
|
|
905
1126
|
follow_paths="$follow_paths$path"$'\n'
|
|
906
1127
|
follow_count=$((follow_count + 1))
|
|
@@ -928,22 +1149,31 @@ follow_take() {
|
|
|
928
1149
|
fi
|
|
929
1150
|
follow_names="$follow_names $path"
|
|
930
1151
|
followed_text="$followed_text"$'\n'"$content"
|
|
931
|
-
if [ "$#" -
|
|
1152
|
+
if [ "$#" -ne 4 ]; then
|
|
932
1153
|
rm_content="$content"
|
|
933
1154
|
else
|
|
934
1155
|
rm_content="$(printf '%s' "$rm_content" | sed 's/^[[:space:]]*#.*$//')" || follow_refuse "$path" "supervisor cleanup view could not be read"
|
|
935
1156
|
fi
|
|
936
1157
|
followed_rm_text="$followed_rm_text"$'\n'"$rm_content"
|
|
937
1158
|
if [ "$depth" -lt "$FOLLOW_MAX_DEPTH" ]; then
|
|
938
|
-
#
|
|
939
|
-
#
|
|
940
|
-
# child inherits this directory but cannot change the parent's. Saving and
|
|
941
|
-
# restoring is what keeps a nested `cd` from silently re-pointing the rest
|
|
942
|
-
# of the outer walk.
|
|
1159
|
+
# Child scripts inherit cwd but cannot change their parent. A sourced
|
|
1160
|
+
# script runs in the caller and keeps both cwd and function definitions.
|
|
943
1161
|
local saved_cwd="$follow_cwd" saved_unknown="$follow_cwd_unknown"
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
1162
|
+
if [ "$#" -eq 4 ]; then
|
|
1163
|
+
# Only follow_supervisor reaches this form, after exact byte identity and
|
|
1164
|
+
# the public argv profile were authenticated. Its owned bootstrap is
|
|
1165
|
+
# already scanned in full by every text policy; its user payload is
|
|
1166
|
+
# followed separately by follow_supervisor. Re-expanding all bootstrap
|
|
1167
|
+
# functions would re-interpret its internal protocol, not inspect a new
|
|
1168
|
+
# user-controlled file, and multiplied its native hook time fourfold.
|
|
1169
|
+
follow_scan "$content" "$((depth + 1))" 0 1
|
|
1170
|
+
else
|
|
1171
|
+
follow_scan "$content" "$((depth + 1))" 0 0 "$invocation_kind"
|
|
1172
|
+
fi
|
|
1173
|
+
if [ "$invocation_kind" != source ]; then
|
|
1174
|
+
follow_cwd="$saved_cwd"
|
|
1175
|
+
follow_cwd_unknown="$saved_unknown"
|
|
1176
|
+
fi
|
|
947
1177
|
fi
|
|
948
1178
|
}
|
|
949
1179
|
|
|
@@ -951,7 +1181,14 @@ follow_take() {
|
|
|
951
1181
|
# nested inside an already-followed script it is the documented residual above.
|
|
952
1182
|
follow_target() {
|
|
953
1183
|
local token="$1" depth="$2" path=""
|
|
1184
|
+
local invocation_kind="${3:-child}"
|
|
954
1185
|
[ -n "$token" ] || return 0
|
|
1186
|
+
if [ "$follow_cwd_unknown" -ne 0 ]; then
|
|
1187
|
+
case "$token" in
|
|
1188
|
+
/* | '~/'*) ;;
|
|
1189
|
+
*) follow_refuse "$token" "the execution directory is unknown" ;;
|
|
1190
|
+
esac
|
|
1191
|
+
fi
|
|
955
1192
|
# Entry authority belongs to each invocation, not the file-read cache. Only
|
|
956
1193
|
# follow_supervisor may admit the exact public interpreter/argv profile.
|
|
957
1194
|
case "$token" in
|
|
@@ -964,7 +1201,7 @@ follow_target() {
|
|
|
964
1201
|
return 0
|
|
965
1202
|
fi
|
|
966
1203
|
path="$follow_located"
|
|
967
|
-
follow_take "$path" "$depth"
|
|
1204
|
+
follow_take "$path" "$depth" "$invocation_kind"
|
|
968
1205
|
}
|
|
969
1206
|
|
|
970
1207
|
# A command word that IS the executable (`./deploy.sh`, `/tmp/x.sh`). Followed
|
|
@@ -1021,23 +1258,422 @@ follow_next_operand() {
|
|
|
1021
1258
|
follow_operand_index="$j"
|
|
1022
1259
|
}
|
|
1023
1260
|
|
|
1261
|
+
# Resolve a piped file in the PRODUCER's directory, which can differ from the
|
|
1262
|
+
# interpreter's after a subshell closes. Preserve unknown-state provenance too.
|
|
1263
|
+
follow_pipe_target() {
|
|
1264
|
+
local saved_cwd="$follow_cwd" saved_unknown="$follow_cwd_unknown"
|
|
1265
|
+
follow_cwd="$3"
|
|
1266
|
+
follow_cwd_unknown="$4"
|
|
1267
|
+
follow_target "$1" "$2"
|
|
1268
|
+
follow_cwd="$saved_cwd"
|
|
1269
|
+
follow_cwd_unknown="$saved_unknown"
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
# Carry branch uncertainty into sourced declarations as well as cwd changes.
|
|
1273
|
+
# The local projection lasts only for this source invocation, not later scans.
|
|
1274
|
+
follow_source_target() {
|
|
1275
|
+
local follow_source_flow_unknown="$3"
|
|
1276
|
+
follow_target "$1" "$2" source
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
# Extract the literal -c operand without evaluating it or treating positional
|
|
1280
|
+
# arguments after its closing quote as commands. Prefix indexing matches the
|
|
1281
|
+
# whitespace tokens used by follow_scan; the operand keeps its internal bytes.
|
|
1282
|
+
follow_inline() {
|
|
1283
|
+
local stmt="$1" from="$2" depth="$3" body
|
|
1284
|
+
local follow_inline_depth="${follow_inline_depth:-0}"
|
|
1285
|
+
[ "$follow_inline_depth" -lt "$FOLLOW_MAX_FUNCTION_DEPTH" ] || follow_refuse "-c" "inline shells exceed the inspection depth"
|
|
1286
|
+
follow_inline_depth=$((follow_inline_depth + 1))
|
|
1287
|
+
body="$(LC_ALL=C SN_INLINE_TEXT="$stmt" SN_INLINE_FROM="$from" awk '
|
|
1288
|
+
BEGIN {
|
|
1289
|
+
s = ENVIRON["SN_INLINE_TEXT"]; n = length(s); i = 1;
|
|
1290
|
+
for (word = 0; word < ENVIRON["SN_INLINE_FROM"]; word++) {
|
|
1291
|
+
while (i <= n && substr(s, i, 1) ~ /[[:space:]]/) { i++ }
|
|
1292
|
+
while (i <= n && substr(s, i, 1) !~ /[[:space:]]/) { i++ }
|
|
1293
|
+
}
|
|
1294
|
+
while (i <= n && substr(s, i, 1) ~ /[[:space:]]/) { i++ }
|
|
1295
|
+
q = substr(s, i, 1);
|
|
1296
|
+
if (q == "\047" || q == "\"") {
|
|
1297
|
+
start = ++i; esc = 0;
|
|
1298
|
+
for (; i <= n; i++) {
|
|
1299
|
+
c = substr(s, i, 1);
|
|
1300
|
+
if (esc) { esc = 0; continue }
|
|
1301
|
+
if (c == "\\" && q == "\"") { esc = 1; continue }
|
|
1302
|
+
if (c == q) {
|
|
1303
|
+
# Adjacent quoted/unquoted fragments are the same shell operand.
|
|
1304
|
+
# Refuse unsupported concatenation instead of scanning a prefix.
|
|
1305
|
+
if (i < n && substr(s, i + 1, 1) !~ /[[:space:]]/) { exit 1 }
|
|
1306
|
+
printf "%s", substr(s, start, i - start); exit;
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
exit 1;
|
|
1310
|
+
}
|
|
1311
|
+
start = i;
|
|
1312
|
+
while (i <= n && substr(s, i, 1) !~ /[[:space:]]/) {
|
|
1313
|
+
c = substr(s, i, 1);
|
|
1314
|
+
if (c == "\047" || c == "\"" || c == "\\") { exit 1 }
|
|
1315
|
+
i++;
|
|
1316
|
+
}
|
|
1317
|
+
printf "%s", substr(s, start, i - start);
|
|
1318
|
+
}
|
|
1319
|
+
')" || follow_refuse "-c" "its command string could not be determined"
|
|
1320
|
+
local saved_cwd="$follow_cwd" saved_unknown="$follow_cwd_unknown"
|
|
1321
|
+
follow_scan "$body" "$depth"
|
|
1322
|
+
follow_cwd="$saved_cwd"
|
|
1323
|
+
follow_cwd_unknown="$saved_unknown"
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
# Resolve literal descriptor duplication without running the proposed shell.
|
|
1327
|
+
# Compound input is established before its body; output is projected when the
|
|
1328
|
+
# producer reaches the pipe. Unknown descriptors refuse rather than drop bytes.
|
|
1329
|
+
follow_redirect_io() {
|
|
1330
|
+
local statement="$1" mode="$2" result
|
|
1331
|
+
result="$(SN_COMPOUND_REDIRECT="$statement" SN_DESCRIPTOR_MAP="$follow_descriptor_map" SN_REDIRECT_CWD="$follow_cwd" SN_REDIRECT_UNKNOWN="$follow_cwd_unknown" python3 - "$mode" <<'PY'
|
|
1332
|
+
import json
|
|
1333
|
+
import os
|
|
1334
|
+
import sys
|
|
1335
|
+
try:
|
|
1336
|
+
mode = sys.argv[1]
|
|
1337
|
+
text = os.environ['SN_COMPOUND_REDIRECT']
|
|
1338
|
+
tokens = []
|
|
1339
|
+
cursor = 0
|
|
1340
|
+
# Keep syntax provenance and byte adjacency: quoted/escaped > is data,
|
|
1341
|
+
# and `cat 2 >&2` names a file rather than redirecting descriptor 2.
|
|
1342
|
+
while cursor < len(text):
|
|
1343
|
+
if text[cursor].isspace():
|
|
1344
|
+
cursor += 1
|
|
1345
|
+
continue
|
|
1346
|
+
start = cursor
|
|
1347
|
+
syntax = text[cursor] in '<>&'
|
|
1348
|
+
plain = True
|
|
1349
|
+
value = ''
|
|
1350
|
+
quote = ''
|
|
1351
|
+
if syntax:
|
|
1352
|
+
while cursor < len(text) and text[cursor] in '<>&':
|
|
1353
|
+
value += text[cursor]; cursor += 1
|
|
1354
|
+
else:
|
|
1355
|
+
while cursor < len(text):
|
|
1356
|
+
char = text[cursor]
|
|
1357
|
+
if not quote and (char.isspace() or char in '<>&'):
|
|
1358
|
+
break
|
|
1359
|
+
if char == '\\' and quote != "'":
|
|
1360
|
+
plain = False
|
|
1361
|
+
cursor += 1
|
|
1362
|
+
if cursor == len(text):
|
|
1363
|
+
raise ValueError('trailing escape')
|
|
1364
|
+
if text[cursor] == '\n':
|
|
1365
|
+
cursor += 1
|
|
1366
|
+
continue
|
|
1367
|
+
if quote == '"' and text[cursor] not in '$`"\\':
|
|
1368
|
+
value += '\\'
|
|
1369
|
+
value += text[cursor]; cursor += 1
|
|
1370
|
+
continue
|
|
1371
|
+
if char in ('"', "'"):
|
|
1372
|
+
if not quote:
|
|
1373
|
+
quote = char; plain = False; cursor += 1
|
|
1374
|
+
continue
|
|
1375
|
+
if quote == char:
|
|
1376
|
+
quote = ''; cursor += 1
|
|
1377
|
+
continue
|
|
1378
|
+
value += char; cursor += 1
|
|
1379
|
+
if quote:
|
|
1380
|
+
raise ValueError('unterminated quote')
|
|
1381
|
+
tokens.append((value, syntax, plain, start, cursor))
|
|
1382
|
+
descriptors = {int(fd): value for fd, value in json.loads(os.environ['SN_DESCRIPTOR_MAP']).items()}
|
|
1383
|
+
operators = ('<', '>', '>>', '<>', '<&', '>&', '&>', '&>>')
|
|
1384
|
+
index = 0
|
|
1385
|
+
while index < len(tokens):
|
|
1386
|
+
fd = None
|
|
1387
|
+
token, syntax, plain, start, end = tokens[index]
|
|
1388
|
+
if token.isdigit() and plain and index + 1 < len(tokens) and tokens[index + 1][1] and end == tokens[index + 1][3]:
|
|
1389
|
+
fd = int(token); index += 1
|
|
1390
|
+
token, syntax, plain, start, end = tokens[index]
|
|
1391
|
+
if not syntax or token not in operators:
|
|
1392
|
+
if mode == 'cat':
|
|
1393
|
+
index += 1
|
|
1394
|
+
continue
|
|
1395
|
+
sys.exit(2)
|
|
1396
|
+
if index + 1 >= len(tokens):
|
|
1397
|
+
sys.exit(2)
|
|
1398
|
+
operator = token
|
|
1399
|
+
operand, operand_syntax, _, _, _ = tokens[index + 1]
|
|
1400
|
+
if operand_syntax:
|
|
1401
|
+
sys.exit(2)
|
|
1402
|
+
index += 2
|
|
1403
|
+
if fd is None:
|
|
1404
|
+
fd = 0 if operator.startswith('<') else 1
|
|
1405
|
+
if operator in ('<&', '>&'):
|
|
1406
|
+
if operand == '-':
|
|
1407
|
+
descriptors[fd] = {'kind': 'closed'}
|
|
1408
|
+
elif operand.isdigit():
|
|
1409
|
+
descriptors[fd] = descriptors.get(int(operand), {'kind': 'unknown'})
|
|
1410
|
+
else:
|
|
1411
|
+
sys.exit(2)
|
|
1412
|
+
else:
|
|
1413
|
+
descriptors[fd] = {'kind': 'file', 'path': operand, 'cwd': os.environ['SN_REDIRECT_CWD'], 'unknown': int(os.environ['SN_REDIRECT_UNKNOWN'])}
|
|
1414
|
+
if operator.startswith('&>'):
|
|
1415
|
+
descriptors[2] = descriptors[fd]
|
|
1416
|
+
output = descriptors[1]['kind']
|
|
1417
|
+
if output == 'unknown' or descriptors[0]['kind'] == 'unknown':
|
|
1418
|
+
sys.exit(2)
|
|
1419
|
+
print(json.dumps({'input': descriptors[0], 'output': output, 'descriptors': descriptors}))
|
|
1420
|
+
except (ValueError, OSError):
|
|
1421
|
+
sys.exit(2)
|
|
1422
|
+
PY
|
|
1423
|
+
)" || follow_refuse "compound redirection" "its input or pipeline output could not be determined"
|
|
1424
|
+
follow_redirect_input="$(printf '%s' "$result" | jq -er '.input.kind')" || follow_refuse "compound redirection" "invalid input projection"
|
|
1425
|
+
follow_redirect_path="$(printf '%s' "$result" | jq -r '.input.path // ""')" || follow_refuse "compound redirection" "invalid input path"
|
|
1426
|
+
follow_redirect_map="$(printf '%s' "$result" | jq -ce '.descriptors')" || follow_refuse "compound redirection" "invalid descriptor projection"
|
|
1427
|
+
follow_redirect_input_cwd="$(printf '%s' "$result" | jq -r '.input.cwd // ""')" || follow_refuse "compound redirection" "invalid input cwd"
|
|
1428
|
+
follow_redirect_input_unknown="$(printf '%s' "$result" | jq -r '.input.unknown // 1')" || follow_refuse "compound redirection" "invalid input uncertainty"
|
|
1429
|
+
follow_redirect_output="$(printf '%s' "$result" | jq -er '.output')" || follow_refuse "compound redirection" "invalid output projection"
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1024
1432
|
# Walk one text for the files it executes. Runs in the PARENT shell, never in a
|
|
1025
1433
|
# command substitution: follow_refuse() must be able to deny, and an exit inside
|
|
1026
1434
|
# a subshell ends that subshell rather than this hook — the mistake documented
|
|
1027
1435
|
# on the rm segmentation below.
|
|
1028
1436
|
follow_scan() {
|
|
1029
1437
|
local text="$1" depth="$2"
|
|
1438
|
+
local function_call_depth="${3:-0}"
|
|
1439
|
+
local authenticated_bootstrap="${4:-0}"
|
|
1440
|
+
local invocation_kind="${5:-child}"
|
|
1441
|
+
local function_flow_unknown="${6:-0}" function_lookup_allowed statement_conditional function_conditional=0
|
|
1442
|
+
local and_cwd="" and_unknown=1 and_ready=0 join_cwd join_unknown
|
|
1443
|
+
if [ "$invocation_kind" = source ]; then
|
|
1444
|
+
function_flow_unknown="${follow_source_flow_unknown:-0}"
|
|
1445
|
+
fi
|
|
1446
|
+
local return_scope_depth=-1
|
|
1447
|
+
local return_list_depth=-1
|
|
1448
|
+
local return_flow_unknown=0
|
|
1449
|
+
local list_depth=0 list_index
|
|
1030
1450
|
local split_out line sep stmt t tj i j n cmd_pos eval_mode inline arg_exec
|
|
1031
1451
|
local heredoc redirected pipe_cat prev_cat pending_operand stdin_mode
|
|
1452
|
+
local pipe_cwd pipe_unknown prev_cwd prev_unknown
|
|
1453
|
+
local follow_stdin_cat="${follow_stdin_cat:-}" follow_stdin_cwd="${follow_stdin_cwd:-}" follow_stdin_unknown="${follow_stdin_unknown:-1}"
|
|
1454
|
+
local follow_redirect_input follow_redirect_path follow_redirect_output follow_redirect_map follow_redirect_input_cwd follow_redirect_input_unknown
|
|
1455
|
+
local follow_descriptor_map="${follow_descriptor_map:-}"
|
|
1456
|
+
if [ -z "$follow_descriptor_map" ]; then
|
|
1457
|
+
follow_descriptor_map='{"0":{"kind":"input"},"1":{"kind":"pipe"},"2":{"kind":"stderr"}}'
|
|
1458
|
+
fi
|
|
1032
1459
|
local find_mode dispatch_pending
|
|
1460
|
+
local scope_depth=0 case_depth=0 function_name="" function_body="" function_index function_found
|
|
1033
1461
|
local -a toks=()
|
|
1034
|
-
|
|
1462
|
+
local -a cwd_scopes=() unknown_scopes=() cat_scopes=() cat_cwd_scopes=() cat_unknown_scopes=() function_scopes=() flow_scopes=() return_flows=()
|
|
1463
|
+
local -a list_cwds=() list_unknowns=() list_functions=() list_flows=() list_asyncs=() list_scope_depths=() list_return_flows=() brace_flows=()
|
|
1464
|
+
local -a brace_descriptor_maps=() scope_descriptor_maps=()
|
|
1465
|
+
local -a brace_stdin_cats=() brace_stdin_cwds=() brace_stdin_unknowns=() scope_stdin_cats=() scope_stdin_cwds=() scope_stdin_unknowns=()
|
|
1466
|
+
local -a case_cwds=() case_unknowns=() case_changes=()
|
|
1467
|
+
if [ "$function_call_depth" -eq 0 ] && [ "$invocation_kind" != source ]; then
|
|
1468
|
+
# Ordinary child shells do not inherit unexported functions. Sourcing and
|
|
1469
|
+
# function calls share the caller's definitions instead.
|
|
1470
|
+
local function_count=0
|
|
1471
|
+
local -a function_names=() function_bodies=() function_uncertainties=()
|
|
1472
|
+
fi
|
|
1473
|
+
split_out="$(follow_split "$text")" || follow_refuse "shell scope" "scope parsing exceeds the inspection budget"
|
|
1035
1474
|
prev_cat=""
|
|
1036
1475
|
while IFS= read -r line; do
|
|
1037
1476
|
sep="${line%%$'\t'*}"
|
|
1038
1477
|
stmt="${line#*$'\t'}"
|
|
1039
|
-
|
|
1040
|
-
|
|
1478
|
+
statement_conditional=0
|
|
1479
|
+
case "$sep" in '&' | '?') statement_conditional=1 ;; esac
|
|
1480
|
+
# A reached && successor observes the preceding successful command's cwd.
|
|
1481
|
+
# Scope/list events invalidate this projection; it must never carry a
|
|
1482
|
+
# child's or a background list's directory into another shell/list.
|
|
1483
|
+
case "$sep" in
|
|
1484
|
+
L | G | H | B | N | F | C | A | K | '(' | '[' | ')' | ']') and_ready=0 ;;
|
|
1485
|
+
esac
|
|
1486
|
+
if [ "$return_list_depth" -ge 0 ]; then
|
|
1487
|
+
case "$sep" in
|
|
1488
|
+
B) [ "$list_depth" -eq "$return_list_depth" ] || continue ;;
|
|
1489
|
+
'(' | '[' | ')' | ']' | G | H) ;;
|
|
1490
|
+
*) continue ;;
|
|
1491
|
+
esac
|
|
1492
|
+
fi
|
|
1493
|
+
if [ "$return_scope_depth" -ge 0 ]; then
|
|
1494
|
+
# A return in a child scope ends that child, not its enclosing function.
|
|
1495
|
+
# Keep only scope events until the matching close restores the parent.
|
|
1496
|
+
case "$sep" in '(' | '[' | ')' | ']' | G | H) ;; *) continue ;; esac
|
|
1497
|
+
fi
|
|
1498
|
+
case "$sep" in
|
|
1499
|
+
L)
|
|
1500
|
+
list_cwds[list_depth]="$follow_cwd"
|
|
1501
|
+
list_unknowns[list_depth]="$follow_cwd_unknown"
|
|
1502
|
+
list_functions[list_depth]=$function_count
|
|
1503
|
+
list_flows[list_depth]=$function_flow_unknown
|
|
1504
|
+
list_asyncs[list_depth]="$stmt"
|
|
1505
|
+
list_scope_depths[list_depth]=$scope_depth
|
|
1506
|
+
list_return_flows[list_depth]=$return_flow_unknown
|
|
1507
|
+
continue
|
|
1508
|
+
;;
|
|
1509
|
+
G)
|
|
1510
|
+
brace_flows[list_depth]=$function_flow_unknown
|
|
1511
|
+
brace_descriptor_maps[list_depth]="$follow_descriptor_map"
|
|
1512
|
+
brace_stdin_cats[list_depth]="$follow_stdin_cat"
|
|
1513
|
+
brace_stdin_cwds[list_depth]="$follow_stdin_cwd"
|
|
1514
|
+
brace_stdin_unknowns[list_depth]="$follow_stdin_unknown"
|
|
1515
|
+
if [ "$stmt" = "|" ]; then
|
|
1516
|
+
follow_stdin_cat="$prev_cat"; follow_stdin_cwd="${prev_cwd:-}"; follow_stdin_unknown="${prev_unknown:-1}"
|
|
1517
|
+
fi
|
|
1518
|
+
case "$stmt" in '&' | '?') function_flow_unknown=1 ;; esac
|
|
1519
|
+
list_depth=$((list_depth + 1)); continue
|
|
1520
|
+
;;
|
|
1521
|
+
H)
|
|
1522
|
+
list_depth=$((list_depth - 1))
|
|
1523
|
+
function_flow_unknown="${brace_flows[list_depth]}"
|
|
1524
|
+
follow_descriptor_map="${brace_descriptor_maps[list_depth]}"
|
|
1525
|
+
follow_stdin_cat="${brace_stdin_cats[list_depth]}"
|
|
1526
|
+
follow_stdin_cwd="${brace_stdin_cwds[list_depth]}"
|
|
1527
|
+
follow_stdin_unknown="${brace_stdin_unknowns[list_depth]}"
|
|
1528
|
+
# A conditional return may exit this function/source before commands
|
|
1529
|
+
# after the brace. Unlike a child return, that uncertainty persists.
|
|
1530
|
+
if [ "$return_flow_unknown" -ne 0 ]; then function_flow_unknown=1; fi
|
|
1531
|
+
continue
|
|
1532
|
+
;;
|
|
1533
|
+
B)
|
|
1534
|
+
# A single & runs the whole AND/OR list asynchronously. Its cwd and
|
|
1535
|
+
# definitions cannot escape into the parent's next list.
|
|
1536
|
+
follow_cwd="${list_cwds[list_depth]}"
|
|
1537
|
+
follow_cwd_unknown="${list_unknowns[list_depth]}"
|
|
1538
|
+
function_flow_unknown="${list_flows[list_depth]}"
|
|
1539
|
+
return_flow_unknown="${list_return_flows[list_depth]}"
|
|
1540
|
+
return_list_depth=-1
|
|
1541
|
+
while [ "$function_count" -gt "${list_functions[list_depth]}" ]; do
|
|
1542
|
+
function_count=$((function_count - 1))
|
|
1543
|
+
function_index=$function_count
|
|
1544
|
+
unset 'function_names[function_index]' 'function_bodies[function_index]' 'function_uncertainties[function_index]'
|
|
1545
|
+
done
|
|
1546
|
+
continue
|
|
1547
|
+
;;
|
|
1548
|
+
N)
|
|
1549
|
+
function_name="${stmt#*$'\t'}"; function_body=""
|
|
1550
|
+
function_conditional="$function_flow_unknown"
|
|
1551
|
+
case "${stmt%%$'\t'*}" in '&' | '?') function_conditional=1 ;; esac
|
|
1552
|
+
continue
|
|
1553
|
+
;;
|
|
1554
|
+
D)
|
|
1555
|
+
if [ "$authenticated_bootstrap" -eq 0 ]; then
|
|
1556
|
+
function_body="$function_body$stmt"$'\n'
|
|
1557
|
+
fi
|
|
1558
|
+
continue
|
|
1559
|
+
;;
|
|
1560
|
+
F)
|
|
1561
|
+
[ "$authenticated_bootstrap" -eq 0 ] || continue
|
|
1562
|
+
function_index=$function_count
|
|
1563
|
+
function_names[function_index]="$function_name"
|
|
1564
|
+
function_bodies[function_index]="$function_body"
|
|
1565
|
+
function_uncertainties[function_index]="$function_conditional"
|
|
1566
|
+
function_count=$((function_count + 1))
|
|
1567
|
+
continue
|
|
1568
|
+
;;
|
|
1569
|
+
C)
|
|
1570
|
+
function_flow_unknown=1
|
|
1571
|
+
case_cwds[case_depth]="$follow_cwd"
|
|
1572
|
+
case_unknowns[case_depth]="$follow_cwd_unknown"
|
|
1573
|
+
case_changes[case_depth]=0
|
|
1574
|
+
case_depth=$((case_depth + 1))
|
|
1575
|
+
continue
|
|
1576
|
+
;;
|
|
1577
|
+
A | K)
|
|
1578
|
+
function_index=$((case_depth - 1))
|
|
1579
|
+
if [ "$follow_cwd" != "${case_cwds[function_index]}" ] || [ "$follow_cwd_unknown" != "${case_unknowns[function_index]}" ]; then
|
|
1580
|
+
case_changes[function_index]=1
|
|
1581
|
+
fi
|
|
1582
|
+
follow_cwd="${case_cwds[function_index]}"
|
|
1583
|
+
follow_cwd_unknown="${case_unknowns[function_index]}"
|
|
1584
|
+
if [ "$sep" = K ]; then
|
|
1585
|
+
if [ "${case_changes[function_index]}" -ne 0 ]; then
|
|
1586
|
+
follow_cwd=""; follow_cwd_unknown=1
|
|
1587
|
+
fi
|
|
1588
|
+
case_depth=$((case_depth - 1))
|
|
1589
|
+
fi
|
|
1590
|
+
continue
|
|
1591
|
+
;;
|
|
1592
|
+
'(' | '[')
|
|
1593
|
+
cwd_scopes[scope_depth]="$follow_cwd"
|
|
1594
|
+
unknown_scopes[scope_depth]="$follow_cwd_unknown"
|
|
1595
|
+
cat_scopes[scope_depth]="$prev_cat"
|
|
1596
|
+
cat_cwd_scopes[scope_depth]="${prev_cwd:-}"
|
|
1597
|
+
cat_unknown_scopes[scope_depth]="${prev_unknown:-1}"
|
|
1598
|
+
scope_descriptor_maps[scope_depth]="$follow_descriptor_map"
|
|
1599
|
+
function_scopes[scope_depth]=$function_count
|
|
1600
|
+
flow_scopes[scope_depth]=$function_flow_unknown
|
|
1601
|
+
return_flows[scope_depth]=$return_flow_unknown
|
|
1602
|
+
scope_stdin_cats[scope_depth]="$follow_stdin_cat"
|
|
1603
|
+
scope_stdin_cwds[scope_depth]="$follow_stdin_cwd"
|
|
1604
|
+
scope_stdin_unknowns[scope_depth]="$follow_stdin_unknown"
|
|
1605
|
+
if [ "$stmt" = "|" ] || [ "$stmt" = "|+" ]; then
|
|
1606
|
+
follow_stdin_cat="$prev_cat"; follow_stdin_cwd="${prev_cwd:-}"; follow_stdin_unknown="${prev_unknown:-1}"
|
|
1607
|
+
follow_descriptor_map="$(printf '%s' "$follow_descriptor_map" | jq -ce '.["0"] = {kind:"input"}')" || follow_refuse "pipeline input" "its descriptor could not be established"
|
|
1608
|
+
fi
|
|
1609
|
+
case "$stmt" in
|
|
1610
|
+
*+)
|
|
1611
|
+
follow_descriptor_map="$(printf '%s' "$follow_descriptor_map" | jq -ce '.["1"] = {kind:"pipe"}')" || follow_refuse "pipeline output" "its descriptor could not be established"
|
|
1612
|
+
;;
|
|
1613
|
+
esac
|
|
1614
|
+
scope_depth=$((scope_depth + 1))
|
|
1615
|
+
list_depth=$((list_depth + 1))
|
|
1616
|
+
continue
|
|
1617
|
+
;;
|
|
1618
|
+
')' | ']')
|
|
1619
|
+
scope_depth=$((scope_depth - 1))
|
|
1620
|
+
list_depth=$((list_depth - 1))
|
|
1621
|
+
follow_cwd="${cwd_scopes[scope_depth]}"
|
|
1622
|
+
follow_descriptor_map="${scope_descriptor_maps[scope_depth]}"
|
|
1623
|
+
follow_cwd_unknown="${unknown_scopes[scope_depth]}"
|
|
1624
|
+
function_flow_unknown="${flow_scopes[scope_depth]}"
|
|
1625
|
+
return_flow_unknown="${return_flows[scope_depth]}"
|
|
1626
|
+
follow_stdin_cat="${scope_stdin_cats[scope_depth]}"
|
|
1627
|
+
follow_stdin_cwd="${scope_stdin_cwds[scope_depth]}"
|
|
1628
|
+
follow_stdin_unknown="${scope_stdin_unknowns[scope_depth]}"
|
|
1629
|
+
while [ "$function_count" -gt "${function_scopes[scope_depth]}" ]; do
|
|
1630
|
+
function_count=$((function_count - 1))
|
|
1631
|
+
function_index=$function_count
|
|
1632
|
+
unset 'function_names[function_index]' 'function_bodies[function_index]' 'function_uncertainties[function_index]'
|
|
1633
|
+
done
|
|
1634
|
+
if [ "$sep" = ']' ]; then
|
|
1635
|
+
prev_cat="${cat_scopes[scope_depth]}"
|
|
1636
|
+
prev_cwd="${cat_cwd_scopes[scope_depth]}"
|
|
1637
|
+
prev_unknown="${cat_unknown_scopes[scope_depth]}"
|
|
1638
|
+
fi
|
|
1639
|
+
if [ "$return_scope_depth" -ge 0 ] && [ "$scope_depth" -lt "$return_scope_depth" ]; then
|
|
1640
|
+
return_scope_depth=-1
|
|
1641
|
+
fi
|
|
1642
|
+
continue
|
|
1643
|
+
;;
|
|
1644
|
+
I)
|
|
1645
|
+
follow_redirect_io "$stmt" compound
|
|
1646
|
+
follow_descriptor_map="$follow_redirect_map"
|
|
1647
|
+
case "$follow_redirect_input" in
|
|
1648
|
+
file)
|
|
1649
|
+
follow_stdin_cat="$follow_redirect_path"
|
|
1650
|
+
follow_stdin_cwd="$follow_redirect_input_cwd"; follow_stdin_unknown="$follow_redirect_input_unknown"
|
|
1651
|
+
;;
|
|
1652
|
+
closed) follow_stdin_cat="" ;;
|
|
1653
|
+
input) ;;
|
|
1654
|
+
*) follow_refuse "compound redirection" "its input source could not be determined" ;;
|
|
1655
|
+
esac
|
|
1656
|
+
continue
|
|
1657
|
+
;;
|
|
1658
|
+
R)
|
|
1659
|
+
# The whole compound tail was applied before its body by I. Its cat
|
|
1660
|
+
# producer already records whether its effective output reaches pipe.
|
|
1661
|
+
continue
|
|
1662
|
+
;;
|
|
1663
|
+
esac
|
|
1664
|
+
join_cwd="$follow_cwd"; join_unknown="$follow_cwd_unknown"
|
|
1665
|
+
if [ "$sep" = '&' ] && [ "$and_ready" -eq 1 ]; then
|
|
1666
|
+
follow_cwd="$and_cwd"; follow_cwd_unknown="$and_unknown"
|
|
1667
|
+
fi
|
|
1668
|
+
and_ready=0
|
|
1669
|
+
pipe_cat="$follow_stdin_cat"
|
|
1670
|
+
pipe_cwd="$follow_stdin_cwd"
|
|
1671
|
+
pipe_unknown="$follow_stdin_unknown"
|
|
1672
|
+
if [ "$sep" = "|" ]; then
|
|
1673
|
+
pipe_cat="$prev_cat"
|
|
1674
|
+
pipe_cwd="${prev_cwd:-}"
|
|
1675
|
+
pipe_unknown="${prev_unknown:-1}"
|
|
1676
|
+
fi
|
|
1041
1677
|
prev_cat=""
|
|
1042
1678
|
case "$stmt" in
|
|
1043
1679
|
*[![:space:]]*) ;;
|
|
@@ -1053,6 +1689,7 @@ follow_scan() {
|
|
|
1053
1689
|
[ "$n" -gt 0 ] || continue
|
|
1054
1690
|
i=0
|
|
1055
1691
|
cmd_pos=1
|
|
1692
|
+
function_lookup_allowed=1
|
|
1056
1693
|
eval_mode=0
|
|
1057
1694
|
arg_exec=0
|
|
1058
1695
|
pending_operand=0
|
|
@@ -1104,7 +1741,65 @@ follow_scan() {
|
|
|
1104
1741
|
follow_unwrap "${toks[i]}"
|
|
1105
1742
|
t="$follow_unwrapped"
|
|
1106
1743
|
fi
|
|
1744
|
+
function_found=0
|
|
1745
|
+
for ((function_index = function_count - 1; function_lookup_allowed && function_index >= 0; function_index--)); do
|
|
1746
|
+
if [ "$t" = "${function_names[function_index]}" ]; then
|
|
1747
|
+
[ "${function_uncertainties[function_index]}" -eq 0 ] || follow_refuse "$t" "its function definition is conditional"
|
|
1748
|
+
# Calls execute in this shell and do not consume a file-follow depth.
|
|
1749
|
+
# A separate bound refuses recursion without truncating the body.
|
|
1750
|
+
[ "$function_call_depth" -lt "$FOLLOW_MAX_FUNCTION_DEPTH" ] || follow_refuse "$t" "function calls exceed the inspection depth"
|
|
1751
|
+
follow_function_calls=$((follow_function_calls + 1))
|
|
1752
|
+
follow_function_bytes=$((follow_function_bytes + ${#function_bodies[function_index]}))
|
|
1753
|
+
if [ "$follow_function_calls" -gt "$FOLLOW_MAX_FUNCTION_CALLS" ] || [ "$follow_function_bytes" -gt "$FOLLOW_MAX_TOTAL_BYTES" ]; then
|
|
1754
|
+
follow_refuse "$t" "function expansion exceeds the inspection budget"
|
|
1755
|
+
fi
|
|
1756
|
+
local call_cwd="$follow_cwd" call_unknown="$follow_cwd_unknown"
|
|
1757
|
+
follow_scan "${function_bodies[function_index]}" "$depth" "$((function_call_depth + 1))" 0 child "$((function_flow_unknown || statement_conditional))"
|
|
1758
|
+
if { [ "$function_flow_unknown" -ne 0 ] || [ "$statement_conditional" -ne 0 ]; } && { [ "$follow_cwd" != "$call_cwd" ] || [ "$follow_cwd_unknown" != "$call_unknown" ]; }; then
|
|
1759
|
+
follow_cwd=""; follow_cwd_unknown=1
|
|
1760
|
+
fi
|
|
1761
|
+
function_found=1
|
|
1762
|
+
break
|
|
1763
|
+
fi
|
|
1764
|
+
done
|
|
1765
|
+
if [ "$function_found" -eq 1 ]; then
|
|
1766
|
+
cmd_pos=0; i=$((i + 1)); continue
|
|
1767
|
+
fi
|
|
1107
1768
|
case "$t" in
|
|
1769
|
+
return)
|
|
1770
|
+
if [ "$statement_conditional" -ne 0 ]; then function_flow_unknown=1; fi
|
|
1771
|
+
if { [ "$function_call_depth" -gt 0 ] || [ "$invocation_kind" = source ]; } && [ "$function_flow_unknown" -ne 0 ]; then
|
|
1772
|
+
return_flow_unknown=1
|
|
1773
|
+
fi
|
|
1774
|
+
if { [ "$function_call_depth" -gt 0 ] || [ "$invocation_kind" = source ]; } && [ "$function_flow_unknown" -eq 0 ]; then
|
|
1775
|
+
# Ordinary braces share the shell but have distinct list frames.
|
|
1776
|
+
# A surrounding background brace list ends only its child; do not
|
|
1777
|
+
# cross a literal subshell boundary while finding that parent list.
|
|
1778
|
+
for ((list_index = list_depth; list_index >= 0; list_index--)); do
|
|
1779
|
+
[ "${list_scope_depths[list_index]}" -eq "$scope_depth" ] || break
|
|
1780
|
+
if [ "${list_asyncs[list_index]}" -ne 0 ]; then
|
|
1781
|
+
return_list_depth="$list_index"
|
|
1782
|
+
break
|
|
1783
|
+
fi
|
|
1784
|
+
done
|
|
1785
|
+
if [ "$return_list_depth" -ge 0 ]; then
|
|
1786
|
+
:
|
|
1787
|
+
elif [ "$scope_depth" -eq 0 ]; then
|
|
1788
|
+
return 0
|
|
1789
|
+
else
|
|
1790
|
+
return_scope_depth="$scope_depth"
|
|
1791
|
+
fi
|
|
1792
|
+
fi
|
|
1793
|
+
cmd_pos=0; i=$((i + 1)); continue
|
|
1794
|
+
;;
|
|
1795
|
+
if | while | until)
|
|
1796
|
+
function_flow_unknown=1
|
|
1797
|
+
i=$((i + 1)); continue
|
|
1798
|
+
;;
|
|
1799
|
+
for | select)
|
|
1800
|
+
function_flow_unknown=1
|
|
1801
|
+
cmd_pos=0; i=$((i + 1)); continue
|
|
1802
|
+
;;
|
|
1108
1803
|
# Prefixes that keep the command position open: assignments, wrappers,
|
|
1109
1804
|
# and the shell keywords a statement can start with. Without them
|
|
1110
1805
|
# `if …; then bash <file>` and `env bash <file>` walk past the
|
|
@@ -1139,12 +1834,17 @@ follow_scan() {
|
|
|
1139
1834
|
# which keeps working when options sit in between — `timeout -s KILL
|
|
1140
1835
|
# 5 bash <file>` reaches `bash` just as `timeout 5 bash <file>` does.
|
|
1141
1836
|
pending_operand=1
|
|
1837
|
+
function_lookup_allowed=0
|
|
1142
1838
|
i=$((i + 1))
|
|
1143
1839
|
continue
|
|
1144
1840
|
;;
|
|
1145
1841
|
sudo | env | command | exec | nohup | nice | time | builtin \
|
|
1146
|
-
| do | then | else |
|
|
1842
|
+
| do | then | else | '!' | '{' \
|
|
1147
1843
|
| */sudo | */env | */nohup | */nice | */time)
|
|
1844
|
+
case "$t" in
|
|
1845
|
+
sudo | env | command | exec | nohup | nice | builtin | */sudo | */env | */nohup | */nice) function_lookup_allowed=0 ;;
|
|
1846
|
+
do | then | else) function_flow_unknown=1 ;;
|
|
1847
|
+
esac
|
|
1148
1848
|
i=$((i + 1))
|
|
1149
1849
|
continue
|
|
1150
1850
|
;;
|
|
@@ -1182,6 +1882,9 @@ follow_scan() {
|
|
|
1182
1882
|
# place would resolve later tokens from a directory the shell left.
|
|
1183
1883
|
follow_cd "${HOME:-}"
|
|
1184
1884
|
fi
|
|
1885
|
+
if [ "$function_flow_unknown" -ne 0 ]; then
|
|
1886
|
+
follow_cwd=""; follow_cwd_unknown=1
|
|
1887
|
+
fi
|
|
1185
1888
|
cmd_pos=0
|
|
1186
1889
|
i=$((i + 1))
|
|
1187
1890
|
continue
|
|
@@ -1191,7 +1894,11 @@ follow_scan() {
|
|
|
1191
1894
|
j="$follow_operand_index"
|
|
1192
1895
|
if [ "$j" -lt "$n" ]; then
|
|
1193
1896
|
follow_unwrap "${toks[j]}"
|
|
1194
|
-
|
|
1897
|
+
local source_cwd="$follow_cwd" source_unknown="$follow_cwd_unknown"
|
|
1898
|
+
follow_source_target "$follow_unwrapped" "$depth" "$((function_flow_unknown || statement_conditional))"
|
|
1899
|
+
if { [ "$function_flow_unknown" -ne 0 ] || [ "$statement_conditional" -ne 0 ]; } && { [ "$follow_cwd" != "$source_cwd" ] || [ "$follow_cwd_unknown" != "$source_unknown" ]; }; then
|
|
1900
|
+
follow_cwd=""; follow_cwd_unknown=1
|
|
1901
|
+
fi
|
|
1195
1902
|
fi
|
|
1196
1903
|
cmd_pos=0
|
|
1197
1904
|
i=$((j + 1))
|
|
@@ -1200,14 +1907,32 @@ follow_scan() {
|
|
|
1200
1907
|
cat | '<')
|
|
1201
1908
|
follow_next_operand "$i" "$n"
|
|
1202
1909
|
j="$follow_operand_index"
|
|
1203
|
-
|
|
1204
|
-
|
|
1910
|
+
follow_unwrapped=""
|
|
1911
|
+
if [ "$j" -lt "$n" ]; then follow_unwrap "${toks[j]}"; fi
|
|
1912
|
+
if [ "$t" = cat ] && { [ "$j" -ge "$n" ] || [ "$follow_unwrapped" = - ]; }; then
|
|
1913
|
+
# Bare cat and cat - forward the group's named stdin unchanged.
|
|
1914
|
+
prev_cat="$pipe_cat"; prev_cwd="$pipe_cwd"; prev_unknown="$pipe_unknown"
|
|
1915
|
+
if [ "$eval_mode" -eq 1 ] && [ -n "$prev_cat" ]; then
|
|
1916
|
+
follow_pipe_target "$prev_cat" "$depth" "$prev_cwd" "$prev_unknown"
|
|
1917
|
+
fi
|
|
1918
|
+
elif [ "$j" -lt "$n" ]; then
|
|
1205
1919
|
if [ "$eval_mode" -eq 1 ]; then
|
|
1206
1920
|
follow_target "$follow_unwrapped" "$depth"
|
|
1207
1921
|
else
|
|
1208
|
-
# Not executed here —
|
|
1209
|
-
#
|
|
1922
|
+
# Not executed here — the pipe consumer decides whether the
|
|
1923
|
+
# producer's file content becomes an executable shell input.
|
|
1210
1924
|
prev_cat="$follow_unwrapped"
|
|
1925
|
+
prev_cwd="$follow_cwd"; prev_unknown="$follow_cwd_unknown"
|
|
1926
|
+
fi
|
|
1927
|
+
fi
|
|
1928
|
+
if [ -n "$prev_cat" ] && [ "$eval_mode" -eq 0 ]; then
|
|
1929
|
+
if [[ "$stmt" == *'>'* ]] || [ "$follow_descriptor_map" != '{"0":{"kind":"input"},"1":{"kind":"pipe"},"2":{"kind":"stderr"}}' ]; then
|
|
1930
|
+
follow_redirect_io "$stmt" cat
|
|
1931
|
+
case "$follow_redirect_output" in
|
|
1932
|
+
pipe) ;;
|
|
1933
|
+
file | closed | stderr | input) prev_cat="" ;;
|
|
1934
|
+
*) follow_refuse "cat redirection" "its output destination could not be determined" ;;
|
|
1935
|
+
esac
|
|
1211
1936
|
fi
|
|
1212
1937
|
fi
|
|
1213
1938
|
cmd_pos=0
|
|
@@ -1292,18 +2017,17 @@ follow_scan() {
|
|
|
1292
2017
|
continue
|
|
1293
2018
|
fi
|
|
1294
2019
|
if [ "$inline" -eq 1 ]; then
|
|
1295
|
-
#
|
|
1296
|
-
#
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
i="$j"
|
|
2020
|
+
# The command string runs in a child shell. It gets its own function
|
|
2021
|
+
# map and cwd effects; positional arguments are data, not commands.
|
|
2022
|
+
follow_inline "$stmt" "$j" "$depth"
|
|
2023
|
+
cmd_pos=0
|
|
2024
|
+
i="$n"
|
|
1301
2025
|
continue
|
|
1302
2026
|
fi
|
|
1303
2027
|
if [ "$stdin_mode" -eq 1 ]; then
|
|
1304
2028
|
# `sh -s -- -y` reads its program from stdin; tokens after `--` are
|
|
1305
2029
|
# positional arguments to that program, never a script path.
|
|
1306
|
-
[ -z "$pipe_cat" ] ||
|
|
2030
|
+
[ -z "$pipe_cat" ] || follow_pipe_target "$pipe_cat" "$depth" "$pipe_cwd" "$pipe_unknown"
|
|
1307
2031
|
cmd_pos=0
|
|
1308
2032
|
i="$n"
|
|
1309
2033
|
continue
|
|
@@ -1317,7 +2041,7 @@ follow_scan() {
|
|
|
1317
2041
|
fi
|
|
1318
2042
|
# No operand: an interactive or stdin-fed interpreter. Only the
|
|
1319
2043
|
# `cat <file> | bash` shape names a FILE, and only that is followed.
|
|
1320
|
-
[ -z "$pipe_cat" ] ||
|
|
2044
|
+
[ -z "$pipe_cat" ] || follow_pipe_target "$pipe_cat" "$depth" "$pipe_cwd" "$pipe_unknown"
|
|
1321
2045
|
cmd_pos=0
|
|
1322
2046
|
i="$j"
|
|
1323
2047
|
continue
|
|
@@ -1351,6 +2075,14 @@ follow_scan() {
|
|
|
1351
2075
|
cmd_pos=0
|
|
1352
2076
|
i=$((i + 1))
|
|
1353
2077
|
done
|
|
2078
|
+
# Keep successful-branch provenance separate from the continuation join.
|
|
2079
|
+
# OR can skip its RHS and still reach a following &&, so it cannot prove
|
|
2080
|
+
# that RHS's cwd. A semicolon observes every possible preceding outcome.
|
|
2081
|
+
and_cwd="$follow_cwd"; and_unknown="$follow_cwd_unknown"
|
|
2082
|
+
if [ "$sep" != '?' ] && [ "$function_flow_unknown" -eq 0 ]; then and_ready=1; fi
|
|
2083
|
+
if [ "$statement_conditional" -ne 0 ] && { [ "$join_unknown" -ne 0 ] || [ "$follow_cwd_unknown" -ne 0 ] || [ "$follow_cwd" != "$join_cwd" ]; }; then
|
|
2084
|
+
follow_cwd=""; follow_cwd_unknown=1
|
|
2085
|
+
fi
|
|
1354
2086
|
done <<<"$split_out"
|
|
1355
2087
|
}
|
|
1356
2088
|
|