Skip to content

Commit

Permalink
make generic "Out of memory!" error more specific
Browse files Browse the repository at this point in the history
The problem: When perl runs out of memory, it outputs a generic "Out of
memory!" error and exits. This makes it hard to track down what's
happening in a complex system, especially since the message does not
even mention perl.

This patch contains two main changes:

1. vec() in lvalue context can throw fake "Out of memory!" errors when
   it discovers that the index being assigned to is too big. Unlike real
   allocation errors, these are trappable with try {}/eval {}.

   This message has been changed to "Out of memory during vec in lvalue
   context" (and since it comes from a Perl_croak() call, it will
   generally have a script name and line number attached).

2. All other places in the source code that can emit "Out of memory!"
   errors have been changed to attach a location identifier to the
   message. For example: "Out of memory in perl:util:safesysmalloc"

   This way the error message at least mentions "perl".

Fixes #21672.
  • Loading branch information
mauke committed Dec 13, 2023
1 parent 4430315 commit 8fbf04a
Show file tree
Hide file tree
Showing 11 changed files with 75 additions and 32 deletions.
4 changes: 2 additions & 2 deletions doop.c
Original file line number Diff line number Diff line change
Expand Up @@ -908,7 +908,7 @@ Perl_do_vecset(pTHX_ SV *sv)
assert(!(errflags & ~(LVf_NEG_OFF|LVf_OUT_OF_RANGE)));
if (errflags & LVf_NEG_OFF)
Perl_croak_nocontext("Negative offset to vec in lvalue context");
Perl_croak_nocontext("Out of memory!");
Perl_croak_nocontext("Out of memory during vec in lvalue context");
}

if (!targ)
Expand Down Expand Up @@ -938,7 +938,7 @@ Perl_do_vecset(pTHX_ SV *sv)
else if (size > 8) {
int n = size/8;
if (offset > Size_t_MAX / n - 1) /* would overflow */
Perl_croak_nocontext("Out of memory!");
Perl_croak_nocontext("Out of memory during vec in lvalue context");
offset *= n;
}

