Repository: VKCOM/noverify
Pull request #714 (2020-08-26 11:20:16)

linter: add offBy1 checker
Reports potential off-by-one mistakes.

Example: $a[count($a)] -> $a[count($a)-1].

Quickfixes are available.

Signed-off-by: Iskander Sharipov <quasilyte@gmail.com>


Patch:
diff --git a/src/cmd/embeddedrules/rules.go b/src/cmd/embeddedrules/rules.go
index 0307512..1e20b64 100644
--- a/src/cmd/embeddedrules/rules.go
+++ b/src/cmd/embeddedrules/rules.go
@@ -77,7 +77,7 @@ func (fi bindataFileInfo) Sys() interface{} {
 	return nil
 }
 
-var _embeddedrulesRulesPhp = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xac\x96\x51\x8f\xe2\x36\x10\xc7\xdf\xf9\x14\x7f\x24\x16\x2d\xdc\x2d\xa9\xd4\xb7\x5d\x58\xae\x8f\x95\x4e\x3d\xa9\x7d\x5f\x70\x92\x09\x71\x2f\xd8\xae\xed\x1c\xa0\xe6\xbe\x7b\x65\x1b\x96\x2c\x24\x47\xb2\x3d\x1e\x10\x4c\x32\xf3\x9b\x99\xff\x38\x93\xf9\x52\xe5\x6a\x30\x88\xa6\xd3\x01\xa6\xf8\x24\x24\x17\x46\x51\x62\xb9\x14\xf8\xed\xf3\x67\x6f\x2c\xb8\xb0\xa4\x11\x3e\x29\x37\x2c\x2e\x68\x80\x69\x74\x76\x4b\xe4\x76\x4b\xc2\xe2\x4f\x52\x52\x5b\x58\xd2\x82\xe9\x03\x68\xaf\x34\x19\xc3\xa5\x30\xb0\x39\xb3\x48\x98\x40\x4c\x30\x7c\xab\x0a\x9e\x71\x4a\x67\xde\x3d\xa6\x4c\x6a\x02\x46\x7b\x2c\xdd\xd7\x23\x46\x07\x7f\x81\x65\x01\xec\x2e\x1c\x8d\xd1\x20\x2b\x45\xc8\xef\x88\xf9\x2b\x44\x3b\xdc\x4f\xf0\xef\x00\xf0\x39\xc1\x79\x6f\xd9\x21\x26\x24\xb2\x2c\x52\x68\x52\x05\x4b\x08\x36\xa7\xd7\xf4\x76\xdc\xe6\xf8\xbb\x34\x16\xa3\x44\x8a\xf4\xe8\x65\x0f\x8a\x10\x4b\x59\xd4\xac\xd1\x00\xe1\x1f\x96\xb0\xba\x24\x3c\x22\x63\x85\xa1\xa7\xc1\x0f\x80\x3b\xcd\x2d\x81\x19\xac\xef\x5d\xb8\x89\x0f\xb0\xae\x53\x30\xfc\xb9\x9c\x53\x9b\x4e\x90\x8c\xef\x6b\xad\xf3\x26\x55\x6a\xc2\x68\x7f\xa6\xd5\x5a\xde\x83\xb2\x6c\xa0\x2c\x4f\x14\x17\x97\x1b\x43\xf6\x7e\xb4\x9f\xd4\xc3\x7f\x6f\x9d\x18\x25\x2d\x09\xcb\x59\x01\xa9\x48\x33\x2f\xaf\xd2\x94\x50\x4a\x22\x21\x17\xad\x24\x73\x35\x2c\x63\x8c\xb6\xcc\x7c\xc5\x62\x81\x5f\x9e\x10\x45\xee\x47\xce\x0c\x72\xbe\xc9\x49\xd7\x03\xd8\x9c\x09\x8c\xdf\xcc\xd4\xfd\x39\xc0\xc4\x47\x78\x3b\x5c\x67\xe7\xd3\x5c\x45\xf8\x43\x5a\x7a\xc4\x8e\xdc\x34\xb9\xac\xd7\xb5\x1c\x86\x0b\xd7\x12\xd7\x20\x76\x95\x39\xe8\x1b\x89\x10\x82\x67\xe0\xaf\xe7\x20\x61\xe5\x26\xb7\x61\x0e\xd7\x6e\x22\x92\x9c\x92\xaf\x5f\xd4\xfa\x78\x5a\xdc\x3f\x83\x58\xda\x3c\xb4\xc5\x4f\x49\x14\xc1\xdd\x6a\xe0\xa7\x0a\xdc\x40\x48\x0b\x86\x8d\x94\xe9\xe9\x36\x64\x52\x23\xe6\x76\xc7\x0d\x9d\x3b\x3a\x99\x79\xf7\x10\x23\x34\x9e\x8b\x0d\xd6\x5c\x7c\x63\x05\x4f\x43\xd8\x8f\xee\xd4\x52\x62\x29\x85\x28\xb7\x31\x69\x64\xb2\x14\xa9\x3f\x11\x6b\x70\x13\xbc\x1d\xd2\xe7\x98\x53\xa1\xb2\xb2\xf8\x88\x98\x12\x56\x9a\x70\xc0\xb4\x94\x16\x32\xf3\xbf\x95\x96\x71\x41\x5b\x97\xe7\xb9\x2d\x21\x11\xfc\x5e\x07\x83\x69\x02\x83\x26\x53\x16\x47\x6f\x66\x67\xc7\x91\xc4\xa7\x1d\xd3\xc2\x65\x7b\x43\xe2\x30\x7d\x4c\x1c\x56\xf4\xcf\x2a\xe6\x96\x89\xf4\xd1\xcb\x07\x8c\x56\xce\x79\xb4\x72\x92\xad\x9e\x4e\xa6\xf1\xab\xdd\x99\xbe\x5f\xe2\x86\x5d\x71\xa2\x89\x37\x6c\xe1\x0d\x5b\x79\x8b\x1e\xf5\xfd\xda\x54\x60\x6b\x85\x3f\x28\xb1\x47\x8d\x0d\xcc\x61\x1b\x73\x58\x63\xf6\x93\xb1\xba\x94\x51\xea\x2b\x15\xab\x3a\xaf\xfa\x3f\x2a\x56\x57\x2a\xbe\xc1\x0d\x5b\x70\xef\x16\xb1\xba\x12\xf1\xa2\xbc\xd6\xfa\xde\xab\x61\x75\xad\xe1\x45\x89\xad\x35\x9e\x91\xed\x4f\x6f\x66\x0c\xdf\x08\x67\xe9\xbc\xe7\x17\xee\xeb\x83\xdf\x0a\x17\x8b\xfe\xc3\xe2\x68\xad\x3d\x8c\x03\xe0\x8b\xba\xb1\xe2\xdf\xec\x28\x1f\xe7\x62\x47\x79\x5b\xfb\x26\xac\x25\xd5\x95\xf2\xd0\x40\x79\xe8\x40\x79\xe8\x45\x99\x36\x50\xa6\x1d\x28\xd3\x5e\x94\xa8\x81\x12\x75\xa0\x44\xbd\x28\x77\x0d\x94\xbb\x0e\x94\xbb\x5e\x94\x71\x03\x65\xdc\x81\x32\xee\x45\xa9\x1a\x28\x55\x07\x4a\xd5\x8b\xf2\xd2\x40\x79\xe9\x40\x79\xe9\x45\x99\xcf\x1b\x30\xc1\x78\x83\x33\x9f\xf7\x02\x3d\x3f\x37\x80\x82\xf1\x06\xe8\xf9\xb9\x17\x68\xd6\xc0\x99\x75\xc0\xcc\x7a\xbe\x0b\x37\x60\x82\xf1\x06\xc7\xbf\x31\xbb\xb7\xe2\xff\x02\x00\x00\xff\xff\xc6\x5e\x48\xe2\x85\x0d\x00\x00")
+var _embeddedrulesRulesPhp = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xac\x97\xdf\x73\xe2\x36\x10\xc7\xdf\xf9\x2b\xbe\xcc\x10\x06\xb8\x03\x37\xd3\xb7\x04\xc2\xb5\x6f\x9d\xb9\xe9\xcd\xb4\x8f\x9d\x04\x64\x7b\x8d\xd5\x18\xc9\x95\xe4\x03\x5a\xee\x7f\xef\x48\x82\x60\xc0\x3e\xec\xb4\x79\x60\xec\x95\x77\x3f\xfb\x4b\x5a\x65\x3a\xcf\xd3\xbc\xd3\x09\x46\xa3\x0e\x46\xf8\x24\x24\x17\x3a\xa7\xc8\x70\x29\xf0\xd3\xe7\xcf\x4e\x98\x71\x61\x48\xc1\xff\xc5\x5c\xb3\x30\xa3\x0e\x46\xc1\x49\x2d\x92\xeb\x35\x09\x83\xdf\x28\x97\xca\xc0\x90\x12\x4c\xed\x40\xdb\x5c\x91\xd6\x5c\x0a\x0d\x93\x32\x83\x88\x09\x84\x04\xcd\xd7\x79\xc6\x13\x4e\xf1\xc4\xa9\x87\x94\x48\x45\x40\x6f\x8b\xb9\xfd\x79\x40\x6f\xe7\x16\x58\xe2\xc1\x76\xe1\x20\x0c\x3a\x49\x21\xbc\x7f\x07\xcc\xef\xde\xda\x6e\x30\xc4\x3f\x1d\xc0\xf9\x04\xab\xbd\x66\xbb\x90\x10\xc9\x22\x8b\xa1\x28\xcf\x58\x44\x30\x29\xbd\xb9\xb7\xe1\x26\xc5\x9f\x85\x36\xe8\x45\x52\xc4\x07\x2d\xb3\xcb\x09\xa1\x94\x59\x49\x1a\x74\xe0\xdf\x30\x87\x51\x05\xe1\x01\x09\xcb\x34\x3d\x76\xbe\x03\xdc\x28\x6e\x08\x4c\x63\x39\xb0\xe6\x86\xce\xc0\xb2\x4c\x41\xf7\xff\xe5\x1c\xd3\x74\x84\x24\x7c\x5b\x4a\x9d\x13\xe5\x85\x22\xf4\xb6\x27\x5a\x29\xe5\x2d\x28\xf3\x0a\xca\xfc\x48\xb1\x76\xb9\xd6\x64\x06\xbd\xed\xb0\x6c\xfe\x5b\x6d\xc7\xe4\xd2\x90\x30\x9c\x65\x90\x39\x29\xe6\xca\x9b\x2b\x8a\x28\x26\x11\x91\xb5\x56\x90\xbe\x6a\x96\x3e\x7a\x6b\xa6\x5f\x31\x9b\xe1\x87\x47\x04\x81\x7d\x48\x99\x46\xca\x57\x29\xa9\xb2\x01\x93\x32\x81\xfe\x59\x4f\x0d\x4e\x06\x86\xce\xc2\x79\x73\x9d\x94\x8f\x7d\x15\xe0\x57\x69\xe8\x01\x1b\xb2\xdd\x64\xbd\x5e\x96\x7c\xe8\xce\x6c\x4a\x6c\x82\xd8\x95\xe7\xa0\xaf\x24\xbc\x09\x9e\x80\xbf\xed\x83\x88\x15\xab\xd4\xf8\x3e\x5c\xda\x8e\x88\x52\x8a\x5e\xbf\xe4\xcb\xc3\x6e\xb1\x6f\x1a\xa1\x34\xa9\x4f\x8b\xeb\x92\x20\x80\xfd\x54\xc3\x75\x15\xb8\x86\x90\x06\x0c\x2b\x29\xe3\xe3\x67\x48\xa4\x42\xc8\xcd\x86\x6b\x3a\x65\x74\x38\x71\xea\xde\x86\x4f\x3c\x17\x2b\x2c\xb9\xf8\xca\x32\x1e\x7b\xb3\x1f\xed\xae\xa5\xc8\x50\x0c\x51\xac\x43\x52\x48\x64\x21\x62\xb7\x23\x96\xe0\xda\x6b\x5b\xa4\xf3\x31\xa5\x2c\x4f\x8a\xec\x23\x42\x8a\x58\xa1\xfd\x06\x53\x52\x1a\xc8\xc4\x3d\xe7\x4a\x86\x19\xad\xad\x9f\xa7\xb4\x78\x47\xf0\x4b\x19\x0c\xa6\x08\x0c\x8a\x74\x91\x1d\xb4\x99\x99\x1c\x5a\x12\x9f\x36\x4c\x09\xeb\xed\x8d\x12\xfb\xee\x63\x62\xb7\xa0\xbf\x16\x21\x37\x4c\xc4\x0f\xae\x7c\x40\x6f\x61\x95\x7b\x0b\x5b\xb2\xc5\xe3\x51\xd4\x7f\x93\x5b\xd1\xb7\x4b\x5c\xb7\x29\x4e\x54\xf1\xba\x35\xbc\x6e\x2d\x6f\xd6\x22\xbe\x1f\xab\x02\xac\x8d\xf0\x3b\x21\xb6\x88\xb1\x82\xd9\xad\x63\x76\x4b\xcc\x76\x65\xdc\x5f\x96\x51\xaa\xab\x2a\xee\xcb\xbc\xfd\x7f\xa9\xe2\xfe\xaa\x8a\x67\xb8\x6e\x0d\xee\xdd\x45\xdc\x5f\x15\xf1\x22\xbc\xda\xf8\xde\x5b\xc3\xfd\x75\x0d\x2f\x42\xac\x8d\xf1\x84\xac\x3f\xbd\x99\xd6\x7c\x25\xac\xa4\xf1\x9c\x9f\xd9\x9f\x0f\x6e\x2a\x5c\x0c\xfa\x0f\xb3\x83\xb4\x74\x18\x7b\xc0\x97\xfc\xc6\x88\x3f\x9b\x51\xce\xce\xc5\x8c\x72\xb2\xfa\x49\x58\x72\xaa\x29\x65\x5c\x41\x19\x37\xa0\x8c\x5b\x51\x46\x15\x94\x51\x03\xca\xa8\x15\x25\xa8\xa0\x04\x0d\x28\x41\x2b\xca\x5d\x05\xe5\xae\x01\xe5\xae\x15\xa5\x5f\x41\xe9\x37\xa0\xf4\x5b\x51\xf6\x15\x94\x7d\x03\xca\xbe\x15\xe5\xa5\x82\xf2\xd2\x80\xf2\xd2\x8a\x32\x9d\x56\x60\xbc\xf0\x06\x67\x3a\x6d\x05\x7a\x7a\xaa\x00\x79\xe1\x0d\xd0\xd3\x53\x2b\xd0\xa4\x82\x33\x69\x80\x99\xb4\xbc\x0b\x57\x60\xbc\xf0\x06\xc7\xdd\x98\x1b\xde\x8a\x93\x64\x1c\xee\xc6\x52\x10\xd6\x5c\x1b\xf6\x7a\x75\x1b\x66\x7f\x44\xb2\x10\x66\xd0\x63\xc3\xe7\xf3\x23\xb5\xb4\x32\xbe\x7f\x3e\x3f\x58\x65\x92\xfc\xbc\xbb\xbf\x3a\x56\x8f\x63\xc5\x5e\xde\x58\x98\xed\x60\xff\x03\x14\x31\xc5\x30\x12\xf6\x96\xe7\x2c\x8e\xef\xdd\x75\x57\x80\x8b\x98\xb6\xa5\x04\xe0\x02\x8b\x31\x2c\xf8\x18\x7e\xd9\xd5\x8b\x3c\xdf\x04\x6b\xfe\x37\xc9\xe4\x16\xd9\x7f\x55\x89\x3e\x2d\x3d\xdb\xd4\xff\x1b\x00\x00\xff\xff\xfa\x48\x39\x30\x00\x0f\x00\x00")
 
 func embeddedrulesRulesPhpBytes() ([]byte, error) {
 	return bindataRead(
diff --git a/src/cmd/embeddedrules/rules.php b/src/cmd/embeddedrules/rules.php
index 7a7d2fe..4c68768 100644
--- a/src/cmd/embeddedrules/rules.php
+++ b/src/cmd/embeddedrules/rules.php
@@ -184,3 +184,22 @@ function assignOp() {
    */
   $x = $x ?? $y;
 }
+
+/**
+ * @comment Report potential off-by-one mistakes.
+ * @before  $a[count($a)]
+ * @after   $a[count($a)-1]
+ */
+function offBy1() {
+  /**
+   * @warning probably intended to use count-1 as an index
+   * @fix     $a[count($a) - 1]
+   */
+  $a[count($a)];
+
+  /**
+   * @warning probably intended to use sizeof-1 as an index
+   * @fix     $a[sizeof($a) - 1]
+   */
+  $a[sizeof($a)];
+}


Test patch:
diff --git a/src/tests/golden/testdata/embeddedrules/golden.txt b/src/tests/golden/testdata/embeddedrules/golden.txt
index 88501ab..9ff3ccb 100644
--- a/src/tests/golden/testdata/embeddedrules/golden.txt
+++ b/src/tests/golden/testdata/embeddedrules/golden.txt
@@ -34,6 +34,15 @@ $a = $a >> $b; // Could rewrite
 MAYBE   assignOp: could rewrite as `$a ??= $b` at testdata/embeddedrules/assignOp.php:52
 $a = $a ?? $b; // Could rewrite
 ^^^^^^^^^^^^^
+WARNING offBy1: probably intended to use count-1 as an index at testdata/embeddedrules/offBy1.php:7
+  $_ = $xs[count($xs)];
+       ^^^^^^^^^^^^^^^
+WARNING offBy1: probably intended to use sizeof-1 as an index at testdata/embeddedrules/offBy1.php:8
+  $_ = $xs[sizeof($xs)];
+       ^^^^^^^^^^^^^^^^
+WARNING offBy1: probably intended to use count-1 as an index at testdata/embeddedrules/offBy1.php:10
+  if ($tabs[count($tabs)] == "") {
+      ^^^^^^^^^^^^^^^^^^^
 WARNING precedence: == has higher precedence than & at testdata/embeddedrules/precedence.php:4
   $_ = 0 == $mask & $x;
        ^^^^^^^^^^^^^^^
diff --git a/src/tests/golden/testdata/embeddedrules/offBy1.php b/src/tests/golden/testdata/embeddedrules/offBy1.php
new file mode 100644
index 0000000..6ca3951
--- /dev/null
+++ b/src/tests/golden/testdata/embeddedrules/offBy1.php
@@ -0,0 +1,21 @@
+<?php
+
+function test_countindex_bad(array $xs, array $tabs) {
+  // Potentially results in undefined index access.
+  // In some very rare cases this code might work,
+  // but it doesn't look like a good practive to rely on it.
+  $_ = $xs[count($xs)];
+  $_ = $xs[sizeof($xs)];
+
+  if ($tabs[count($tabs)] == "") {
+    // This expression is not detected. :(
+    // See #713.
+    unset($tabs[count($tabs)]);
+  }
+}
+
+function test_countindex_good(array $xs) {
+  $_ = $xs[count($xs)-1];
+  $_ = $xs[sizeof($xs)-1];
+  $_ = $xs[0];
+}