Fix format leaks memory - #1575
Conversation
- Switched to using the internal function `format*` instead of `format`. - Fixed the interface call. - Configured it to call `format*` when there is a single argument, and `fmt` otherwise.
hellerve
left a comment
There was a problem hiding this comment.
That does not fix the issue, it just shifts it from format to format*. See, for instance (format* "%d%d" 1). format is intentionally the single argument version, if we want to make it safer, we should make all implementors check the format string.
|
Thank you for the review. I overlooked the fact that I'll revert this and intercept each type's implementation instead, e.g.: (defmodule Int
(private format*)
(hidden format*)
(register format* (Fn [&String Int] String) "Int_format")
(defn format [s x]
(let [n (count-specifiers s)]
(if (= n 1)
(format* s x)
(let [msg (String.append "error in format string: expected exactly 1 specifier, found "
&(Int.str n))]
(do
(IO.errorln &msg)
msg)))))
(implements format Int.format))Small question: I intend to define a helper function called |
|
So the issue with this is that it’s a runtime check (which we try to avoid, which is why we do it at compile time in I can sketch out what I mean in case it’s unclear. |
|
I don't yet have a concrete idea of the implementation, so it would be helpful if you could provide an overview (or a sketch). My concerns are as follows:
(defmacro format [s x] (format s x))
(format "%d" 1) ;; Infinite loopOnce Specifically, the behavior of a |
|
i just played around with it and have a working patch. A hint: (hidden format-directives)
(doc format-directives "counts the format directives in `s`, ignoring escaped `%%`.")
(defndynamic format-directives [s]
; do cool stuff
)
(doc format-unchecked "formats `x` into `s` without checking the format string. Prefer `format`, which checks it at compile time.")
(defn format-unchecked [s x] (format s x))
(doc format "formats `x` into the format string `s`. `s` must contain exactly one format directive, and is required to be a string literal. Use `fmt` to format more than one value.")
(defmacro format [s x]
(let [n (format-directives s)]
(if (= n 1)
`(format-unchecked %s %x)
(macro-error (str "error in format string: expected exactly one directive, but found " n
" in \"" s "\". use fmt to format more than one value")))))my version also required load order to be changed such that the format file is loaded after |
This reverts commit e790461.
This reverts commit 7eb4a98.
It hadn't been reverted, so I committed it again.
- Commit based on the PR message. - Implemented logic to count the number of `%` characters. - The new file `FormatChecked.carp` is loaded after the `format` function has been implemented. Since it can be loaded at any point after implementation, I set it to load at the end of `Core.carp`. - Passing a string ending with `%` to `snprintf` results in undefined behavior, so this is treated as an error. Since handling of the characters following `%` is left entirely to `snprintf`, it might be advisable to validate the types (e.g., prohibiting `%n`).
|
Does anyone know how to catch |
Yeah it's not possible to catch. I think that's acceptable. |
|
OK. |
- Because can't catches `macro-error`.
|
I removed the test that checked for |
| (hidden format-directives) | ||
| (doc format-directives "counts the format directives in `s`, ignoring escaped `%%`.") | ||
| (defndynamic format-directives [s] | ||
| (let [idx (String.index-of s \%) | ||
| len (String.length s)] | ||
| (cond | ||
| (= idx -1) 0 ;; no % | ||
| (= idx (dec len)) -1 ;; found % at the end -> error | ||
| (= \% (String.char-at s (inc idx))) ;; next char = % -> this is an escaped % | ||
| (format-directives (String.suffix s (+ idx 2))) ;; other %? | ||
| (let [rest (format-directives (String.suffix s (inc idx)))] | ||
| (cond | ||
| (= rest -1) -1 ;; ex: "a%%%" -> error | ||
| (= rest 0) 1 ;; found % -> ok | ||
| 2))))) ;; too many % -> error |
There was a problem hiding this comment.
as it stands right now, this doesn’t actually count the directives. it will return 2 for anything over 1 directive.
format*instead offormat(name change only).formatinterface to useformat*(name change only).format*when there is a single argument, andfmtotherwise.formatleaks memory #1432