Expand Down
3 changes: 3 additions & 0 deletions embed.fnc
Original file line number Diff line number Diff line change
Expand Up @@ -855,6 +855,9 @@ Tfprv |void |croak_caller |NULLOK const char *pat \
|...
CTrs |void |croak_memory_wrap
Tpr |void |croak_no_mem
Tpr |void |croak_no_mem_ext \
|NN const char *context \
|STRLEN len
ATdpr |void |croak_no_modify
TXpr |void |croak_popstack
Adpr |void |croak_sv |NN SV *baseex
Expand Down
1 change: 1 addition & 0 deletions embed.h
Original file line number Diff line number Diff line change
Expand Up @@ -921,6 +921,7 @@
# define create_eval_scope(a,b,c) Perl_create_eval_scope(aTHX_ a,b,c)
# define croak_caller Perl_croak_caller
# define croak_no_mem Perl_croak_no_mem
# define croak_no_mem_ext Perl_croak_no_mem_ext
# define croak_popstack Perl_croak_popstack
# define custom_op_get_field(a,b) Perl_custom_op_get_field(aTHX_ a,b)
# define cv_clone_into(a,b) Perl_cv_clone_into(aTHX_ a,b)
Expand Down
6 changes: 3 additions & 3 deletions op.c
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ S_link_freed_op(pTHX_ OPSLAB *slab, OP *o) {
slab->opslab_freed = (OP**)PerlMemShared_calloc((slab->opslab_freed_size), sizeof(OP*));

if (!slab->opslab_freed)
croak_no_mem();
croak_no_mem_ext(STR_WITH_LEN("op:link_freed_op"));
}
else if (index >= slab->opslab_freed_size) {
/* It's probably not worth doing exponential expansion here, the number of op sizes
Expand All @@ -295,7 +295,7 @@ S_link_freed_op(pTHX_ OPSLAB *slab, OP *o) {
OP **p = (OP **)PerlMemShared_realloc(slab->opslab_freed, newsize * sizeof(OP*));

if (!p)
croak_no_mem();
croak_no_mem_ext(STR_WITH_LEN("op:link_freed_op"));

Zero(p+slab->opslab_freed_size, newsize - slab->opslab_freed_size, OP *);

Expand Down Expand Up @@ -15832,7 +15832,7 @@ Perl_rcpv_new(pTHX_ const char *pv, STRLEN len, U32 flags) {

rcpv = (RCPV *)PerlMemShared_malloc(sizeof(struct rcpv) + len);
if (!rcpv)
croak_no_mem();
croak_no_mem_ext(STR_WITH_LEN("op:rcpv_new"));

rcpv->len = len; /* store length including null,
RCPV_LEN() subtracts 1 to account for this */
Expand Down
3 changes: 2 additions & 1 deletion os2/os2ish.h
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,8 @@ extern int rc;
# define pthread_setspecific(k,v) (*(k)=(v),0)
# define pthread_key_create(keyp,flag) \
( DosAllocThreadLocalMemory(1,(unsigned long**)keyp) \
? Perl_croak_nocontext("Out of memory!"), 1 \
/* diag_listed_as: Out of memory in perl:%s */ \
? Perl_croak_nocontext("Out of memory in perl:os2:pthread_key_create"), 1 \
: 0 \
)
#endif /* USE_SLOW_THREAD_SPECIFIC */
Expand Down
2 changes: 1 addition & 1 deletion perlio.c
Original file line number Diff line number Diff line change
Expand Up @@ -2697,7 +2697,7 @@ S_more_refcounted_fds(pTHX_ const int new_fd)

if (!new_array) {
MUTEX_UNLOCK(&PL_perlio_mutex);
croak_no_mem();
croak_no_mem_ext(STR_WITH_LEN("perlio:more_refcounted_fds"));
}

PL_perlio_fd_refcnt_size = new_max;
Expand Down
16 changes: 16 additions & 0 deletions pod/perldiag.pod
Original file line number Diff line number Diff line change
Expand Up @@ -4892,12 +4892,28 @@ where the failed request happened.
is most likely to be caused by a typo in the Perl program. e.g.,
C<$arr[time]> instead of C<$arr[$time]>.

=item Out of memory during vec in lvalue context

(F) An attempt was made to extend a string beyond the largest possible memory
allocation by assigning to C<vec()> called with a large second argument.

=item Out of memory for yacc stack

(F) The yacc parser wanted to grow its stack so it could continue
parsing, but realloc() wouldn't give it more memory, virtual or
otherwise.

=item Out of memory in perl:%s

(X) A low-level memory allocation routine failed, indicating there was
insufficient remaining (virtual) memory to satisfy the request. Perl has no
option but to exit immediately.

At least in Unix you may be able to get past this by increasing your process
datasize limits: in csh/tcsh use C<limit> and C<limit datasize n> (where C<n>
is the number of kilobytes) to check the current limits and change them, and in
ksh/bash/zsh use C<ulimit -a> and C<ulimit -d n>, respectively.

=item '.' outside of string in pack

(F) The argument to a '.' in your template tried to move the working
Expand Down
7 changes: 7 additions & 0 deletions proto.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 10 additions & 10 deletions t/op/vec.t
Original file line number Diff line number Diff line change
Expand Up @@ -152,12 +152,12 @@ like($@, qr/^Modification of a read-only value attempted at /,
# partially duplicates some tests above, but those cases are repeated
# here for completeness.
#
# Note that all the 'Out of memory!' errors trapped eval {} are 'fake'
# croaks generated by pp_vec() etc when they have detected something
# that would have otherwise overflowed. The real 'Out of memory!'
# error thrown by safesysrealloc() etc is not trappable. If it were
# accidentally triggered in this test script, the script would exit at
# that point.
# Note that all the 'Out of memory during vec in lvalue context' errors
# trapped by eval {} are 'fake' croaks generated by pp_vec() etc when they
# have detected something that would have otherwise overflowed. The real
# 'Out of memory!' error thrown by safesysrealloc() etc is not trappable.
# If it were accidentally triggered in this test script, the script would
# exit at that point.


my $s = "abcdefghijklmnopqrstuvwxyz";
Expand All @@ -168,7 +168,7 @@ like($@, qr/^Modification of a read-only value attempted at /,
$x = vec($s, ~0, 8);
is($x, 0, "RT 130915: UV_MAX rval");
eval { vec($s, ~0, 8) = 1 };
like($@, qr/^Out of memory!/, "RT 130915: UV_MAX lval");
like($@, qr/^Out of memory during vec in lvalue context/, "RT 130915: UV_MAX lval");

# offset is negative

Expand All @@ -190,7 +190,7 @@ like($@, qr/^Modification of a read-only value attempted at /,
$x = vec($s, $sm2, 8);
is($x, 0, "RT 130915: size_max*2 rval");
eval { vec($s, $sm2, 8) = 1 };
like($@, qr/^Out of memory!/, "RT 130915: size_max*2 lval");
like($@, qr/^Out of memory during vec in lvalue context/, "RT 130915: size_max*2 lval");
}

# (offset * num-bytes) could overflow
Expand All @@ -204,7 +204,7 @@ like($@, qr/^Modification of a read-only value attempted at /,
$x = vec($s, $offset, $bytes*8);
is($x, 0, "large offset: bytes=$bytes biglog2=$biglog2 i=$i: rval");
eval { vec($s, $offset, $bytes*8) = 1; };
like($@, qr/^Out of memory!/,
like($@, qr/^Out of memory during vec in lvalue context/,
"large offset: bytes=$bytes biglog2=$biglog2 i=$i: rval");
}
}
Expand Down Expand Up @@ -247,7 +247,7 @@ like($@, qr/^Modification of a read-only value attempted at /,
$v = RT131083(0, vec($s, $off, 8));
is($v, 0, "RT131083 rval ~0");
$v = eval { RT131083(1, vec($s, $off, 8)); };
like($@, qr/Out of memory!/, "RT131083 lval ~0");
like($@, qr/Out of memory during vec in lvalue context/, "RT131083 lval ~0");
}

{
Expand Down
29 changes: 22 additions & 7 deletions util.c
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ Perl_safesysmalloc(MEM_SIZE size)
if (PL_nomemok)
ptr = NULL;
else
croak_no_mem();
croak_no_mem_ext(STR_WITH_LEN("util:safesysmalloc"));
}
}
return ptr;
Expand Down Expand Up @@ -340,7 +340,7 @@ Perl_safesysrealloc(Malloc_t where,MEM_SIZE size)
if (PL_nomemok)
ptr = NULL;
else
croak_no_mem();
croak_no_mem_ext(STR_WITH_LEN("util:safesysrealloc"));
}
}
}
Expand Down Expand Up @@ -512,7 +512,7 @@ Perl_safesyscalloc(MEM_SIZE count, MEM_SIZE size)
#endif
if (PL_nomemok)
return NULL;
croak_no_mem();
croak_no_mem_ext(STR_WITH_LEN("util:safesyscalloc"));
}
}

