Clamp CI truncate function arguments to the input length

What does this MR do and why?

The truncate(<offset>,<length>) interpolation function for CI/CD inputs accepts any run of digits for both arguments (truncate.rb#L10), converts them with to_i and passes them to String#[] (truncate.rb#L19). String#[] raises RangeError when an Integer does not fit in a C long, so $[[ inputs.s | truncate(0,99999999999999999999) ]] raises instead of returning a value or a configuration error.

Nothing between the function and its callers rescues RangeError: it is not in Config::RESCUE_ERRORS, nor in the rescues of expand_config or YamlProcessor#execute. So the static validation of the CI lint API answers 500, and the lint dry run and pipeline creation end in the catch-all of Chain::Config::Process, which reports Undefined error (<correlation id>) and sends the exception to error tracking. The issue has the reproduction.

This MR clamps both arguments to the length of the input before slicing. No argument past the end of the input changes the result, so every value that worked before gives the same result, and a larger value now behaves like any other value past the end of the input: truncate(0,99999999999999999999) returns the whole string and truncate(99999999999999999999,2) returns an empty string.

I chose clamping over a new validation error with an upper limit: the documentation of truncate sets no maximum, an offset or a length past the end of the input already returns what exists, and any limit low enough to be portable (for example 2**31 - 1) would turn configurations that work today into errors. If you would rather have an error, it is a small change to execute and to the expected values of the new spec table.

Behavior change: an argument too large for a C long no longer raises an exception; it gives the same result as the input length would. Values that fit in a C long behave exactly as before, which the new spec pins on both sides of the 2**63 boundary.

References

Closes #635966 (closed)

!125802 (merged) added the truncate function with the unbounded \d+ arguments.

Screenshots or screen recordings

Not applicable: backend change with no UI.

How to set up and validate locally

  1. Run bundle exec rspec spec/lib/gitlab/ci/config/interpolation/functions/truncate_spec.rb. On master, the four new examples whose arguments do not fit in a C long fail with RangeError at truncate.rb:19; on this branch all 11 examples pass.
  2. Optionally, on an instance running this branch, check the configuration from the issue with the CI Lint tool. It should be valid, with script: echo "abcdef" in the merged YAML.

What I ran, on master at 714fe569 with Ruby 3.3.11 and the project bundle:

  • the new spec without the fix: 11 examples, 4 failures, each RangeError at truncate.rb:19;
  • the same spec with the fix: 11 examples, 0 failures;
  • every fast_spec_helper spec under spec/lib/gitlab/ci/config/interpolation/ and spec/lib/gitlab/ci/config/yaml/ with the fix: 180 examples, 0 failures;
  • with the fix, against a test database: interpolation/interpolator_spec.rb, yaml/loader_spec.rb and yaml/documents_spec.rb (40 examples, 0 failures), spec/lib/gitlab/ci/lint_spec.rb (108 examples, 0 failures) and spec/services/ci/create_pipeline_service/inputs_spec.rb (17 examples, 0 failures);
  • a throwaway spec, not part of this MR, that runs the configuration from the issue through Gitlab::Ci::Lint#legacy_validate, which POST /projects/:id/ci/lint calls, and through Ci::CreatePipelineService. Without the fix, the static validation raises RangeError at truncate.rb:19, the dry run answers valid: false with Undefined error (<correlation id>), and the pipeline fails with config_error and the same message. With the fix, all three are valid and the job's script is echo "abcdef";
  • bundle exec rubocop on both changed files: no offenses; scripts/lint/commit_linter.rb on the commit: no findings;
  • four mutations of the fix (dropping either clamp, or clamping either argument to the input length minus one): the new spec fails for each.

I did not run the request specs, such as spec/requests/api/lint_spec.rb, which need the application CSS built; the pipeline covers them.

Red and green output
$ bundle exec rspec spec/lib/gitlab/ci/config/interpolation/functions/truncate_spec.rb   # master
     Failure/Error: input_value[offset, length].to_s

     RangeError:
       bignum too big to convert into `long'
     # ./lib/gitlab/ci/config/interpolation/functions/truncate.rb:19:in `[]'
     # ./lib/gitlab/ci/config/interpolation/functions/truncate.rb:19:in `execute'
11 examples, 4 failures

$ bundle exec rspec spec/lib/gitlab/ci/config/interpolation/functions/truncate_spec.rb   # this branch
11 examples, 0 failures

I found this while maintaining gitlab-mcp-server; its upstream-bugs.md records everything done on its dependencies and sibling projects.

MR acceptance checklist

Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.

Merge request reports

Loading
Loading