/*
 * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package com.oracle.svm.core.log;

import java.nio.charset.StandardCharsets;

import org.graalvm.compiler.core.common.calc.UnsignedMath;
import org.graalvm.compiler.word.Word;
import org.graalvm.nativeimage.ImageSingletons;
import org.graalvm.nativeimage.LogHandler;
import org.graalvm.nativeimage.StackValue;
import org.graalvm.nativeimage.c.type.CCharPointer;
import org.graalvm.word.Pointer;
import org.graalvm.word.PointerBase;
import org.graalvm.word.UnsignedWord;
import org.graalvm.word.WordBase;
import org.graalvm.word.WordFactory;

import com.oracle.svm.core.SubstrateUtil;
import com.oracle.svm.core.annotate.RestrictHeapAccess;
import com.oracle.svm.core.c.NonmovableArrays;
import com.oracle.svm.core.heap.Heap;
import com.oracle.svm.core.jdk.JDKUtils;
import com.oracle.svm.core.util.VMError;

public class RealLog extends Log {

    private boolean autoflush = false;
    private int indent = 0;

    protected RealLog() {
        super();
    }

    @Override
    public boolean isEnabled() {
        return true;
    }

    @Override
    public Log string(String value) {
        if (value != null) {
            rawString(value);
        } else {
            rawString("null");
        }
        return this;
    }

    @Override
    public Log string(String str, int fill, int align) {

        int spaces = fill - str.length();

        if (align == RIGHT_ALIGN) {
            spaces(spaces);
        }

        string(str);

        if (align == LEFT_ALIGN) {
            spaces(spaces);
        }

        return this;
    }

    @Override
    public Log string(char[] value) {
        if (value != null) {
            rawString(value);
        } else {
            rawString("null");
        }
        return this;
    }

    @Override
    public Log string(byte[] value, int offset, int length) {
        if (value == null) {
            rawString("null");
        } else if ((offset < 0) || (offset > value.length) || (length < 0) || ((offset + length) > value.length) || ((offset + length) < 0)) {
            rawString("OUT OF BOUNDS");
        } else if (Heap.getHeap().isInImageHeap(value)) {
            rawBytes(NonmovableArrays.addressOf(NonmovableArrays.fromImageHeap(value), offset), WordFactory.unsigned(length));
        } else {
            rawBytes(value, offset, length);
        }
        return this;
    }

    
Write a raw java array by copying it first to a stack allocated temporary buffer. Caller must ensure that the offset and length are within bounds.
/** * Write a raw java array by copying it first to a stack allocated temporary buffer. Caller must * ensure that the offset and length are within bounds. */
private void rawBytes(Object value, int offset, int length) { /* * Stack allocation needs an allocation size that is a compile time constant, so we split * the byte array up in multiple chunks and write them separately. */ final int chunkSize = 256; final CCharPointer bytes = StackValue.get(chunkSize); int chunkOffset = offset; int inputLength = length; while (inputLength > 0) { int chunkLength = Math.min(inputLength, chunkSize); for (int i = 0; i < chunkLength; i++) { int index = chunkOffset + i; byte b; if (value instanceof String) { b = (byte) charAt((String) value, index); } else if (value instanceof char[]) { b = (byte) ((char[]) value)[index]; } else { b = ((byte[]) value)[index]; } bytes.write(i, b); } rawBytes(bytes, WordFactory.unsigned(chunkLength)); chunkOffset += chunkLength; inputLength -= chunkLength; } } @RestrictHeapAccess(access = RestrictHeapAccess.Access.UNRESTRICTED, overridesCallers = true, reason = "String.charAt can allocate exception, but we know that our access is in bounds") private static char charAt(String s, int index) { return s.charAt(index); } @Override public Log string(CCharPointer value) { if (value.notEqual(WordFactory.nullPointer())) { rawBytes(value, SubstrateUtil.strlen(value)); } else { rawString("null"); } return this; } @Override public Log character(char value) { CCharPointer bytes = StackValue.get(CCharPointer.class); bytes.write((byte) value); rawBytes(bytes, WordFactory.unsigned(1)); return this; } private static final byte[] NEWLINE = System.lineSeparator().getBytes(StandardCharsets.US_ASCII); @Override public Log newline() { string(NEWLINE); if (autoflush) { flush(); } spaces(indent); return this; }
Prints the value according according to the given format specification. The digits '0' to '9' followed by the letters 'a' to 'z' are used to represent the digits.
Params:
  • value – The value to print.
  • radix – The base of the value, between 2 and 36.
  • signed – true if the value should be treated as a signed value (and the digits are preceded by '-' for negative values).