Expand Down Expand Up @@ -1342,7 +1342,7 @@ Perl_savesharedpv(pTHX_ const char *pv)
pvlen = strlen(pv)+1;
newaddr = (char*)PerlMemShared_malloc(pvlen);
if (!newaddr) {
croak_no_mem();
croak_no_mem_ext(STR_WITH_LEN("util:savesharedpv"));
}
return (char*)memcpy(newaddr, pv, pvlen);
}
Expand All @@ -1365,7 +1365,7 @@ Perl_savesharedpvn(pTHX_ const char *const pv, const STRLEN len)
/* PERL_ARGS_ASSERT_SAVESHAREDPVN; */

if (!newaddr) {
croak_no_mem();
croak_no_mem_ext(STR_WITH_LEN("util:savesharedpvn"));
}
newaddr[len] = '\0';
return (char*)memcpy(newaddr, pv, len);
Expand Down Expand Up @@ -1970,20 +1970,35 @@ Perl_croak_no_modify(void)
This is typically called when malloc returns NULL.
*/
void
Perl_croak_no_mem(void)
Perl_croak_no_mem_ext(const char *context, STRLEN len)
{
dTHX;

PERL_ARGS_ASSERT_CROAK_NO_MEM_EXT;

int fd = PerlIO_fileno(Perl_error_log);
if (fd < 0)
SETERRNO(EBADF,RMS_IFI);
else {
/* Can't use PerlIO to write as it allocates memory */
PERL_UNUSED_RESULT(PerlLIO_write(fd, PL_no_mem, sizeof(PL_no_mem)-1));
static const char oomp[] = "Out of memory in perl:";
if (
PerlLIO_write(fd, oomp, sizeof oomp - 1) >= 0
&& PerlLIO_write(fd, context, len) >= 0
&& PerlLIO_write(fd, "\n", 1) >= 0
) {
/* nop */
}
}
my_exit(1);
}

