Training courses

Kernel and Embedded Linux

Bootlin training courses

Embedded Linux, kernel,
Yocto Project, Buildroot, real-time,
graphics, boot time, debugging...

Bootlin logo

Elixir Cross Referencer

#! /usr/bin/lua
-- $NetBSD: check-msgs.lua,v 1.18 2023/07/02 23:40:23 rillig Exp $

--[[

usage: lua ./check-msgs.lua *.c *.y

Check that the message text in the comments of the C source code matches the
actual user-visible message text in err.c.

]]


local function load_messages()
  local msgs = {} ---@type table<string>string

  local f = assert(io.open("err.c"))
  for line in f:lines() do
    local msg, id = line:match("%s*\"(.+)\",%s*/%*%s*(Q?%d+)%s*%*/$")
    if msg ~= nil then
      msgs[id] = msg
    end
  end

  f:close()

  return msgs
end


local had_errors = false
---@param fmt string
function print_error(fmt, ...)
  print(fmt:format(...))
  had_errors = true
end


local function check_message(fname, lineno, id, comment, msgs)
  local msg = msgs[id]

  if msg == nil then
    print_error("%s:%d: id=%s not found", fname, lineno, id)
    return
  end

  msg = msg:gsub("/%*", "**")
  msg = msg:gsub("%*/", "**")
  msg = msg:gsub("\\(.)", "%1")

  if comment == msg then
    return
  end

  local prefix = comment:match("^(.-)%s*%.%.%.$")
  if prefix ~= nil and msg:find(prefix, 1, 1) == 1 then
    return
  end

  print_error("%s:%d:   id=%-3s   msg=%-40s   comment=%s",
    fname, lineno, id, msg, comment)
end

local message_prefix = {
  error = "",
  error_at = "",
  warning = "",
  warning_at = "",
  query_message = "Q",
  c99ism = "",
  c11ism = "",
  c23ism = "",
  gnuism = "",
}

local function check_file(fname, msgs)
  local f = assert(io.open(fname, "r"))
  local lineno = 0
  local prev = ""
  for line in f:lines() do
    lineno = lineno + 1

    local func, id = line:match("^%s+([%w_]+)%((%d+)[),]")
    local prefix = message_prefix[func]
    if prefix then
      id = prefix .. id
      local comment = prev:match("^%s+/%* (.+) %*/$")
      if comment ~= nil then
        check_message(fname, lineno, id, comment, msgs)
      else
        print_error("%s:%d: missing comment for %s: /* %s */",
          fname, lineno, id, msgs[id])
      end
    end

    prev = line
  end

  f:close()
end


local function file_contains(filename, text)
  local f = assert(io.open(filename, "r"))
  local found = f:read("a"):find(text, 1, true)
  f:close()
  return found
end


-- Ensure that each test file for a particular message mentions the full text
-- of that message and the message ID.
local function check_test_files(msgs)
  local testdir = "../../../tests/usr.bin/xlint/lint1"
  local cmd = ("cd '%s' && printf '%%s\\n' msg_[0-9][0-9][0-9]*.c"):format(testdir)
  local filenames = assert(io.popen(cmd))
  for filename in filenames:lines() do
    local msgid = filename:match("^msg_(%d%d%d)")
    if msgs[msgid] then
      local unescaped_msg = msgs[msgid]:gsub("\\(.)", "%1")
      local expected_text = ("%s [%s]"):format(unescaped_msg, msgid)
      local fullname = ("%s/%s"):format(testdir, filename)
      if not file_contains(fullname, expected_text) then
        print_error("%s must contain: %s", fullname, expected_text)
      end
    end
  end
  filenames:close()
end

local function main(arg)
  local msgs = load_messages()
  for _, fname in ipairs(arg) do
    check_file(fname, msgs)
  end
  check_test_files(msgs)
end

main(arg)
os.exit(not had_errors)