vendredi 31 décembre 2021

See what takes time in kernel compilation

Makefile projects can be profiled and debugged with the remake project. You need a recent version of remake that supports --profile=json (see --help).

This works with parallel builds as well (I've use -j8 here but use whatever you have).

  • "name" is the makefile target
  • "len" is the duration in seconds


$ cd linux-git
$ mkdir -p json && remake --profile=json --profile-directory=$PWD/json -j8
$ cd json
 
# Look at items sorted by total time (time taken by all dependencies + the recipe length)
$ jq -s '[.[] |.targets[]|select(.recipe!=null)|{ name: .name, len: (.end-.start) }]|sort_by(-.len)' *.json
[
{
"name": "bzImage",
"len": 77.26787257194519
},
{
"name": "vmlinux",
"len": 76.33200693130493
},
{
"name": "arch/x86/kernel/vmlinux.lds",
"len": 75.16884279251099
},
{
"name": "drivers",
"len": 75.013343334198
},
{
"name": "arch/x86/lib/lib.a",
"len": 75.01327657699585
},
....


# Or look at item sorted by recipe time only (without the dependencies)
$ jq -s '[.[] |.targets[]|select(.recipe!=null)|{ name: .name, len: (.end-.recipe) }]|sort_by(-.len)' *json
[
{
"name": "drivers",
"len": 47.737035512924194
},
{
"name": "fs",
"len": 45.02375769615173
},
{
"name": "net",
"len": 40.28609848022461
},
{
"name": "net/ipv4",
"len": 31.197179794311523
},
...



mercredi 9 octobre 2019

AdressSanitizer link error and autoconf configure script

If you get linking errors like the following when trying to use AddressSanitizer in an autoconf project:
spnego.c:(.text+0x3c): undefined reference to `__asan_option_detect_stack_use_after_return'
/bin/ld: spnego.c:(.text+0x49): undefined reference to `__asan_stack_malloc_1'
/bin/ld: spnego.c:(.text+0xfd): undefined reference to `__asan_report_load16'
/bin/ld: spnego.c:(.text+0x180): undefined reference to `__asan_report_load8'
/bin/ld: spnego.c:(.text+0x1ad): undefined reference to `__asan_report_load8'
/bin/ld: spnego.c:(.text+0x1ff): undefined reference to `__asan_report_load8'
/bin/ld: spnego.c:(.text+0x22e): undefined reference to `__asan_report_load8'


It's because the configure guesses something wrong. You can work around it by providing the answer via an env var like so:

CFLAGS='-fsanitize=address' \
LDFLAGS='-fsanitize=address' \
ac_cv_func_malloc_0_nonnull=yes \
./configure

mercredi 2 mai 2018

notmuch mark as spam

Adds a keybinding that applies/removes tags to the currently viewed email or --when looking at search results-- the thread under point.
(defun mark-spam ()
  (interactive)
  (let ((tlist '("+spam" "-unread" "-inbox" "-new")))
    (if (eq major-mode 'notmuch-show-mode)
 (progn
   (let ((id (notmuch-show-get-message-id)))
     (notmuch-tag id tlist)
     (notmuch-show-next-thread)))
      (notmuch-search-tag tlist)
      (notmuch-search-next-thread))))

(define-key 'notmuch-search-mode-map (kbd "S") 'mark-spam)
(define-key 'notmuch-show-mode-map (kbd "S") 'mark-spam)

vendredi 17 février 2017

_ddebug table is empty in a CONFIG_DYNAMIC_DEBUG build

_ddebug table is empty in a CONFIG_DYNAMIC_DEBUG build


If you are pulling your hair and wondering why your kernel does not have a /sys/kernel/debug/dynamic_debug directory despite having CONFIG_DYNAMIC_DEBUG enabled and debugfs mounted, the issue could be that you built the kernel using GOLD linker. You can simply switch through the LD Makefile variable (make LD=ld.bfd)

lundi 16 janvier 2017

Extracting and applying a clean git patchset from an email thread with notmuch

I just wrote a quick python script to filter the actual patches from a email thread send with git send-email (those "[PATCH 1/15] xyz" threads). The script assumes you use notmuch. Give it a search expression that returns a list of messages that includes all the patches like the thread-id (C-u c i while looking at the thread in emacs).

You can get it on github. There is a small elisp snippet in the README to run the script and apply the patchset from emacs.

vendredi 13 janvier 2017

Copy current buffer absolute file path to the OS clipboard

(defun copy-full-path ()
  (interactive)
  (let ((path (buffer-file-name)))
    (when path
      (setq path (expand-file-name path))
      (funcall interprogram-cut-function path)
      (message "copied %s in clipboard" path))))

Quick-and-dirty email template for notmuch

(defun prepare-report ()
  (interactive)
  (notmuch-mua-new-mail)
  (insert "foo@example.com")
  (search-forward "Subject: ")
  (insert "work report week " (format-time-string "%W"))
  (search-forward "\n\n")
  (backward-char)
  (insert "my super email content\n"))

Jump to code from Coverity report emails (notmuch)

If you use notmuch as your email client and receive Coverity reports emails, here's a quick function that compiles the reports to a single buffer (newest first), applies some color on the errors, and enables the compilation minor mode so you can use Emacs regular "jump to next error" key.

Adapt the default-directory to your project source.

(defun samba-coverity ()
  (interactive)
  (let ((b (get-buffer-create "*coverity*")))
    (with-current-buffer b
      (erase-buffer)
      (setq default-directory (expand-file-name "~/prog/samba-git"))
      (insert
       (shell-command-to-string
 (concat
  "for i in $(notmuch search --output=messages"
  " 'from:scan-admin@coverity.com'); do notmuch show $i; done"
  " | perl -pE 's,^/(\\S+): (\\d+) in,cov:$1:$2: in,'")))
      (goto-char (point-min))
      (while (search-forward-regexp (rx bol ">>>") nil t)
 (let ((beg (save-excursion (beginning-of-line) (point)))
       (end (save-excursion (end-of-line) (point))))
   (put-text-property beg end 'face 'error)))
      (goto-char (point-min))      
      (compilation-minor-mode))
    (switch-to-buffer b)))

vendredi 8 avril 2016

Insert new rpm-style changelog entry

(defun new-changelog-entry ()
  (interactive)
  (let ((line-len 67)
        (date (substring (shell-command-to-string "LC_ALL=C date -u") 0 -1))
        ;; (email user-mail-address) probably wrong
        (email (concat user-real-login-name "@suse.com"))
        (final-pos))
    (goto-char (point-min))
    (insert (make-string line-len ?-) "\n"
            date " - " email "\n\n"
            " - ")
    (setq final-pos (point))
    (insert "\n\n")
    (goto-char final-pos)))

mardi 22 mars 2016

Analyzing Samba with PVS-Studio on Linux

If you have followed the last developement in C/C++ static analysis tools you must have heard of PVS-Studio. I heard of them through the articles they publish on their site where they analyze open source projects. They have analyzed quite big projects including the Linux kernel, Qt, Unreal, … and they have always managed to find crazy bugs that have been siting there for some time, undetected. Typos, bad copy-paste, undefined behaviours, non-sense code, syntax errors that miraculously still compile… As John Carmack said:

Everything that is syntactically legal that the compiler will accept will eventually wind up in your codebase.

Unfortunately, the tool is advertized as Windows-only. The program comes in the form of a Visual Studio plugin or a separate independent program if you don't have the former. I first used it back in 2014 on a relatively large C++ codebase used internally in the computer graphics department of my university in Lyon (LIRIS). We were using Visual Studio (which I normally rarely use) so I thought I should give it a try. I was really pleased with the results and kept checking the PVS-Studio website for more articles.

Two years and several PVS-Studio articles later I started working on Samba. The whole project is about 2 millions lines of C code and I thought it would be a good candidate for PVS-Studio. A static analysis tool shouldn't have too much platform-specific code so I started thinking about it. The analyzer works on preprocessed code so it needs to run the preprocessor on your sources and for that it needs all your preprocessor flags, macros and includes path. Gathering this automatically can be painful. For this step I wrote a strace-based script that "spies" your build tool for compiler calls, that way it should be build-tool agnostic. You can find the latest version of this tool on github.

I sent the script to the PVS-Studio guys and after some back and forth, I was given an experimental Linux build of PVS-Studio (thanks again!). The script now covers all the analyzing process from gathering compiler flags, to analyzing, displaying and filtering the results.

Here's how you use it.

In order to not have to point to the license and binary at every use you can set up env variables.

    $ export PVS_LICENSE=~/prog/pvs/PVS-Studio.lic
    $ export PVS_BIN=~/prog/pvs/PVS-Studio

Go to your project directory and generate a config file for your C++11 project.

    $ pvs-tool genconf  -l C++11 pvs.cfg

If you need to configure the build before building, do it. Then trace the actual build (your build command should go after the --).

    $ pvs-tool trace    -- make -j8

This will output a "strace_out" file which have all the information we need. The analyze step will process that file to extract all compilation units and preprocessor flags, and run PVS-Studio on it.

    $ pvs-tool analyze  pvs.cfg
    pvs-tool: deleting existing log pvs.log...
    001/061 [ 0%] analyzing /hom../rtags/src/ClangIndexer.cpp...
    002/061 [ 1%] analyzing /hom../rtags/src/CompilerManager.cpp...
    003/061 [ 3%] analyzing /hom../rtags/src/CompletionThread.cpp...
    004/061 [ 4%] analyzing /hom../rtags/src/DependenciesJob.cpp...
    <...>
    061/061 [98%] analyzing /hom../rtags/src/rp.cpp...
    pvs-tool: analysis finished
    pvs-tool: cleaning output...
    pvs-tool: done (2M -> 0M)

The cleaning part removes duplicated lines and will drastically reduce the file size of big results.

You can now view the results, grouped by files

    $ pvs-tool view     pvs.log

The output is similar to gcc/make so it works as-is in e.g. the Emacs editor and I can use my usual builtin goto-error functions. You can disable diagnostics e.g.

    $ pvs-tool view -d V2006,V2008 pvs.log

By default it only shows level 1 errors but you can change it with -l.

You can look at the -h help messsage for more.


PVS-Studio found many problems in Samba. Most of them were false positives but this is expected when you use any static analysis tool on large codebase. The important thing is it also found real bugs. I'm going to share the most interesting ones along with their fix, in the form of diffs.

- if (memcmp(u0, _u0, sizeof(u0) != 0)) {
+ if (memcmp(u0, _u0, sizeof(*u0)) != 0) {
   printf("USER_MODALS_INFO_0 struct has changed!!!!\n");
   return -1;
  }

Here, the closing parenthesis was misplaced. The result of the sizeof comparaison was used as the compared memory size (always 1 byte). Also, we want the size of the type u0 points to, not the size of the pointer.


   handle_main_input(regedit, key);
   update_panels();
   doupdate();
- } while (key != 'q' || key == 'Q');
+ } while (key != 'q' && key != 'Q');

Here, we want to exit the loop on any case of the letter 'q'.


  uid = request->data.auth.uid;
 
- if (uid < 0) {
+ if (uid == (uid_t)-1) {
   DEBUG(1,("invalid uid: '%u'\n", (unsigned int)uid));
   return -1;
  }

Here we tested the uid_t type for negative values.

The sign of the uid_t type is left unspecified by POSIX. It's defined as an unsigned 32b int on Linux, therefore the < 0 check is always false.

For unsigned version of uid_t, in the comparaison uid == -1 the compiler will implicitely cast -1 to unsigned making it a valid test for both signed and unsigned version of uid_t. I've made the cast explicit because less magic is better in this case.


  DEBUG(4,("smb_pam_auth: PAM: Authenticate User: %s\n", user));
 
- pam_error = pam_authenticate(pamh, PAM_SILENT | allow_null_passwords ? 0 : PAM_DISALLOW_NULL_AUTHTOK);
+ pam_error = pam_authenticate(pamh, PAM_SILENT | (allow_null_passwords ? 0 : PAM_DISALLOW_NULL_AUTHTOK));
  switch( pam_error ){
   case PAM_AUTH_ERR:
    DEBUG(2, ("smb_pam_auth: PAM: Authentication Error for user %s\n", user));

Simple operator priority error.


  gensec_init();
  dump_args();
 
- if (check_arg_numeric("ibs") == 0 || check_arg_numeric("ibs") == 0) {
+ if (check_arg_numeric("ibs") == 0 || check_arg_numeric("obs") == 0) {
   fprintf(stderr, "%s: block sizes must be greater that zero\n",
     PROGNAME);
   exit(SYNTAX_EXIT_CODE);

Here the test was doing the same thing twice.


   if (!gss_oid_equal(&name1->gn_type, &name2->gn_type)) {
    *name_equal = 0;
   } else if (name1->gn_value.length != name2->gn_value.length ||
-      memcmp(name1->gn_value.value, name1->gn_value.value,
+      memcmp(name1->gn_value.value, name2->gn_value.value,
    name1->gn_value.length)) {
    *name_equal = 0;
   }

Here memcmp was called with the same pointer, thus comparing the same region of memory with itself.


  ioctl_arg.fd = src_fd;
  ioctl_arg.transid = 0;
  ioctl_arg.flags = (rw == false) ? BTRFS_SUBVOL_RDONLY : 0;
- memset(ioctl_arg.unused, 0, ARRAY_SIZE(ioctl_arg.unused));
+ memset(ioctl_arg.unused, 0, sizeof(ioctl_arg.unused));
  len = strlcpy(ioctl_arg.name, dest_subvolume,
         ARRAY_SIZE(ioctl_arg.name));
  if (len >= ARRAY_SIZE(ioctl_arg.name)) {

Here memset was given the size as a number of elements instead of a byte size.


  if (n + IDR_BITS < 31 &&
-     ((id & ~(~0 << MAX_ID_SHIFT)) >> (n + IDR_BITS))) {
+     ((id & ~(~0U << MAX_ID_SHIFT)) >> (n + IDR_BITS))) {
   return NULL;
  }

Using negative values on the left-side of a left-shift operation is an Undefined Behaviour in C.


  if (cli_api(cli,
        param, sizeof(param), 1024, /* Param, length, maxlen */
-       data, soffset, sizeof(data), /* data, length, maxlen */
+       data, soffset, data_size, /* data, length, maxlen */
        &rparam, &rprcnt,   /* return params, length */
        &rdata, &rdrcnt))   /* return data, length */
  {

Here data used to be a stack allocated array but was changed to a heap allocated buffer without updating the sizeof use.


   goto query;
  }
 
- if ((p->auth.auth_type != DCERPC_AUTH_TYPE_NTLMSSP) ||
-     (p->auth.auth_type != DCERPC_AUTH_TYPE_KRB5) ||
-     (p->auth.auth_type != DCERPC_AUTH_TYPE_SPNEGO)) {
+ if (!((p->auth.auth_type == DCERPC_AUTH_TYPE_NTLMSSP) ||
+       (p->auth.auth_type == DCERPC_AUTH_TYPE_KRB5) ||
+       (p->auth.auth_type == DCERPC_AUTH_TYPE_SPNEGO))) {
   return NT_STATUS_ACCESS_DENIED;
  }

Prior to this fix, the condition was always true and the function always returned "access denied".


- Py_RETURN_NONE;
  talloc_free(frame);
+ Py_RETURN_NONE;
}

Py_RETURN_NONE is a macro that hides a return statement. In this python binding many functions were returning before freeing heap allocated memory. This problem was present in dozens of functions.


  int i;
- for (i=0;ARRAY_SIZE(results);i++) {
+ for (i=0;i<ARRAY_SIZE(results);i++) {
   if (results[i].res == res) return results[i].name;
  }
  return "*";

Here the for condition was always true.


 int create_unlink_tmp(const char *dir)
 {
+ if (!dir) {
+  dir = tmpdir();
+ }
+
  size_t len = strlen(dir);
  char fname[len+25];
  int fd;
  mode_t mask;
 
- if (!dir) {
-  dir = tmpdir();
- }
-

Here the dir pointer was used before the null-check.

Overall I'm really pleased with PVS-Studio and I would recommend it. Unfortunately it's not officially available on Linux. Although you can just contact them if you're interested it seems :)

jeudi 17 mars 2016

Edit files and run stuff on remote hosts (Tramp quick how to)

Edit file on remote host (ssh .config aware)
C-x C-f /<host>:<path>
You can use your .ssh config hosts when replacing <host>. This sets the buffer current-directory to the remote one, and many parts of emacs take advantage of that (M-x shell, compile, ...).

jeudi 3 mars 2016

Guess C indent rules based on the buffer content

Guess basic indent rules from the buffer content. You can add this to the c-mode hook, works good enough.

(defun guess-c-indent-rules ()
  (interactive)
  (save-excursion
    (goto-char (point-min))
    (cond
     ;; check GNU first (1-level indent is 2 space)
     ((search-forward-regexp (rx bol "  " (or "if" "do" "while" "for" "return")) nil t)
      (message "GNU style detected, setting it...")
      (c-set-style "gnu"))

     ;; linux style tab indent (samba)
     ((search-forward-regexp (rx bol (+ "\t") (or "if" "do" "while" "for"))  nil t)
      (message "indenting with 8-spaces tabs detected, linux style...")
      (c-set-style "linux")
      (setq indent-tabs-mode t
     c-basic-offset 8))

     ;; 4 space mode
     ((search-forward-regexp (rx bol (+ "    ") (or "if" "do" "while" "for")) nil t)
      (message "indenting with 4 spaces...")
      (setq indent-tabs-mode nil
     c-basic-offset 4))
     (t
      (message "cannot guess indentation, you're on your own!")))))

jeudi 29 octobre 2015

Delete lines matching a regex; Keep lines matching a regexp

kill-lines-rx will prompt you for a regex and delete all lines matching it. Use with a prefix argument (C-u) to highlight and ask before removing.
(defun kill-lines-rx (&optional ask)
  (interactive "P")
  (save-excursion
    (goto-char (point-min))
    (let ((rx (read-regexp "Rx: ")))
      (while (search-forward-regexp rx nil t)
 (beginning-of-line)
 (set-mark (save-excursion (end-of-line) (point)))
 (when (or (not ask) (y-or-n-p "Kill? "))
   (delete-region (point) (mark))
   (delete-char 1))))))
kill-lines-unless-rx will prompt you for a regex and delete all lines not matching it. Use with a prefix argument (C-u) to highlight and ask before keeping.
(defun kill-lines-unless-rx (&optional ask)
  (interactive "P")
  (let (keep)
    (save-excursion
      (goto-char (point-min))
      (let ((rx (read-regexp "Rx: ")))
 (while (search-forward-regexp rx nil t)
   (beginning-of-line)
   (set-mark (save-excursion (end-of-line) (point)))
   (when (or (not ask) (y-or-n-p "Keep? "))
     (push (buffer-substring (point) (mark)) keep))
   (forward-line))))
    (erase-buffer)
    (dolist (e (nreverse keep))
      (insert e "\n"))))

vendredi 4 septembre 2015

org-mode live html export preview in the browser

Using the MozRepl Firefox extension along with Emacs moz.el package you can export your current org document to html on each buffer change or on each save. There's a bunch of JS code to make Firefox resuse existing tabs instead of opening new ones. Run M-x toggle-live-preview or M-x toggle-preview-on-save depending on what you want.
(defvar moz-useful-functions "
function find_tab_with_url(url) {
    var bs = gBrowser.browsers
    for (var i = 0; i < bs.length; i++) {
 try {
     if (bs[i].currentURI.spec == url)
  return i
 } catch (e) {}
    }
    return -1
}

function select_tab(t) {
    if (gBrowser.selectedTab != t)
        gBrowser.selectedTab = t
}

function add_or_reload_url (url) {
    var i = find_tab_with_url(url)
    var t = ''
    if (i < 0) {
 t = gBrowser.addTab(url)
    } else {
 gBrowser.browsers[i].reload()
        t = gBrowser.tabs[i]
    }
    select_tab(t) 
}
")

(defun preview-buffer-in-firefox ()
  (interactive)
  (require 'moz)
  (moz-send-string moz-useful-functions)
  (let ((fn (org-html-export-to-html)))
    (moz-send-string (concat "add_or_reload_url(\"file://" (expand-file-name fn) "\");\n"))))

(defun live-preview (&optional beg end len)
  (when (or (not (and beg end len)) (and beg end len (/= (1+ (- end beg)) len)))
    (message "%s: update!" (format-time-string "%r"))
    (preview-buffer-in-firefox)))

(defun toggle-live-preview ()
  (interactive)
  (if (memq 'live-preview first-change-hook)
      (remove-hook 'first-change-hook 'live-preview 'buffer-local)
    (add-hook 'first-change-hook 'live-preview nil 'buffer-local)))

(defun toggle-preview-on-save ()
  (interactive)
  (if (memq 'live-preview after-save-hook)
      (remove-hook 'after-save-hook 'live-preview 'buffer-local)
    (add-hook 'after-save-hook 'live-preview nil 'buffer-local)))

lundi 17 août 2015

Quickly edit your init file

Nothing fancy about this, just handy: opens my init file and place cursor at the end.
(defun init ()
  (interactive)
  (find-file "~/.emacs.d/init.el")
  (goto-char (point-max)))

Rename file and buffer

I don't know why this is not built-in...
(defun rename-file-and-buffer (new-name)
  "Renames both current buffer and file it's visiting to NEW-NAME."
  (interactive "sNew name: ")
  (let ((name (buffer-name))
        (filename (buffer-file-name)))
    (if (not filename)
        (message "Buffer '%s' is not visiting a file!" name)
      (if (get-buffer new-name)
          (message "A buffer named '%s' already exists!" new-name)
        (progn
          (rename-file name new-name 1)
          (rename-buffer new-name)
          (set-visited-file-name new-name)
          (set-buffer-modified-p nil))))))

Open current file as root

Don't think I've written this one myself and I don't remember where it comes from. Oh well.
(defun sudo-edit (&optional arg)
  "Edit currently visited file as root.

With a prefix ARG prompt for a file to visit.
Will also prompt for a file to visit if current
buffer is not visiting a file."
  (interactive "P")
  (if (or arg (not buffer-file-name))
      (find-file (concat "/sudo:root@localhost:"
                         (ido-read-file-name "Find file(as root): ")))
    (find-alternate-file (concat "/sudo:root@localhost:" buffer-file-name))))

dimanche 16 août 2015

Number line in region using custom printf-like format

By default starts at zero, but you can use C-u 50 M-x my-number-line to make it start at 50...
The commands asks for a printf format that will be inserted at the start of each line, so you can do right alignment, hex, whatever.

Example region:
Pellentesque tristique imperdiet tortor. Cras placerat accumsan
nulla. Donec hendrerit tempor tellus. Nam a sapien. Nam vestibulum
accumsan nisl. Donec at pede. Pellentesque dapibus suscipit
ligula. Nunc porta vulputate tellus. Nunc aliquet, augue nec
adipiscing interdum, lacus tellus malesuada massa, quis varius mi
purus non odio. Nunc rutrum turpis sed pede. Etiam vel tortor sodales
tellus ultricies commodo.
Mark all lines, M-x my-number-line RET 0x%03x SPC RET and BAM, lines are prefixed with hex line numbers:
0x000 Pellentesque tristique imperdiet tortor. Cras placerat accumsan
0x001 nulla. Donec hendrerit tempor tellus. Nam a sapien. Nam vestibulum
0x002 accumsan nisl. Donec at pede. Pellentesque dapibus suscipit
0x003 ligula. Nunc porta vulputate tellus. Nunc aliquet, augue nec
0x004 adipiscing interdum, lacus tellus malesuada massa, quis varius mi
0x005 purus non odio. Nunc rutrum turpis sed pede. Etiam vel tortor sodales
0x006 tellus ultricies commodo.
(defun my-number-line (arg beg end fmt)
  (interactive "P\nr\nsformat: ")
  (let ((n (if (numberp arg) arg 0)))
    (save-excursion
      (goto-char beg)
      (beginning-of-line)
      (while (< (point) end)
        (beginning-of-line)
        (let ((s (format fmt n)))
          (insert s)
          (incf end (length s)))
        (incf n)
        (forward-line)
        (beginning-of-line)))))

Put readable file encoding and line endings mode in the mode-line

I was never able to decipher the default mode-line information about encoding/line endings so I changed it:

(defvar my-mode-line-coding-format
  '(:eval
    (let* ((code (symbol-name buffer-file-coding-system))
           (eol-type (coding-system-eol-type buffer-file-coding-system))
           (eol (if (eq 0 eol-type) "UNIX"
                  (if (eq 1 eol-type) "DOS"
                    (if (eq 2 eol-type) "MAC"
                      "???")))))
      (concat code " " eol " "))))
(put 'my-mode-line-coding-format 'risky-local-variable t)
(setq-default mode-line-format (substitute 'my-mode-line-coding-format 'mode-line-mule-info mode-line-format))
Now you get "utf-8 UNIX" in place of the cryptic "U*:%" whatever bullshit it was.

Make delete-forward do what I mean

  • If you're on a non-space character, delete forward until the next space character.
  • If you're on a space character, delete forward until the next non-space character
(defun my-delete-space-forward ()
  (interactive)
  (let* ((char (buffer-substring-no-properties (point) (1+ (point))))
         (notspace-rx (rx (not (any "\t\n "))))
         (space-rx (rx (any "\t\n ")))
         (rx (if (string-match-p space-rx char) notspace-rx space-rx))
         (end-pos (save-excursion
                    (search-forward-regexp rx nil 'end))))

    (delete-region (point) (if end-pos (1- end-pos) (point-max)))))