1 /*
   2  * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 package jdk.vm.ci.hotspot;
  24 
  25 import java.io.ByteArrayOutputStream;
  26 import java.io.DataOutputStream;
  27 import java.io.IOException;
  28 import java.security.MessageDigest;
  29 import java.security.NoSuchAlgorithmException;
  30 import java.util.Arrays;
  31 
  32 import jdk.vm.ci.common.JVMCIError;
  33 import jdk.vm.ci.meta.ResolvedJavaMethod;
  34 import jdk.vm.ci.meta.ResolvedJavaType;
  35 import jdk.vm.ci.meta.SpeculationLog.SpeculationReasonEncoding;
  36 
  37 /**
  38  * Implements a {@link SpeculationReasonEncoding} that {@linkplain #getByteArray() produces} a byte
  39  * array. Data is added via a {@link DataOutputStream}. When producing the final byte array, if the
  40  * total length of data exceeds the length of a SHA-1 digest, then a SHA-1 digest of the data is
  41  * produced instead.
  42  */
  43 final class HotSpotSpeculationEncoding extends ByteArrayOutputStream implements SpeculationReasonEncoding {
  44 
  45     private DataOutputStream dos = new DataOutputStream(this);
  46     private byte[] result;
  47 
  48     HotSpotSpeculationEncoding() {
  49         super(SHA1_LENGTH);
  50     }
  51 
  52     private void checkOpen() {
  53         if (result != null) {
  54             throw new IllegalArgumentException("Cannot update closed speculation encoding");
  55         }
  56     }
  57 
  58     private static final int NULL_METHOD = -1;
  59     private static final int NULL_TYPE = -2;
  60     private static final int NULL_STRING = -3;
  61 
  62     @Override
  63     public void addByte(int value) {
  64         checkOpen();
  65         try {
  66             dos.writeByte(value);
  67         } catch (IOException e) {
  68             throw new InternalError(e);
  69         }
  70     }
  71 
  72     @Override
  73     public void addShort(int value) {
  74         checkOpen();
  75         try {
  76             dos.writeShort(value);
  77         } catch (IOException e) {
  78             throw new InternalError(e);
  79         }
  80     }
  81 
  82     @Override
  83     public void addMethod(ResolvedJavaMethod method) {
  84         if (!addNull(method, NULL_METHOD)) {
  85             checkOpen();
  86             if (method instanceof HotSpotResolvedJavaMethodImpl) {
  87                 try {
  88                     dos.writeLong(((HotSpotResolvedJavaMethodImpl) method).getMetaspaceMethod());
  89                 } catch (IOException e) {
  90                     throw new InternalError(e);
  91                 }
  92             } else {
  93                 throw new IllegalArgumentException("Cannot encode unsupported type " + method.getClass().getName() + ": " + method.format("%H.%n(%p)"));
  94             }
  95         }
  96     }
  97 
  98     @Override
  99     public void addType(ResolvedJavaType type) {
 100         if (!addNull(type, NULL_TYPE)) {
 101             checkOpen();
 102             if (type instanceof HotSpotResolvedObjectTypeImpl) {
 103                 try {
 104                     dos.writeLong(((HotSpotResolvedObjectTypeImpl) type).getMetaspaceKlass());
 105                 } catch (IOException e) {
 106                     throw new InternalError(e);
 107                 }
 108             } else {
 109                 throw new IllegalArgumentException("Cannot encode unsupported type " + type.getClass().getName() + ": " + type.toClassName());
 110             }
 111         }
 112     }
 113 
 114     @Override
 115     public void addString(String value) {
 116         if (!addNull(value, NULL_STRING)) {
 117             checkOpen();
 118             try {
 119                 dos.writeChars(value);
 120             } catch (IOException e) {
 121                 throw new InternalError(e);
 122             }
 123         }
 124     }
 125 
 126     @Override
 127     public void addInt(int value) {
 128         checkOpen();
 129         try {
 130             dos.writeInt(value);
 131         } catch (IOException e) {
 132             throw new InternalError(e);
 133         }
 134     }
 135 
 136     @Override
 137     public void addLong(long value) {
 138         checkOpen();
 139         try {
 140             dos.writeLong(value);
 141         } catch (IOException e) {
 142             throw new InternalError(e);
 143         }
 144     }
 145 
 146     private boolean addNull(Object o, int nullValue) {
 147         if (o == null) {
 148             addInt(nullValue);
 149             return true;
 150         }
 151         return false;
 152     }
 153 
 154     /**
 155      * Prototype SHA1 digest that is cloned before use.
 156      */
 157     private static final MessageDigest SHA1 = getSHA1();
 158     private static final int SHA1_LENGTH = SHA1.getDigestLength();
 159 
 160     private static MessageDigest getSHA1() {
 161         try {
 162             MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
 163             sha1.clone();
 164             return sha1;
 165         } catch (CloneNotSupportedException | NoSuchAlgorithmException e) {
 166             // Should never happen given that SHA-1 is mandated in a
 167             // compliant Java platform implementation.
 168             throw new JVMCIError("Expect a cloneable implementation of a SHA-1 message digest to be available", e);
 169         }
 170     }
 171 
 172     /**
 173      * Gets the final encoded byte array and closes this encoding such that any further attempts to
 174      * update it result in an {@link IllegalArgumentException}.
 175      */
 176     byte[] getByteArray() {
 177         if (result == null) {
 178             if (count > SHA1_LENGTH) {
 179                 try {
 180                     MessageDigest md = (MessageDigest) SHA1.clone();
 181                     md.update(buf, 0, count);
 182                     result = md.digest();
 183                 } catch (CloneNotSupportedException e) {
 184                     throw new InternalError(e);
 185                 }
 186             } else {
 187                 if (buf.length == count) {
 188                     // No need to copy the byte array
 189                     return buf;
 190                 }
 191                 result = Arrays.copyOf(buf, count);
 192             }
 193             dos = null;
 194         }
 195         return result;
 196     }
 197 }