/** * Prints the value according according to the given format specification. The digits '0' to '9' * followed by the letters 'a' to 'z' are used to represent the digits. * * @param value The value to print. * @param radix The base of the value, between 2 and 36. * @param signed true if the value should be treated as a signed value (and the digits are * preceded by '-' for negative values). */
@Override public Log number(long value, int radix, boolean signed) { return number(value, radix, signed, 0, NO_ALIGN); } private Log number(long value, int radix, boolean signed, int fill, int align) { if (radix < 2 || radix > 36) { /* Ignore bogus parameter value. */ return this; } /* Enough space for 64 digits in binary format, and the '-' for a negative value. */ final int chunkSize = Long.SIZE + 1; CCharPointer bytes = StackValue.get(chunkSize, CCharPointer.class); int charPos = chunkSize; boolean negative = signed && value < 0; long curValue; if (negative) { /* * We do not have to worry about the overflow of Long.MIN_VALUE here, since we treat * curValue as an unsigned value. */ curValue = -value; } else { curValue = value; } while (UnsignedMath.aboveOrEqual(curValue, radix)) { charPos--; bytes.write(charPos, digit(Long.remainderUnsigned(curValue, radix))); curValue = Long.divideUnsigned(curValue, radix); } charPos--; bytes.write(charPos, digit(curValue)); if (negative) { charPos--; bytes.write(charPos, (byte) '-'); } int length = chunkSize - charPos; if (align == RIGHT_ALIGN) { int spaces = fill - length; spaces(spaces); } rawBytes(bytes.addressOf(charPos), WordFactory.unsigned(length)); if (align == LEFT_ALIGN) { int spaces = fill - length; spaces(spaces); } return this; } @Override public Log signed(WordBase value) { return number(value.rawValue(), 10, true); } @Override public Log signed(int value) { return number(value, 10, true); } @Override public Log signed(long value) { return number(value, 10, true); } @Override public Log unsigned(WordBase value) { return number(value.rawValue(), 10, false); } @Override public Log unsigned(WordBase value, int fill, int align) { return number(value.rawValue(), 10, false, fill, align); } @Override public Log unsigned(int value) { // unsigned expansion from int to long return number(value & 0xffffffffL, 10, false); } @Override public Log unsigned(long value) { return number(value, 10, false); } @Override public Log unsigned(long value, int fill, int align) { return number(value, 10, false, fill, align); }
Fast printing of a rational numbers without allocation memory.

Note: this method will not perform rounding.

Note: this method will print all trailing zeros, i.e., rational(1, 2, 4) prints 0.5000

Params:
  • numerator – Numerator in division
  • denominator – or divisor
  • decimals – number of decimals after the . to be printed. Note that no rounding is performed and trailing zeros are printed.
/** * Fast printing of a rational numbers without allocation memory. * * <p> * Note: this method will not perform rounding. * </p> * <p> * Note: this method will print all trailing zeros, i.e., {@code rational(1, 2, 4)} prints * {@code 0.5000} * </p> * * @param numerator Numerator in division * @param denominator or divisor * @param decimals number of decimals after the . to be printed. Note that no rounding is * performed and trailing zeros are printed. */
@Override public Log rational(long numerator, long denominator, long decimals) { if (denominator == 0) { throw VMError.shouldNotReachHere("Division by zero"); } if (decimals < 0) { throw VMError.shouldNotReachHere("Number of decimals smaller than 0"); } long value = numerator / denominator; unsigned(value); if (decimals > 0) { character('.'); long positiveNumerator = Math.abs(numerator); long positiveDenominator = Math.abs(denominator); long remainder = positiveNumerator % positiveDenominator; for (int i = 0; i < decimals; i++) { remainder *= 10; unsigned(remainder / positiveDenominator); remainder = remainder % positiveDenominator; } } return this; } @Override public Log hex(WordBase value) { return string("0x").number(value.rawValue(), 16, false); } @Override public Log hex(int value) { return string("0x").number(value & 0xffffffffL, 16, false); } @Override public Log hex(long value) { return string("0x").number(value, 16, false); } private static final byte[] trueString = Boolean.TRUE.toString().getBytes(); private static final byte[] falseString = Boolean.FALSE.toString().getBytes(); @Override public Log bool(boolean value) { return string(value ? trueString : falseString); } @Override public Log object(Object value) { return (value == null ? string("null") : string(value.getClass().getName()).string("@").hex(Word.objectToUntrackedPointer(value))); } private static final char spaceChar = ' '; @Override public Log spaces(int value) { for (int i = 0; i < value; i += 1) { character(spaceChar); } return this; } @Override public Log flush() { ImageSingletons.lookup(LogHandler.class).flush(); return this; } @Override public Log autoflush(boolean onOrOff) { autoflush = onOrOff; return this; } @Override public Log redent(boolean addOrRemove) { int delta = addOrRemove ? 2 : -2; indent = Math.max(0, indent + delta); return this; } private static byte digit(long d) { return (byte) (d + (d < 10 ? '0' : 'a' - 10)); } protected Log rawBytes(CCharPointer bytes, UnsignedWord length) { ImageSingletons.lookup(LogHandler.class).log(bytes, length); return this; } private void rawString(String value) { rawBytes(value, 0, value.length()); } private void rawString(char[] value) { rawBytes(value, 0, value.length); } @Override public Log zhex(long value) { int zeros = Long.numberOfLeadingZeros(value); int hexZeros = zeros / 4; for (int i = 0; i < hexZeros; i += 1) { character('0'); } if (value != 0) { number(value, 16, false); } return this; } private Log zhex(int value, int wordSizeInBytes) { int zeros = Integer.numberOfLeadingZeros(value) - 32 + (wordSizeInBytes * 8); int hexZeros = zeros / 4; for (int i = 0; i < hexZeros; i += 1) { character('0'); } if (value != 0) { number(value & 0xffffffffL, 16, false); } return this; } @Override public Log zhex(int value) { return zhex(value, 4); } @Override public Log zhex(short value) { int intValue = value; return zhex(intValue & 0xffff, 2); } @Override public Log zhex(byte value) { int intValue = value; return zhex(intValue & 0xff, 1); } @Override public Log hexdump(PointerBase from, int wordSize, int numWords) { Pointer base = WordFactory.pointer(from.rawValue()); int sanitizedWordsize = wordSize > 0 ? Integer.highestOneBit(Math.min(wordSize, 8)) : 2; for (int offset = 0; offset < sanitizedWordsize * numWords; offset += sanitizedWordsize) { if (offset % 16 == 0) { zhex(base.add(offset).rawValue()); string(":"); } string(" "); switch (sanitizedWordsize) { case 1: zhex(base.readByte(offset)); break; case 2: zhex(base.readShort(offset)); break; case 4: zhex(base.readInt(offset)); break; case 8: zhex(base.readLong(offset)); break; } if ((offset + sanitizedWordsize) % 16 == 0) { newline(); } } return this; } @Override public Log exception(Throwable t, int maxFrames) { if (t == null) { return object(t); } /* * We do not want to call getMessage(), since it can be overridden by subclasses of * Throwable. So we access the raw detailMessage directly from the field in Throwable. That * is better than printing nothing. */ String detailMessage = JDKUtils.getRawMessage(t); StackTraceElement[] stackTrace = JDKUtils.getRawStackTrace(t); string(t.getClass().getName()).string(": ").string(detailMessage); if (stackTrace != null) { int i; for (i = 0; i < stackTrace.length && i < maxFrames; i++) { StackTraceElement element = stackTrace[i]; if (element != null) { newline(); string(" at ").string(element.getClassName()).string(".").string(element.getMethodName()); string("(").string(element.getFileName()).string(":").signed(element.getLineNumber()).string(")"); } } int remaining = stackTrace.length - i; if (remaining > 0) { newline().string(" ... ").unsigned(remaining).string(" more"); } } return newline(); } }