Skip to content

Commit

Permalink
Define compiler string formatting utilities
Browse files Browse the repository at this point in the history
- TR::[v]printfLen(): return the length of the formatted string without
  writing the string into any buffer

- TR::[v]snprintfTrunc(): format into a fixed-size buffer, possibly
  truncating

- TR::[v]snprintfNoTrunc(): format into a fixed-size buffer with an
  assertion that the result is not truncated

- TR::StringBuf: a reusable growable Region-allocated buffer into which
  formatted text can be appended using [v]appendf()

These obviate the MSVC conditional snprintf #define at use sites, and
they deal with the portability concerns of that #define correctly and in
a centralized place. Additionally, StringBuf makes it simple to allocate
enough memory to do formatting without truncation.
  • Loading branch information
jdmpapin committed Aug 6, 2021
1 parent c1cfe33 commit cc0a9b8
Show file tree
Hide file tree
Showing 7 changed files with 316 additions and 25 deletions.
11 changes: 4 additions & 7 deletions compiler/control/CompileMethod.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2000, 2020 IBM Corp. and others
* Copyright (c) 2000, 2021 IBM Corp. and others
*
* This program and the accompanying materials are made available under
* the terms of the Eclipse Public License 2.0 which accompanies this
Expand Down Expand Up @@ -54,16 +54,13 @@
#include "ilgen/IlGenRequest.hpp"
#include "ilgen/IlGeneratorMethodDetails.hpp"
#include "infra/Assert.hpp"
#include "infra/String.hpp"
#include "ras/Debug.hpp"
#include "env/SystemSegmentProvider.hpp"
#include "env/DebugSegmentProvider.hpp"
#include "omrformatconsts.h"
#include "runtime/CodeCacheManager.hpp"

#if defined (_MSC_VER) && _MSC_VER < 1900
#define snprintf _snprintf
#endif

