Problem/Motivation

This issue is not triggered by any test.

When running ./scripts/run-tests.sh with some incorrect param (and no --color param):

php ./scripts/run-tests.sh --verbose --some_option

the following PHP notice is displayed:

PHP Notice:  Trying to access array offset on value of type null in ./scripts/run-tests.sh on line 732

Affected code:

732  if ($args['color']) {
733    echo "\033[" . $color_code . "m" . $message . "\033[0m";
734  }

Proposed resolution

Fix PHP notice by wrapping the code in isset()

Remaining tasks

Patch review

User interface changes

None

API changes

None

Data model changes

None

Release notes snippet

See the parent issue

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Taran2L created an issue. See original summary.

Taran2L’s picture

Status: Active » Needs review
FileSize
433 bytes
joseph.olstad’s picture

Ayesh’s picture

Status: Needs review » Needs work

Shouldn't this be `!empty($args['color'])` call instead of isset()?

`$args['color'] = 0` will evaluate to false in current code, but true with `isset()`.

Test failures are not related to this patch either way :)

longwave’s picture

Agree with #4, this should be !empty()

joseph.olstad’s picture

combine isset with empty, avoid all notices.

MustangGB’s picture

combine isset with empty, avoid all notices.

No, they are mutually exclusive, choose one or the other.

https://www.php.net/manual/en/function.empty.php
empty() does not generate a warning if the variable does not exist.

joseph.olstad’s picture

Thanks, yes I agree, empty , there's no need for a micro optimisation here as this script is not actually called thousands of times in a thread like some others are (for loops in code that is run on every bootstrap for instance).
#2770065: array_key_exists() micro-optimization
isset() being the fastest of the two, double condition is faster but out of scope for this patch.

new patch

joseph.olstad’s picture

Status: Needs work » Needs review
mcdruid’s picture

Status: Needs review » Needs work

Yup, we either need isset($args['color']) or !empty($args['color']). The last patch has just empty($args['color']) which is wrong.

I think isset($args['color']) is probably more consistent with what we've been doing elsewhere, and as @joseph.olstad mentioned I don't think we care too much about the relative performance in this case.

How about other references to elements of the $args global?

$ grep -n "if (\$args\['" scripts/run-tests.sh 
18:if ($args['help'] || $count == 0) {
23:if ($args['execute-test']) {
40:if ($args['clean']) {
61:if ($args['list']) {
99:if ($args['xml']) {
418:  if ($args['color']) {
437:  if ($args['all']) {
441:    if ($args['class']) {
460:    elseif ($args['file']) {
475:    elseif ($args['directory']) {
486:      if ($args['directory'][0] === '/') {
560:  if ($args['all']) {
668:  if ($args['verbose']) {
732:  if ($args['color']) {

...do these not have the same issue?

joseph.olstad’s picture

ok , ya oops about my last patch , how about this:

joseph.olstad’s picture

Status: Needs work » Needs review
mcdruid’s picture

Status: Needs review » Needs work

Thanks!

This is going to be tricky to review as I suppose we're stuck with only manual testing!

It looks like there's real mix of isset() and empty() / !empty() in the existing code.

Although it might be nice to be more consistent, I think in this case I'd prefer to keep the changes to a minimum, so my vote would be only change references to array elements that'll cause PHP 7.4 exceptions and leave anything else that's already there alone.

longwave’s picture

The real question here to me is why is only color affected, when we have code to ensure defaults exist in the $args array - this looks like it should solve this for all cases:

function simpletest_script_parse_args() {
  // Set default values.
  $args = array(
    'script' => '',
    'help' => FALSE,
    'list' => FALSE,
    'clean' => FALSE,
    'url' => '',
    'php' => '',
    'concurrency' => 1,
    'all' => FALSE,
    'class' => FALSE,
    'file' => FALSE,
    'directory' => '',
    'color' => FALSE,
    'verbose' => FALSE,
    'test_names' => array(),
    // Used internally.
    'test-id' => 0,
    'execute-test' => '',
    'xml' => '',
  );
Ayesh’s picture

I really hope I'm missing something here...

isset() and empty() are not interchangeable. isset() doesn't care about the "truthiness" of the value. It will simply return true if the object property, array key, or variable exists and is not null. empty() will do it, and more, by loosely evaluating its truthiness.

I do apologize if my comment in #4 sounded misleading. It was not against the use of isset vs !empty, but rather the semantically correct use of it. In function simpletest_script_parse_args(), you will see that the $args array is initialized and the key color is set to FALSE. So with isset($args['color]), even FALSE evaluates to true.

Ayesh’s picture

I cross-posted above #14. I totally agree with @longwave I think we need to investigate why color key was missing but nothing else.
Can anyone else actually reproduce this bug?

mcdruid’s picture

I had totally missed the initialisation of the defaults for $args - I hadn't really read the code properly at all, so I'm glad other people are :)

mcdruid’s picture

As for reproducing the issue, yes I think I can:

# php scripts/run-tests.sh --borked
PHP Notice:  Trying to access array offset on value of type null in /path/to/drupal-7.x/scripts/run-tests.sh on line 732
  ERROR: Unknown argument '--borked'.

# php --version
PHP 7.4.3 (cli) (built: Feb 23 2020 07:24:02) ( NTS )
mcdruid’s picture

Is the problem that this:

      else {                                                                       
        // Argument not found in list.                                             
        simpletest_script_print_error("Unknown argument '$arg'.");                 
        exit(SIMPLETEST_SCRIPT_EXIT_FAILURE);                                      
      } 

...is called from within simpletest_script_parse_args() before that function returns, which means that $args (with the defaults set) has not yet gone into the global scope.

So at this point:

function simpletest_script_print($message, $color_code) {                       
  global $args;                                                                 
  if ($args['color']) {                                                         
    echo "\033[" . $color_code . "m" . $message . "\033[0m";                    
  }

...isn't the global $args we're looking for, so to speak.

Simplest fix would probably be to check if it's not empty then (that should work both in the case there's nothing there and it's been initialised to the default of FALSE).

longwave’s picture

I investigated the same way and found the same issue as #19, $args is not in global scope by the time simpletest_script_print() is called in the case of invalid parameters, so the only thing we need to fix is the check inside that function. In theory we could do a more comprehensive fix and correct the scoping but practically I think we should go for the smallest fix that works.

I also agree that !empty() is correct here as FALSE should not pass the condition.

mcdruid’s picture

Status: Needs review » Reviewed & tested by the community
Issue tags: +Pending Drupal 7 commit

Thanks!

joseph.olstad’s picture

yes I like the minimalist approach, I was just throwing the kitchen sink in there for discussion.
+ patch 20 thanks @longwave

Taran2L’s picture

+1 RTBC

Fabianx’s picture

Assigned: Unassigned » mcdruid

RTBM - thanks all!

  • mcdruid committed 230c34e on 7.x
    Issue #3126140 by joseph.olstad, Taran2L, longwave, mcdruid, Ayesh,...
mcdruid’s picture

Assigned: mcdruid » Unassigned
Status: Reviewed & tested by the community » Fixed
Issue tags: -Pending Drupal 7 commit

Thanks everyone!

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.