1 /*
   2  * Copyright (c) 2015, 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 org.graalvm.compiler.debug;
  24 
  25 import java.util.ArrayList;
  26 import java.util.Collection;
  27 import java.util.Collections;
  28 import java.util.List;
  29 import java.util.stream.Collectors;
  30 
  31 import org.graalvm.compiler.options.Option;
  32 import org.graalvm.compiler.options.OptionValue;
  33 
  34 /**
  35  * Facility for fingerprinting execution.
  36  */
  37 public class Fingerprint implements AutoCloseable {
  38 
  39     public static class Options {
  40         @Option(help = "Enables execution fingerprinting.")//
  41         public static final OptionValue<Boolean> UseFingerprinting = new OptionValue<>(false);
  42 
  43         @Option(help = "Limit number of events shown in fingerprinting error message.")//
  44         public static final OptionValue<Integer> FingerprintErrorEventTailLength = new OptionValue<>(50);
  45 
  46         @Option(help = "Fingerprinting event at which to execute breakpointable code.")//
  47         public static final OptionValue<Integer> FingerprintingBreakpointEvent = new OptionValue<>(-1);
  48     }
  49 
  50     /**
  51      * Determines whether fingerprinting is enabled.
  52      */
  53     public static final boolean ENABLED = Options.UseFingerprinting.getValue();
  54 
  55     private static final ThreadLocal<Fingerprint> current = ENABLED ? new ThreadLocal<>() : null;
  56 
  57     private final List<String> events;
  58     private int index;
  59 
  60     /**
  61      * Creates an object to record a fingerprint.
  62      */
  63     public Fingerprint() {
  64         events = new ArrayList<>();
  65         index = -1;
  66     }
  67 
  68     /**
  69      * Creates an object to verify execution matches a given fingerprint.
  70      *
  71      * @param toVerifyAgainst the fingerprint events to verify against
  72      */
  73     public Fingerprint(List<String> toVerifyAgainst) {
  74         this.events = toVerifyAgainst;
  75         index = 0;
  76     }
  77 
  78     /**
  79      * Creates an object to verify execution matches a given fingerprint.
  80      *
  81      * @param toVerifyAgainst the fingerprint to verify against
  82      */
  83     public Fingerprint(Fingerprint toVerifyAgainst) {
  84         this(toVerifyAgainst.events);
  85     }
  86 
  87     public Collection<String> getEvents() {
  88         return Collections.unmodifiableCollection(events);
  89     }
  90 
  91     /**
  92      * Starts fingerprint recording or verification for the current thread. At most one fingerprint
  93      * object can be active for any thread.
  94      */
  95     public Fingerprint open() {
  96         if (ENABLED) {
  97             assert current.get() == null;
  98             current.set(this);
  99             return this;
 100         }
 101         return null;
 102     }
 103 
 104     /**
 105      * Finishes fingerprint recording or verification for the current thread.
 106      */
 107     @Override
 108     public void close() {
 109         if (ENABLED) {
 110             assert current.get() == this;
 111             current.set(null);
 112         }
 113     }
 114 
 115     private static final int BREAKPOINT_EVENT = Options.FingerprintingBreakpointEvent.getValue();
 116 
 117     /**
 118      * Submits an execution event for the purpose of recording or verifying a fingerprint. This must
 119      * only be called if {@link #ENABLED} is {@code true}.
 120      */
 121     public static void submit(String format, Object... args) {
 122         assert ENABLED : "fingerprinting must be enabled (-Dgraal." + Options.UseFingerprinting.getName() + "=true)";
 123         Fingerprint fingerprint = current.get();
 124         if (fingerprint != null) {
 125             int eventId = fingerprint.nextEventId();
 126             if (eventId == BREAKPOINT_EVENT) {
 127                 // Set IDE breakpoint on the following line and set the relevant
 128                 // system property to debug a fingerprint verification error.
 129                 System.console();
 130             }
 131             fingerprint.event(String.format(eventId + ": " + format, args));
 132         }
 133     }
 134 
 135     private int nextEventId() {
 136         return index == -1 ? events.size() : index;
 137     }
 138 
 139     private static final int MAX_EVENT_TAIL_IN_ERROR_MESSAGE = Options.FingerprintErrorEventTailLength.getValue();
 140 
 141     private String tail() {
 142         int start = Math.max(index - MAX_EVENT_TAIL_IN_ERROR_MESSAGE, 0);
 143         return events.subList(start, index).stream().collect(Collectors.joining(String.format("%n")));
 144     }
 145 
 146     private void event(String entry) {
 147         if (index == -1) {
 148             events.add(entry);
 149         } else {
 150             if (index > events.size()) {
 151                 throw new InternalError(String.format("%s%nOriginal fingerprint limit reached", tail()));
 152             }
 153             String l = events.get(index);
 154             if (!l.equals(entry)) {
 155                 throw new InternalError(String.format("%s%nFingerprint differs at event %d%nexpected: %s%n  actual: %s", tail(), index, l, entry));
 156             }
 157             index++;
 158         }
 159     }
 160 }