static void
writePerfToolEntry(void *start, uint32_t size, const char *name)
{
Expand All @@ -82,8 +79,8 @@ writePerfToolEntry(void *start, uint32_t size, const char *name)
static const int maxPerfFilenameSize = 15 + sizeof(jvmPid)* 3; // "/tmp/perf-%ld.map"
char perfFilename[maxPerfFilenameSize] = { 0 };

int numCharsWritten = snprintf(perfFilename, maxPerfFilenameSize, "/tmp/perf-%" OMR_PRId64 ".map", static_cast<int64_t>(jvmPid));
if (numCharsWritten > 0 && numCharsWritten < maxPerfFilenameSize)
bool truncated = TR::snprintfTrunc(perfFilename, maxPerfFilenameSize, "/tmp/perf-%" OMR_PRId64 ".map", static_cast<int64_t>(jvmPid));
if (!truncated)
{
perfFile = fopen(perfFilename, "a");
}
Expand Down
11 changes: 4 additions & 7 deletions compiler/ilgen/OMRMethodBuilder.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2016, 2020 IBM Corp. and others
* Copyright (c) 2016, 2021 IBM Corp. and others
*
* This program and the accompanying materials are made available under
* the terms of the Eclipse Public License 2.0 which accompanies this
Expand Down Expand Up @@ -44,6 +44,7 @@
#include "infra/Cfg.hpp"
#include "infra/STLUtils.hpp"
#include "infra/List.hpp"
#include "infra/String.hpp"
#include "ilgen/IlGeneratorMethodDetails_inlines.hpp"
#include "ilgen/IlInjector.hpp"
#include "ilgen/IlBuilder.hpp"
Expand All @@ -60,10 +61,6 @@
#define TraceEnabled (comp()->getOption(TR_TraceILGen))
#define TraceIL(m, ...) {if (TraceEnabled) {traceMsg(comp(), m, ##__VA_ARGS__);}}

#if defined (_MSC_VER) && _MSC_VER < 1900
#define snprintf _snprintf
#endif

// MethodBuilder is an IlBuilder object representing an entire method /
// function, so it conceptually has an entry point (though multiple entry
// method builders are entirely possible). Typically there is a single
Expand Down Expand Up @@ -492,13 +489,13 @@ OMR::MethodBuilder::isSymbolAnArray(const char *name)
void
OMR::MethodBuilder::DefineLine(const char *line)
{
snprintf(_definingLine, MAX_LINE_NUM_LEN * sizeof(char), "%s", line);
TR::snprintfNoTrunc(_definingLine, MAX_LINE_NUM_LEN * sizeof(char), "%s", line);
}

void
OMR::MethodBuilder::DefineLine(int line)
{
snprintf(_definingLine, MAX_LINE_NUM_LEN * sizeof(char), "%d", line);
TR::snprintfNoTrunc(_definingLine, MAX_LINE_NUM_LEN * sizeof(char), "%d", line);
}

void
Expand Down
3 changes: 2 additions & 1 deletion compiler/infra/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
###############################################################################
# Copyright (c) 2017, 2018 IBM Corp. and others
# Copyright (c) 2017, 2021 IBM Corp. and others
#
# This program and the accompanying materials are made available under
# the terms of the Eclipse Public License 2.0 which accompanies this
Expand Down Expand Up @@ -32,6 +32,7 @@ compiler_library(infra
${CMAKE_CURRENT_LIST_DIR}/OMRMonitorTable.cpp
${CMAKE_CURRENT_LIST_DIR}/Random.cpp
${CMAKE_CURRENT_LIST_DIR}/SimpleRegex.cpp
${CMAKE_CURRENT_LIST_DIR}/String.cpp
${CMAKE_CURRENT_LIST_DIR}/STLUtils.cpp
${CMAKE_CURRENT_LIST_DIR}/Timer.cpp
${CMAKE_CURRENT_LIST_DIR}/TreeServices.cpp
Expand Down
190 changes: 190 additions & 0 deletions compiler/infra/String.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
/*******************************************************************************
* Copyright (c) 2021, 2021 IBM Corp. and others
*
* This program and the accompanying materials are made available under
* the terms of the Eclipse Public License 2.0 which accompanies this
* distribution and is available at http://eclipse.org/legal/epl-2.0
* or the Apache License, Version 2.0 which accompanies this distribution
* and is available at https://www.apache.org/licenses/LICENSE-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License, v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception [1] and GNU General Public
* License, version 2 with the OpenJDK Assembly Exception [2].
*
* [1] https://www.gnu.org/software/classpath/license.html
* [2] http://openjdk.java.net/legal/assembly-exception.html
*
* SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 OR LicenseRef-GPL-2.0 WITH Assembly-exception
*******************************************************************************/

#include "infra/String.hpp"
#include "infra/Assert.hpp"
#include "env/FrontEnd.hpp" // defines va_copy for old MSVC and z/OS
#include <limits.h>

#if defined (_MSC_VER) && (_MSC_VER < 1900)
#define stdlib_vsnprintf _vsnprintf
#else
#define stdlib_vsnprintf vsnprintf
#endif

namespace TR {

// Returns the length of the formatted string without writing it to memory.
int vprintfLen(const char *fmt, va_list args)
{
// This works with both standard vsnprintf() and _vsnprintf().
return stdlib_vsnprintf(NULL, 0, fmt, args);
}

// Returns the length of the formatted string without writing it to memory.
int printfLen(const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
int len = TR::vprintfLen(fmt, args);
va_end(args);
return len;
}

// Returns true if the formatted string was truncated. The length of the
// formatted string (excluding the NUL terminator) is stored into len. That
// length is (size - 1) if the output was truncated, because when using
// _vsnprintf() the would-be length is unknown.
bool vsnprintfTrunc(char *buf, size_t size, int *len, const char *fmt, va_list args)
{
// This assertion guarantees that (size - 1) will not overflow in the
// truncation case below, and that there is actually a location in which to
// store the terminating NUL. The only reason to pass size=0 would be to
// compute the formatted length, which should be done using
// TR::[v]printfLen() instead.
TR_ASSERT_FATAL(size > 0, "vsnprintfTrunc: no buffer space provided");

// This assertion guarantees that it's safe to truncate (size - 1) to int in
// the string truncation case below.
TR_ASSERT_FATAL(size - 1 <= (size_t)INT_MAX, "vsnprintfTrunc: buffer too large");

int n = stdlib_vsnprintf(buf, size, fmt, args);

// The formatted length does not include the NUL, so if (n >= size), the
// result has been truncated. If we are using _vsnprintf(), it's possible to
// get (n == size), which indicates that the entire formatted string has
// been stored *without* a trailing NUL, but not (n > size). In the case
// where standard vsnprintf() produces (n > size), _vsnprintf() returns a
// negative value instead. So a negative result also indicates truncation.
bool truncated = n < 0 || n >= size;
if (truncated)
{
buf[size - 1] = '\0'; // Ensure NUL-termination in the _vsnprintf() case.
*len = (int)(size - 1);
}
else
{
*len = n;
}

return truncated;
}

// See vsnprintfTrunc() above.
bool snprintfTrunc(char *buf, size_t size, int *len, const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
bool truncated = TR::vsnprintfTrunc(buf, size, len, fmt, args);
va_end(args);
return truncated;
}

// Variant of vsnprintfTrunc() without the len pointer, for cases where the
// caller is uninterested in the length.
bool vsnprintfTrunc(char *buf, size_t size, const char *fmt, va_list args)
{
int dummy;
return vsnprintfTrunc(buf, size, &dummy, fmt, args);
}

// Variant of snprintfTrunc() without the len pointer, for cases where the
// caller is uninterested in the length.
bool snprintfTrunc(char *buf, size_t size, const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
bool truncated = TR::vsnprintfTrunc(buf, size, fmt, args);
va_end(args);
return truncated;
}

// Asserts that the formatted string was not truncated and returns its length
// (excluding the NUL terminator).
int vsnprintfNoTrunc(char *buf, size_t size, const char *fmt, va_list args)
{
int len;
bool truncated = TR::vsnprintfTrunc(buf, size, &len, fmt, args);
TR_ASSERT_FATAL(!truncated, "vsnprintfNoTrunc: truncation occurred");
return len;
}

// Asserts that the formatted string was not truncated and returns its length
// (excluding the NUL terminator).
int snprintfNoTrunc(char *buf, size_t size, const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
int len = TR::vsnprintfNoTrunc(buf, size, fmt, args);
va_end(args);
return len;
}

void TR::StringBuf::vappendf(const char *fmt, va_list args)
{
va_list argsCopy;

va_copy(argsCopy, args);
int appendLen = TR::vprintfLen(fmt, argsCopy);
va_copy_end(argsCopy);

TR_ASSERT_FATAL(appendLen >= 0, "error in format string");

size_t newLen = _len + appendLen;
ensureCapacity(newLen);

TR_ASSERT_FATAL(appendLen + 1 <= _cap - _len, "insufficient buffer capacity");
int realAppendLen = stdlib_vsnprintf(&_text[_len], appendLen + 1, fmt, args);
TR_ASSERT_FATAL(realAppendLen == appendLen, "incorrect predicted snprintf length");
TR_ASSERT_FATAL(_text[newLen] == '\0', "missing NUL terminator");

_len = newLen;
}

void TR::StringBuf::appendf(const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
vappendf(fmt, args);
va_end(args);
}

void TR::StringBuf::ensureCapacity(size_t newLen)
{
// Note that the capacity must always be strictly greater than the length so
// that there is room for the NUL terminator.
if (newLen < _cap)
return; // nothing to do

size_t newCap = newLen + 1; // + 1 for NUL terminator

// Grow the buffer geometrically each time it is reallocated to ensure that
// appends are amortized O(1) per character
if (newCap < 2 * _cap)
newCap = 2 * _cap;

char *newText = (char*)_region.allocate(newCap);
memcpy(newText, _text, _len + 1); // + 1 for NUL terminator
_text = newText;
_cap = newCap;
}

}
114 changes: 114 additions & 0 deletions compiler/infra/String.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/*******************************************************************************
* Copyright (c) 2021, 2021 IBM Corp. and others
*
* This program and the accompanying materials are made available under
* the terms of the Eclipse Public License 2.0 which accompanies this
* distribution and is available at http://eclipse.org/legal/epl-2.0
* or the Apache License, Version 2.0 which accompanies this distribution
* and is available at https://www.apache.org/licenses/LICENSE-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License, v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception [1] and GNU General Public
* License, version 2 with the OpenJDK Assembly Exception [2].
*
* [1] https://www.gnu.org/software/classpath/license.html
* [2] http://openjdk.java.net/legal/assembly-exception.html
*
* SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 OR LicenseRef-GPL-2.0 WITH Assembly-exception
*******************************************************************************/

#ifndef TR_STRING_INCL
#define TR_STRING_INCL

#include <stdarg.h>
#include <stddef.h>
#include "env/defines.h"
#include "env/TRMemory.hpp"

#if HOST_COMPILER == COMPILER_GCC || HOST_COMPILER == COMPILER_CLANG
#define TR_PRINTF_FORMAT_ATTR(fmtIndex, argsIndex) \
__attribute__((format(printf, (fmtIndex), (argsIndex))))
#else
#define TR_PRINTF_FORMAT_ATTR(fmtIndex, argsIndex)
#endif

namespace TR {

int vprintfLen(const char *fmt, va_list args);

int printfLen(const char *fmt, ...) TR_PRINTF_FORMAT_ATTR(1, 2);

bool vsnprintfTrunc(char *buf, size_t size, int *len, const char *fmt, va_list args);

bool snprintfTrunc(char *buf, size_t size, int *len, const char *fmt, ...)
TR_PRINTF_FORMAT_ATTR(4, 5);

bool vsnprintfTrunc(char *buf, size_t size, const char *fmt, va_list args);

bool snprintfTrunc(char *buf, size_t size, const char *fmt, ...)
TR_PRINTF_FORMAT_ATTR(3, 4);

int vsnprintfNoTrunc(char *buf, size_t size, const char *fmt, va_list args);

int snprintfNoTrunc(char *buf, size_t size, const char *fmt, ...)
TR_PRINTF_FORMAT_ATTR(3, 4);

class StringBuf
{
TR::Region &_region;
size_t _cap;
size_t _len;
char *_text;

// can't use a delegating constructor
void initEmptyBuffer()
{
TR_ASSERT_FATAL(_cap > 0, "StringBuf: no buffer space");
TR_ASSERT_FATAL(_text != NULL, "StringBuf: buffer is null");
_text[0] = '\0';
}

public:

// region, initialBuffer must both outlive this StringBuffer
StringBuf(TR::Region &region, char *initialBuffer, size_t capacity)
: _region(region), _cap(capacity), _len(0), _text(initialBuffer)
{
initEmptyBuffer();
}

// region must outlive this StringBuffer
StringBuf(TR::Region &region, size_t capacity = 32)
: _region(region), _cap(capacity), _len(0), _text((char*)region.allocate(capacity))
{
initEmptyBuffer();
}

size_t len() const { return _len; }
const char *text() const { return _text; }

void clear()
{
_len = 0;
_text[0] = '\0';
}

void vappendf(const char *fmt, va_list args);

void appendf(const char *fmt, ...) TR_PRINTF_FORMAT_ATTR(2, 3);

private:

// Non-copyable. These are undefined and will cause a link error if anything
// attempts to use them accidentally.
StringBuf(const StringBuf &);
StringBuf &operator=(const StringBuf &);

void ensureCapacity(size_t newLen);
};

}

#endif
Loading

0 comments on commit cc0a9b8

Please sign in to comment.