[clang] [clang][analyzer] Model more getline/getdelim pre and postconditions (PR #83027)

Balázs Kéri via cfe-commits cfe-commits at lists.llvm.org
Thu Mar 21 09:47:11 PDT 2024


================
@@ -376,3 +377,122 @@ void fflush_on_open_failed_stream(void) {
   }
   fclose(F);
 }
+
+void getline_null_file() {
+  char *buffer = NULL;
+  size_t n = 0;
+  getline(&buffer, &n, NULL); // expected-warning {{Stream pointer might be NULL}}
+}
+
+void getdelim_null_file() {
+  char *buffer = NULL;
+  size_t n = 0;
+  getdelim(&buffer, &n, '\n', NULL); // expected-warning {{Stream pointer might be NULL}}
+}
+
+void getline_no_return_check() {
+  FILE *file = fopen("file.txt", "r");
+  if (file == NULL) {
+    return;
+  }
+
+  char *line = NULL;
+  size_t len = 0;
+  getline(&line, &len, file);
+
+  if (line[0] == '\0') {} // expected-warning {{The left operand of '==' is a garbage value}}
+
+  free(line);
+  fclose(file);
+}
+
+void getline_after_eof() {
+  FILE *file = fopen("file.txt", "r");
+  if (file == NULL) {
+    return;
+  }
+
+  size_t n = 10;
+  char *buffer = malloc(n);
+  ssize_t read = fread(buffer, n, 1, file);
+  if (!feof(file)) {
+    getline(&buffer, &n, file); // expected-warning {{File position of the stream might be 'indeterminate' after a failed operation. Can cause undefined behavior}}
+  }
+  fclose(file);
+  free(buffer);
+}
+
+void getline_feof() {
+  FILE *file = fopen("file.txt", "r");
+  if (file == NULL) {
+    return;
+  }
+
+  size_t n = 10;
+  char *buffer = malloc(n);
+  ssize_t read = fread(buffer, n, 1, file);
+  getline(&buffer, &n, file); // expected-warning {{File position of the stream might be 'indeterminate' after a failed operation. Can cause undefined behavior}} \\
+  expected-warning {{Read function called when stream is in EOF state. Function has no effect}}
+  fclose(file);
+  free(buffer);
+}
+
+void getline_feof_check() {
+  FILE *file = fopen("file.txt", "r");
+  if (file == NULL) {
+    return;
+  }
+
+  char *line = NULL;
+  size_t len = 0;
+  ssize_t r = getline(&line, &len, file);
+
+  if (r != -1) {
+    // success, end-of-file is not possible
+    int f = feof(file);
+    clang_analyzer_eval(f == 0); // expected-warning {{TRUE}}
+  } else {
+    // failure, end-of-file is possible, but not the only reason to fail
+    int f = feof(file);
+    clang_analyzer_eval(f == 0); // expected-warning {{TRUE}} \\
+    expected-warning {{FALSE}}
+  }
+  free(line);
+  fclose(file);
+}
+
+void getline_ret_value() {
+  FILE *file = fopen("file.txt", "r");
+  if (file == NULL) {
+    return;
+  }
+
+  size_t n = 0;
+  char *buffer = NULL;
+  ssize_t r = getline(&buffer, &n, file);
+
+  if (r > -1) {
+    // The return value does *not* include the terminating null byte.
+    // The buffer must be large enough to include it.
+    clang_analyzer_eval(n > r); // expected-warning{{TRUE}}
+  }
+
+  fclose(file);
+  free(buffer);
+}
+
+
+void getline_buffer_size_invariant(char *buffer) {
----------------
balazske wrote:

My concern was mostly that `buffer` is an argument and `n` is not and this looks not relevant for this test case, so if a `char *buffer = NULL` would be added (and the argument removed) it would look better. Otherwise an `assert` can be added to the code after the place where the second assumption on return value is made. I think too that the invalidation of `n` solves this problem.

https://github.com/llvm/llvm-project/pull/83027


More information about the cfe-commits mailing list