RealDebug.java

/* ========================================================================
 * PlantUML : a free UML diagram generator
 * ========================================================================
 *
 * (C) Copyright 2009-2024, Arnaud Roques
 *
 * Project Info:  https://plantuml.com
 * 
 * If you like this project or if you find it useful, you can support us at:
 * 
 * https://plantuml.com/patreon (only 1$ per month!)
 * https://plantuml.com/paypal
 * 
 * This file is part of PlantUML.
 *
 * PlantUML is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * PlantUML 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 for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
 * USA.
 *
 *
 * Original Author:  Arnaud Roques
 * 
 *
 */
package net.sourceforge.plantuml.real;

import net.sourceforge.plantuml.log.Logme;
import net.sourceforge.plantuml.teavm.TeaVM;

// Layout (RealMax/RealMin/PositiveForce chains) can create many thousands of
// objects for a single large diagram: one RealMax/RealMoveable per gauge tile,
// one PositiveForce per constraint. Capturing a stack trace in every
// constructor "just in case" it is needed later for a diagnostic message is
// affordable on the JVM (fillInStackTrace() is cheap there) but is much more
// costly on targets compiled through TeaVM, where each capture walks a native
// JavaScript stack instead of a JVM one. Since these captures only exist to
// help track down an "Infinite recursion?" crash and are never used on the
// happy path, they are opt-in on the JVM: enable with -DPLANTUML_REAL_DEBUG=true
// (or the environment variable of the same name) while investigating such a
// crash.
//
// create() is the single gate for this: it hands back a real RealDebug
// (holding an already-captured stack trace) only when the flag above is set,
// and null otherwise. A RealMax/RealMoveable/PositiveForce that doesn't need
// one therefore pays for neither the capture nor an extra allocation -- its
// creationPoint field is just null, exactly as if RealDebug didn't exist --
// which is what keeps this opt-in rather than paid by every instance.
// printCreationStackTrace(RealDebug) is the matching read side: it prints the
// "rerun with -DPLANTUML_REAL_DEBUG=true" fallback for a null RealDebug (the
// common case) or forwards the captured trace to Logme.error(Throwable) for a
// real one.
//
// Under TeaVM, create() unconditionally returns null, not just by default:
// its only consumer, Logme.error(Throwable), is itself a no-op there (see
// Logme.java's own "if (!TeaVM.isTeaVM())" guard), so a captured stack trace
// could never be shown to anyone in a browser build even if PLANTUML_REAL_DEBUG
// were set -- there is no debugging capability being traded away, only dead
// work being skipped. TeaVM.isTeaVM() resolves to a compile-time constant, so
// `!TeaVM.isTeaVM() && CAPTURE_CREATION_POINT` in create() folds to a constant
// false under TeaVM and the whole branch -- including the call that would have
// allocated a RealDebug -- is dead-code-eliminated from the generated
// JavaScript rather than merely short-circuited at runtime -- same mechanism
// as FileFormat.imDummy. (CAPTURE_CREATION_POINT itself is still evaluated,
// once, at class-init time under TeaVM too, since nothing marks the
// System.getProperty/getenv calls below as JVM-only; that single read has no
// measurable cost, unlike the per-instance capture it used to gate directly.)
class RealDebug {

	static private final boolean CAPTURE_CREATION_POINT = isTrue(System.getProperty("PLANTUML_REAL_DEBUG"))
			|| isTrue(System.getenv("PLANTUML_REAL_DEBUG"));

	private final Throwable creationPoint;

	// Returns null (no allocation) unless PLANTUML_REAL_DEBUG is set and we're
	// not under TeaVM -- see the class comment above for why that keeps this
	// free on the common path in both environments.
	public static RealDebug create() {
		if (!TeaVM.isTeaVM() && CAPTURE_CREATION_POINT)
			return new RealDebug();
		return null;
	}

	private RealDebug() {
		creationPoint = new Throwable();
		creationPoint.fillInStackTrace();
	}

	// debug is null whenever create() didn't capture anything (flag off, or
	// TeaVM) -- see the class comment above.
	public static void printCreationStackTrace(RealDebug debug) {
		if (debug == null)
			System.err.println("(creation point not captured; rerun with -DPLANTUML_REAL_DEBUG=true to capture it)");
		else
			Logme.error(debug.creationPoint);
	}

	private static boolean isTrue(final String value) {
		return value != null && (value.equalsIgnoreCase("true") || value.equals("1"));
	}

}