]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
analyzer: model getenv
authorRidham Khurana <khurana.ridham222@gmail.com>
Wed, 18 Mar 2026 14:44:25 +0000 (10:44 -0400)
committerDavid Malcolm <dmalcolm@redhat.com>
Tue, 28 Apr 2026 23:11:26 +0000 (19:11 -0400)
Model getenv as a known function so that the analyzer bifurcates on
its return value, handling both the NULL and non-NULL cases.

Also check that its argument is a null-terminated string.

gcc/analyzer/ChangeLog:
* kf.cc (class kf_getenv): New.
(kf_getenv::impl_call_post): New.
(register_known_functions): Register kf_getenv.

gcc/testsuite/ChangeLog:
* gcc.dg/analyzer/getenv-1.c: New test.

Signed-off-by: Ridham Khurana <khurana.ridham222@gmail.com>
gcc/analyzer/kf.cc
gcc/testsuite/gcc.dg/analyzer/getenv-1.c [new file with mode: 0644]

index 3860a763f7458b7609d91d2f72c6987611417eba..5d2af3866a79c20559226882319d4ffc9105782f 100644 (file)
@@ -1501,6 +1501,94 @@ public:
   }
 };
 
+/* Handler for calls to "getenv".
+     char *getenv (const char *name);
+
+   Returns either NULL (if the environment variable is not found),
+   or a pointer to the value string.  */
+
+class kf_getenv : public known_function
+{
+public:
+  bool matches_call_types_p (const call_details &cd) const final override
+  {
+    return (cd.num_args () == 1 && cd.arg_is_pointer_p (0));
+  }
+
+  void impl_call_pre (const call_details &cd) const final override
+  {
+    cd.check_for_null_terminated_string_arg (0);
+  }
+
+  void impl_call_post (const call_details &cd) const final override;
+};
+
+void
+kf_getenv::impl_call_post (const call_details &cd) const
+{
+  class getenv_call_info : public call_info
+  {
+  public:
+    getenv_call_info (const call_details &cd, bool found)
+    : call_info (cd), m_found (found)
+    {
+    }
+
+    void print_desc (pretty_printer &pp) const final override
+    {
+      if (m_found)
+       pp_printf (&pp,
+                  "when %qE returns non-NULL",
+                  get_fndecl ());
+      else
+       pp_printf (&pp,
+                  "when %qE returns NULL",
+                  get_fndecl ());
+    }
+
+    bool update_model (region_model *model,
+                      const exploded_edge *,
+                      region_model_context *ctxt) const final override
+    {
+      const call_details cd (get_call_details (model, ctxt));
+      if (tree lhs_type = cd.get_lhs_type ())
+       {
+         region_model_manager *mgr = model->get_manager ();
+         const svalue *result;
+         if (m_found)
+           {
+             /* Return a conjured non-NULL pointer.  */
+             result
+               = mgr->get_or_create_conjured_svalue (lhs_type,
+                                                      &cd.get_call_stmt (),
+                                                      cd.get_lhs_region (),
+                                                      conjured_purge (model,
+                                                                      ctxt));
+             const svalue *null_ptr
+               = mgr->get_or_create_int_cst (lhs_type, 0);
+             model->add_constraint (result, NE_EXPR, null_ptr, ctxt);
+           }
+         else
+           result = mgr->get_or_create_int_cst (lhs_type, 0);
+         cd.maybe_set_lhs (result);
+       }
+      return true;
+    }
+  private:
+    bool m_found;
+  };
+
+  /* Body of kf_getenv::impl_call_post.  */
+  if (cd.get_ctxt ())
+    {
+      cd.get_ctxt ()->bifurcate
+       (std::make_unique<getenv_call_info> (cd, false));
+      cd.get_ctxt ()->bifurcate
+       (std::make_unique<getenv_call_info> (cd, true));
+      cd.get_ctxt ()->terminate_path ();
+    }
+}
+
 /* Handler for calls to "putenv".
 
    In theory we could try to model the state of the environment variables
@@ -3018,6 +3106,7 @@ register_known_functions (known_function_manager &kfm,
   /* Known POSIX functions, and some non-standard extensions.  */
   {
     kfm.add ("fopen", std::make_unique<kf_fopen> ());
+    kfm.add ("getenv", std::make_unique<kf_getenv> ());
     kfm.add ("mkdtemp", std::make_unique<kf_mktemp_simple> (
                          kf_mktemp_family::outcome::null_ptr));
     kfm.add ("mkostemp", std::make_unique<kf_mkostemp> ());
diff --git a/gcc/testsuite/gcc.dg/analyzer/getenv-1.c b/gcc/testsuite/gcc.dg/analyzer/getenv-1.c
new file mode 100644 (file)
index 0000000..43afdf1
--- /dev/null
@@ -0,0 +1,49 @@
+#include <stdlib.h>
+#include "analyzer-decls.h"
+
+extern void do_something (const char *p)
+  __attribute__((nonnull (1)));
+
+/* Verify that the analyzer bifurcates on getenv, considering
+   both NULL and non-NULL outcomes.  */
+
+void test_null_deref (void)
+{
+  char *p = getenv ("HOME"); /* { dg-message "when 'getenv' returns NULL" } */
+  *p = 'a'; /* { dg-warning "dereference of NULL 'p'" } */
+}
+
+void test_checked (void)
+{
+  char *p = getenv ("HOME");
+  if (p)
+    do_something (p); /* no warning: p is non-NULL here.  */
+}
+
+void test_unchecked_use (void)
+{
+  char *p = getenv ("PATH"); /* { dg-message "when 'getenv' returns NULL" } */
+  do_something (p); /* { dg-warning "use of NULL 'p' where non-null expected" } */
+}
+
+void test_bifurcation (void)
+{
+  char *p = getenv ("EDITOR");
+  if (p)
+    __analyzer_eval (p != NULL); /* { dg-warning "TRUE" } */
+}
+
+void test_getenv_returns_nonnull (void)
+{
+  char *p = getenv ("TERM");
+  if (!p)
+    return;
+  __analyzer_eval (p != NULL); /* { dg-warning "TRUE" } */
+}
+
+void test_unterminated (void)
+{
+  char buf[3] = "abc";
+  getenv (buf); /* { dg-warning "stack-based buffer over-read" } */
+  /* { dg-message "while looking for null terminator for argument 1 \\('&buf'\\) of 'getenv'..." "event" { target *-*-* } .-1 } */
+}