void
Perl_croak_no_mem(void)
{
croak_no_mem_ext(STR_WITH_LEN("???"));
}

/* does not return, used only in POPSTACK */
void
Perl_croak_popstack(void)
Expand Down
16 changes: 8 additions & 8 deletions win32/win32.c
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ static void remove_dead_process(long child);
static int terminate_process(DWORD pid, HANDLE process_handle, int sig);
static int my_killpg(int pid, int sig);
static int my_kill(int pid, int sig);
static void out_of_memory(void);
static void out_of_memory(const char *context, STRLEN len);
static char* wstr_to_str(const wchar_t* wstr);
static long filetime_to_clock(PFILETIME ft);
static BOOL filetime_from_time(PFILETIME ft, time_t t);
Expand Down Expand Up @@ -2227,11 +2227,11 @@ win32_longpath(char *path)
}

static void
out_of_memory(void)
out_of_memory(const char *context, STRLEN len)
{

if (PL_curinterp)
croak_no_mem();
croak_no_mem_ext(context, len);
exit(1);
}

Expand All @@ -2255,7 +2255,7 @@ wstr_to_str(const wchar_t* wstr)
NULL, 0, NULL, NULL);
char* str = (char*)malloc(len);
if (!str)
out_of_memory();
out_of_memory(STR_WITH_LEN("win32:wstr_to_str"));
WideCharToMultiByte(CP_ACP, WC_NO_BEST_FIT_CHARS, wstr, wlen,
str, len, NULL, &used_default);
return str;
Expand Down Expand Up @@ -2288,7 +2288,7 @@ win32_ansipath(const WCHAR *widename)
NULL, 0, NULL, NULL);
name = (char*)win32_malloc(len);
if (!name)
out_of_memory();
out_of_memory(STR_WITH_LEN("win32:win32_ansipath"));

WideCharToMultiByte(CP_ACP, WC_NO_BEST_FIT_CHARS, widename, widelen,
name, len, NULL, &use_default);
Expand All @@ -2297,14 +2297,14 @@ win32_ansipath(const WCHAR *widename)
if (shortlen) {
WCHAR *shortname = (WCHAR*)win32_malloc(shortlen*sizeof(WCHAR));
if (!shortname)
out_of_memory();
out_of_memory(STR_WITH_LEN("win32:win32_ansipath"));
shortlen = GetShortPathNameW(widename, shortname, shortlen)+1;

len = WideCharToMultiByte(CP_ACP, WC_NO_BEST_FIT_CHARS, shortname, shortlen,
NULL, 0, NULL, NULL);
name = (char*)win32_realloc(name, len);
if (!name)
out_of_memory();
out_of_memory(STR_WITH_LEN("win32:win32_ansipath"));
WideCharToMultiByte(CP_ACP, WC_NO_BEST_FIT_CHARS, shortname, shortlen,
name, len, NULL, NULL);
win32_free(shortname);
Expand Down Expand Up @@ -2337,7 +2337,7 @@ win32_getenvironmentstrings(void)
lpWStr, wenvstrings_len, NULL, 0, NULL, NULL);
lpTmp = lpStr = (char *)win32_calloc(aenvstrings_len, sizeof(char));
if(!lpTmp)
out_of_memory();
out_of_memory(STR_WITH_LEN("win32:win32_getenvironmentstrings"));

/* Convert the string from UTF-16 encoding to ACP encoding */
WideCharToMultiByte(CP_ACP, WC_NO_BEST_FIT_CHARS, lpWStr, wenvstrings_len, lpStr,
Expand Down

0 comments on commit 8fbf04a

Please sign in to comment.