Source Maps — Runtime Error Location Reporting
When a UPLC program fails at runtime (Error term, budget exhaustion, builtin type error), the default error message points to opaque UPLC internals:
Evaluation failed: Error term encounteredSource maps bridge this gap by mapping UPLC terms back to the Java source line that generated them.
Quick Start
Section titled “Quick Start”With JulcEval (recommended)
Section titled “With JulcEval (recommended)”Add .sourceMap() to your evaluator:
// Before (no source location)var eval = JulcEval.forClass(SwapOrder.class);PlutusData result = eval.call("makerAddress", MAKER).asData();// On failure: "Evaluation failed: Error term encountered"
// After (with source location)var eval = JulcEval.forClass(SwapOrder.class).sourceMap();PlutusData result = eval.call("makerAddress", MAKER).asData();// On failure: "Evaluation failed: Error term encountered// at SwapOrder.java:42 (Builtins.error())"That’s it. One method call. The error message now includes the file name, line number, and a snippet of the Java expression that caused the failure.
With ValidatorTest
Section titled “With ValidatorTest”For validator-level testing:
// Compile with source mapsCompileResult compiled = ValidatorTest.compileValidatorWithSourceMap(EscrowValidator.class);
// EvaluateEvalResult result = ValidatorTest.evaluate(compiled.program(), scriptContext);
// On failure, resolve the Java source locationSourceLocation location = ValidatorTest.resolveErrorLocation(result, compiled.sourceMap());System.out.println("Error at: " + location);// → "EscrowValidator.java:58 (Builtins.error())"Or use the assertion shorthand:
// Throws with source location in the error messageValidatorTest.assertValidatesWithSourceMap(compiled, scriptContext);// → AssertionError: Expected validator to succeed, but got:// Failure{error=Error term encountered, budget=ExBudget{cpu=..., mem=...}, traces=[]}// at EscrowValidator.java:58 (Builtins.error())Enabling Source Maps
Section titled “Enabling Source Maps”Source maps must be enabled at compile time. There are three ways depending on your build setup.
Gradle Plugin DSL
Section titled “Gradle Plugin DSL”If you use the julc Gradle plugin:
julc { sourceMap = true}Annotation Processor in Gradle (without julc plugin)
Section titled “Annotation Processor in Gradle (without julc plugin)”Pass the -Ajulc.sourceMap=true compiler arg directly:
tasks.withType(JavaCompile).configureEach { options.compilerArgs.add('-Ajulc.sourceMap=true')}Annotation Processor in Maven
Section titled “Annotation Processor in Maven”<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <compilerArgs> <arg>-Ajulc.sourceMap=true</arg> </compilerArgs> </configuration></plugin>In all cases, the annotation processor reads the option via processingEnv.getOptions().get("julc.sourceMap")
and writes a .sourcemap.json file alongside the compiled .plutus.json under
META-INF/plutus/.
Usage Patterns
Section titled “Usage Patterns”Pattern 1: JulcEval with .sourceMap() (simplest)
Section titled “Pattern 1: JulcEval with .sourceMap() (simplest)”@Testvoid testSwapOrder() { var eval = JulcEval.forClass(SwapOrder.class).sourceMap();
// Happy path — works as before PlutusData result = eval.call("makerAddress", MAKER).asData(); assertNotNull(result);
// Error path — now shows source location in the exception message assertThrows(ExtractionException.class, () -> eval.call("validate", invalidData).asBoolean() );}Pattern 2: Validator compilation with source maps
Section titled “Pattern 2: Validator compilation with source maps”@Testvoid testEscrowValidator() { // Compile once with source maps var compiled = ValidatorTest.compileValidatorWithSourceMap(EscrowValidator.class);
// Evaluate multiple scenarios var ctx1 = buildScriptContext(/* valid */); ValidatorTest.assertValidatesWithSourceMap(compiled, ctx1);
var ctx2 = buildScriptContext(/* invalid */); ValidatorTest.assertRejectsWithSourceMap(compiled, ctx2);}With ContractTest
Section titled “With ContractTest”For real project tests (e.g., julc-examples) that extend ContractTest:
class SwapOrderTest extends ContractTest {
@Test void cancel_rejectsNonMaker() throws Exception { // Compile with source maps (drop-in replacement for compileValidator) var compiled = compileValidatorWithSourceMap(SwapOrder.class);
var ref = TestDataBuilder.randomTxOutRef_typed(); var ctx = spendingContext(ref, datum) .redeemer(redeemer) .signer(OTHER_PKH) .buildPlutusData();
var result = evaluate(compiled.program(), ctx);
// assertFailure with source map — error message includes Java source location assertFailure(result, compiled.sourceMap());
// Optional: log budget + error location logResult("cancel_rejectsNonMaker", result, compiled.sourceMap()); // → [cancel_rejectsNonMaker] CPU: 967522, Mem: 4536 | Error at: SwapOrder.java:42 (Builtins.error())
// Optional: resolve location programmatically var location = resolveErrorLocation(result, compiled.sourceMap()); System.out.println("Error at: " + location); }}Available ContractTest source map methods:
compileValidatorWithSourceMap(Class<?>)— compile with source maps (default source root)compileValidatorWithSourceMap(Class<?>, Path)— compile with custom source rootassertSuccess(result, sourceMap)/assertFailure(result, sourceMap)— assertions with source location in error messagesresolveErrorLocation(result, sourceMap)— getSourceLocationprogrammaticallylogResult(testName, result, sourceMap)— print budget + error location
Pattern 3: Programmatic (full control)
Section titled “Pattern 3: Programmatic (full control)”@Testvoid testWithFullControl() { var options = new CompilerOptions().setSourceMapEnabled(true); var compiler = new JulcCompiler(StdlibRegistry.defaultRegistry(), options);
CompileResult compiled = compiler.compile(source); SourceMap sourceMap = compiled.sourceMap();
var vm = JulcVm.create(); EvalResult result = vm.evaluateWithArgs(compiled.program(), List.of(scriptContext));
if (!result.isSuccess()) { // Get the failed Term from the result Term failedTerm = switch (result) { case EvalResult.Failure f -> f.failedTerm(); case EvalResult.BudgetExhausted b -> b.failedTerm(); default -> null; };
// Look up in source map SourceLocation location = sourceMap.lookup(failedTerm); if (location != null) { System.out.println("Error at: " + location); // → "MyValidator.java:42 (Builtins.error())" } }}Execution Tracing
Section titled “Execution Tracing”Source maps tell you where a failure occurred. Execution tracing goes further — it records every CEK machine step that has a source mapping, along with the CPU and memory consumed at each step. This lets you see the hot path through your validator and identify which Java lines consume the most budget.
What a trace contains
Section titled “What a trace contains”Each trace entry is an ExecutionTraceEntry record:
public record ExecutionTraceEntry( String fileName, // Java source file name int line, // 1-based line number String fragment, // Java expression snippet (nullable) String nodeType, // CEK step type: "Apply", "Force", "Case", "Error" long cpuDelta, // CPU consumed since previous trace point long memDelta // Memory consumed since previous trace point)With ValidatorTest (static)
Section titled “With ValidatorTest (static)”var compiled = ValidatorTest.compileValidatorWithSourceMap(MyValidator.class);var traced = ValidatorTest.evaluateWithTrace(compiled, scriptContext);
// Step-by-step trace with IntelliJ-clickable file:line linksSystem.out.println(traced.formatTrace());
// Aggregated CPU/mem by source location, sorted by costSystem.out.println(traced.formatBudgetSummary());
// Access the raw resultEvalResult result = traced.result();With ContractTest (instance)
Section titled “With ContractTest (instance)”class MyTest extends ContractTest { @Test void testBudget() { var compiled = compileValidatorWithSourceMap(MyValidator.class); var result = evaluateWithTrace(compiled, ctx);
// Format last trace System.out.println(formatExecutionTrace());
// Aggregated budget summary System.out.println(formatBudgetSummary());
// Or access raw entries List<ExecutionTraceEntry> entries = getLastExecutionTrace(); }}Output format: format() (step-by-step)
Section titled “Output format: format() (step-by-step)”Execution trace (1247 steps): at .(MyValidator.java:18) Apply cpu=+230 mem=+100 | validate(PlutusData, PlutusData) at .(MyValidator.java:22) Apply cpu=+456 mem=+200 | txInfo.fee() at .(MyValidator.java:23) Force cpu=+128 mem=+64 | list.head() ...Total: cpu=967522, mem=4536File and line references use at .(File.java:line) format, which is clickable in IntelliJ
console output.
Output format: formatSummary() (aggregated by location)
Section titled “Output format: formatSummary() (aggregated by location)”Budget by source location (top consumers first): MyValidator.java:42 visits=12 cpu=245000 mem=1200 | Builtins.equalsData(a, b) MyValidator.java:38 visits=8 cpu=180000 mem=900 | list.head() MyValidator.java:55 visits=1 cpu=95000 mem=450 | Builtins.verifyEd25519Signature(...) ...This format groups all visits to the same source line, sums their budgets, and sorts by CPU cost descending — making it easy to find optimization targets.
Builtin Trace
Section titled “Builtin Trace”Builtin trace records the last 20 builtin function executions in a ring buffer — function name, arguments, and result. It answers “what values caused the failure?”, complementary to source maps (“where?”) and execution tracing (“what path?”).
Key properties:
- Always on by default — no compile-time or runtime flags needed
- Negligible overhead — ring buffer of 20 entries, lazy string formatting (hot path stores raw refs)
- No source map required — useful for pre-compiled
.plutusfiles in the CLI - SPI-agnostic — works with any VM backend (uses string summaries, not impl-specific types)
Comparison
Section titled “Comparison”| Aspect | Source Map | Builtin Trace | Execution Trace |
|---|---|---|---|
| Answers | Where did it fail? | What values caused it? | What path did execution take? |
| Overhead | Compile-time only | Negligible (ring buffer) | Heavy (per-step recording) |
| Opt-in | Compile with sourceMap=true | Always on (default) | Enable tracing + source map |
| Output | MyValidator.java:42 | EqualsInteger(5, 3) → False | Full step-by-step trace |
| Use case | Locate error in source | Quick failure diagnosis | Budget profiling |
Usage from each entry point
Section titled “Usage from each entry point”JulcEval (fluent API)
Section titled “JulcEval (fluent API)”var eval = JulcEval.forClass(MyValidator.class).builtinTrace();eval.call("validate", data).asBoolean();// On failure: rich FailureReport in exception message// After any call:List<BuiltinExecution> trace = eval.getLastBuiltinTrace();Note:
.builtinTrace()is an alias for.sourceMap()— it communicates intent. Builtin trace is always collected regardless.
ValidatorTest (static API)
Section titled “ValidatorTest (static API)”// Lightweight: builtin trace only (no execution tracing overhead)var traced = ValidatorTest.evaluateWithBuiltinTrace(compiled, args);traced.builtinTrace(); // → List<BuiltinExecution>
// Lightweight diagnostics: returns FailureReport on failureFailureReport report = ValidatorTest.evaluateWithBuiltinDiagnostics(compiled, args);if (report != null) System.out.println(FailureReportFormatter.format(report));
// Full diagnostics: both execution trace + builtin traceFailureReport report = ValidatorTest.evaluateWithDiagnostics(compiled, args);
// Assert with rich error message on failureValidatorTest.assertValidatesWithDiagnostics(compiled, args);ContractTest (instance methods)
Section titled “ContractTest (instance methods)”class MyTest extends ContractTest { @Test void test() { var compiled = compileValidatorWithSourceMap(MyValidator.class);
// Lightweight eval (no execution tracing) var result = evaluateWithBuiltinTrace(compiled, ctx);
// Access builtin trace from shared VM List<BuiltinExecution> trace = getLastBuiltinTrace(); for (var exec : trace) { System.out.println(exec); // EqualsInteger(5, 3) → False } }}CLI (julc eval)
Section titled “CLI (julc eval)”$ julc eval my-validator.plutusFAIL: Error term encountered EqualsInteger(5, 3) → False
Last builtins: UnIData(<Data>) → 5 UnIData(<Data>) → 3 EqualsInteger(5, 3) → False
Budget: CPU=1,234,567 Mem=45,678The CLI automatically collects builtin trace and uses AnsiFailureReportFormatter for
colored terminal output.
Programmatic (JulcVm directly)
Section titled “Programmatic (JulcVm directly)”var vm = JulcVm.create();
// Per-evaluation options — thread-safe, no shared statevar options = EvalOptions.DEFAULT .withSourceMap(sourceMap) .withTracing(true);EvalResult result = vm.evaluateWithArgs(program, List.of(args), options);
// Traces are in the resultList<BuiltinExecution> trace = result.builtinTrace();List<ExecutionTraceEntry> execTrace = result.executionTrace();
// Disable builtin trace for zero-overhead production evalvar prodOptions = EvalOptions.DEFAULT.withBuiltinTrace(false);EvalResult result2 = vm.evaluate(program, prodOptions);Advantages
Section titled “Advantages”- Zero configuration — always collected, no compile-time or runtime flags needed
- Negligible overhead — ring buffer of 20 entries, lazy string formatting
- Pinpoints the exact comparison that failed —
findCauseBuiltin()scans backwards for False-returning comparisons (EqualsInteger,LessThanInteger,EqualsByteString, etc.) - Works without source maps — useful for pre-compiled
.plutusfiles - SPI-agnostic — works with any VM backend
Limitations
Section titled “Limitations”- Only last 20 builtins — earlier operations are lost (ring buffer)
- No source location per builtin — builtins aren’t mapped to Java lines (use source map for that)
- Values are summarized — ByteStrings truncated to 16 hex chars, strings to 20 chars, complex
values shown as
<Data>,[N elems],<Pair> - Cannot be disabled per-call from testkit APIs (always collected if VM supports it; disable via
JulcVm.setBuiltinTraceEnabled(false))
Failure Reports
Section titled “Failure Reports”FailureReport is a structured record that bundles all diagnostic context for a failed
evaluation: error message, source location, last builtins, execution trace, budget, and
trace messages.
Building a FailureReport
Section titled “Building a FailureReport”FailureReportBuilder.build() constructs a report from an evaluation result and optional traces:
FailureReport report = FailureReportBuilder.build(result, sourceMap, executionTrace, builtinTrace);
// Convenience overloads:FailureReport report = FailureReportBuilder.build(result, sourceMap); // no tracesFailureReport report = FailureReportBuilder.build(result); // no source map or tracesReturns null if the result is a Success.
Formatting
Section titled “Formatting”FailureReportFormatter.format(report) produces plain text output:
FAIL at .(VestingValidator.java:42) return deadline <= currentSlot LessThanEqualsInteger(5, 3) → False
Last builtins: UnIData(<Data>) → 5 UnIData(<Data>) → 3 LessThanEqualsInteger(5, 3) → False
Budget: CPU=1,234,567 Mem=45,678The header shows the source location (if available) or the error message. The highlighted
“cause” line is the last comparison builtin that returned False, identified by
report.findCauseBuiltin().
For CLI output, AnsiFailureReportFormatter adds ANSI colors: FAIL in red, builtin names
in cyan, → False in red, → True in green, budget in dim.
Choosing the Right Diagnostic Level
Section titled “Choosing the Right Diagnostic Level”From lightest to heaviest:
- No diagnostics —
ValidatorTest.evaluate()/ContractTest.evaluate()— fastest, just success/failure + budget - Builtin trace only —
evaluateWithBuiltinTrace()— adds “what values failed?” at negligible cost - Builtin diagnostics —
evaluateWithBuiltinDiagnostics()— returns structuredFailureReportwith source location + builtins - Full diagnostics —
evaluateWithDiagnostics()— adds per-step execution trace for budget profiling
Note: Builtin trace is always collected by the VM (enabled by default). The difference between levels 1 and 2 is whether you retrieve the trace, not whether it’s recorded. For zero-overhead production evaluation, disable with
vm.setBuiltinTraceEnabled(false).
Using with QuickTxBuilder (JulcTransactionEvaluator)
Section titled “Using with QuickTxBuilder (JulcTransactionEvaluator)”For offchain integration with cardano-client-lib’s QuickTxBuilder, use
JulcTransactionEvaluator with source maps and tracing.
Loading scripts with source maps
Section titled “Loading scripts with source maps”JulcScriptLoader.loadWithSourceMap() loads a pre-compiled script from the classpath and
reconstructs its source map with correct Term object identity:
// Load script + source map + deserialized programJulcScriptLoader.LoadResult loaded = JulcScriptLoader.loadWithSourceMap(MyValidator.class);
PlutusV3Script script = loaded.script(); // ready for QuickTxBuilderSourceMap sourceMap = loaded.sourceMap(); // may be null if no .sourcemap.jsonProgram program = loaded.program(); // deserialized UPLC program
// For parameterized validatorsvar loaded = JulcScriptLoader.loadWithSourceMap(MyValidator.class, param1, param2);LoadResult is a record:
public record LoadResult( PlutusV3Script script, SourceMap sourceMap, Program program)Registering scripts and enabling tracing
Section titled “Registering scripts and enabling tracing”// Create the evaluator (typically with a BackendService for protocol params)var evaluator = new JulcTransactionEvaluator(backendService);
// Register script so the evaluator can resolve it by hashevaluator.registerScript(script.getScriptHash(), loaded);
// Enable per-step execution tracingevaluator.enableTracing(true);
// Use with QuickTxBuildervar quickTx = new QuickTxBuilder(backendService) .withTxEvaluator(evaluator);Retrieving traces after evaluation
Section titled “Retrieving traces after evaluation”// After QuickTxBuilder submits a transaction...Map<String, List<ExecutionTraceEntry>> traces = evaluator.getLastTraces();
// Traces are keyed by redeemer tag+index, e.g. "SPEND[0]", "MINT[0]"for (var entry : traces.entrySet()) { System.out.println("=== " + entry.getKey() + " ==="); System.out.println(ExecutionTraceEntry.format(entry.getValue())); System.out.println(ExecutionTraceEntry.formatSummary(entry.getValue()));}Important: When tracing is enabled, traces are automatically printed to stdout during
evaluation. This is intentional — QuickTxBuilder may swallow Result.error() messages,
so printing ensures you always see the trace output even if the transaction fails silently.
Source map methods
Section titled “Source map methods”You can also register source maps and programs individually:
evaluator.setSourceMap(scriptHash, sourceMap);evaluator.setProgram(scriptHash, program);What Gets Mapped
Section titled “What Gets Mapped”Source maps capture positions for these Java constructs:
| Java Construct | Example | Mapped? |
|---|---|---|
| Method calls | Builtins.error(), list.head() | Yes |
| Binary expressions | a.compareTo(b) < 0 | Yes |
| Unary expressions | !isValid | Yes |
| Field access | txInfo.fee() | Yes |
| Object creation | new EscrowDatum(...) | Yes |
| If statements | if (amount < 0) { ... } | Yes |
| Switch expressions | switch (action) { ... } | Yes |
| Return statements | return result | Yes |
| Method definitions | public static boolean validate(...) | Yes |
| Conditionals | cond ? a : b | Yes |
| Literals | 42, true, "hello" | No (not useful) |
| Variable references | amount | No (not useful) |
| Compiler-generated code | ValidatorWrapper, wrapDecode | No (no Java source) |
How It Works
Section titled “How It Works”Architecture
Section titled “Architecture”Java Source → PirGenerator → PIR Terms → UplcGenerator → UPLC Terms → CekMachine ↓ ↓ ↓ pirPositions map uplcPositions map currentTerm (PirTerm → SourceLocation) (Term → SourceLocation) (on exception) ↓ SourceMap (IdentityHashMap)- PirGenerator records
SourceLocationfor each PIR term it creates from a JavaParser AST node - UplcGenerator transfers positions from PIR terms to their outermost UPLC terms, propagating parent locations to children
- The resulting
IdentityHashMap<Term, SourceLocation>is wrapped in aSourceMap - When the CekMachine throws an exception, it attaches
currentTerm(the UPLC term being evaluated) - JavaVmProvider passes
failedTermthrough toEvalResult.Failure/BudgetExhausted - The testkit resolves
failedTermagainst theSourceMapto get theSourceLocation
Why optimization is skipped
Section titled “Why optimization is skipped”Source maps use IdentityHashMap — lookups are by object identity (==), not structural equality.
The UPLC optimizer creates new Term objects, which breaks identity. Since source maps are a
debugging feature, skipping optimization is the right trade-off: unoptimized UPLC is functionally
identical, just uses more budget.
Scalus VM backend
Section titled “Scalus VM backend”Source maps only work with the Java VM backend (julc-vm-java). The Scalus backend
serializes terms to FLAT format and re-parses them, which destroys object identity.
When using Scalus, failedTerm will be null.
API Reference
Section titled “API Reference”CompilerOptions
Section titled “CompilerOptions”new CompilerOptions() .setSourceMapEnabled(true) // enable source map generation .setVerbose(true); // optional: log source map statsSourceLocation
Section titled “SourceLocation”record SourceLocation(String fileName, int line, int column, String fragment)// toString() → "MyValidator.java:42 (Builtins.error())"SourceMap
Section titled “SourceMap”SourceMap.EMPTY // always returns nullsourceMap.lookup(term) // → SourceLocation or nullsourceMap.size() // number of mapped termssourceMap.isEmpty() // true if no entriesEvalResult.Failure / EvalResult.BudgetExhausted
Section titled “EvalResult.Failure / EvalResult.BudgetExhausted”failure.failedTerm() // → Term (nullable) — the UPLC term that caused the errorexhausted.failedTerm() // → Term (nullable) — the UPLC term when budget ran outExecutionTraceEntry
Section titled “ExecutionTraceEntry”record ExecutionTraceEntry( String fileName, int line, String fragment, String nodeType, long cpuDelta, long memDelta)
// Format a list of entries as step-by-step traceExecutionTraceEntry.format(entries) // → String (multi-line, with totals)
// Aggregate by source location, sorted by CPU costExecutionTraceEntry.formatSummary(entries) // → String (multi-line, with visit counts)
// toString() uses IntelliJ-clickable format: "at .(File.java:42)"BuiltinExecution
Section titled “BuiltinExecution”record BuiltinExecution(DefaultFun fun, String argSummary, String resultSummary)// toString() → "EqualsInteger(5, 3) → False"Values are summarized: ByteStrings truncated to 16 hex chars (#a1b2c3...), strings to 20
chars, complex values shown as <Data>, [N elems], <Pair>.
FailureReport
Section titled “FailureReport”record FailureReport( String errorMessage, // VM error message SourceLocation sourceLocation, // Java source location (nullable) List<BuiltinExecution> lastBuiltins, // last N builtin executions List<ExecutionTraceEntry> lastSteps, // last N execution trace entries ExBudget consumed, // budget consumed List<String> traceMessages // Builtins.trace() messages)
report.findCauseBuiltin() // → last comparison builtin returning False, or nullFailureReportBuilder
Section titled “FailureReportBuilder”FailureReportBuilder.build(result, sourceMap, executionTrace, builtinTrace) // → FailureReport or nullFailureReportBuilder.build(result, sourceMap) // no tracesFailureReportBuilder.build(result) // no source map or tracesFailureReportFormatter
Section titled “FailureReportFormatter”FailureReportFormatter.format(report) // → plain text multi-line stringEvalResult (traces)
Section titled “EvalResult (traces)”result.builtinTrace() // → List<BuiltinExecution> (empty if disabled)result.executionTrace() // → List<ExecutionTraceEntry> (empty if tracing off)EvalOptions (per-evaluation configuration)
Section titled “EvalOptions (per-evaluation configuration)”EvalOptions.DEFAULT // no source map, no tracing, builtin trace ONEvalOptions.DEFAULT.withSourceMap(sourceMap) // enable source mapsEvalOptions.DEFAULT.withTracing(true) // enable execution tracingEvalOptions.DEFAULT.withBuiltinTrace(false) // disable builtin trace (zero-overhead)new EvalOptions(sourceMap, true, true) // all three at onceValidatorTest
Section titled “ValidatorTest”// Compile with source mapsValidatorTest.compileValidatorWithSourceMap(MyValidator.class)ValidatorTest.compileValidatorWithSourceMap(MyValidator.class, sourceRoot)ValidatorTest.compileWithSourceMap(javaSource)
// Evaluate with execution tracingValidatorTest.evaluateWithTrace(compiled, args...) // → EvalResult (with traces)
// Evaluate with builtin trace only (lightweight, no execution tracing)ValidatorTest.evaluateWithBuiltinTrace(compiled, args...) // → EvalResult (with builtin trace)
// Diagnostics: builtin-only (lightweight) or full (with execution trace)ValidatorTest.evaluateWithBuiltinDiagnostics(compiled, args...) // → FailureReport or nullValidatorTest.evaluateWithDiagnostics(compiled, args...) // → FailureReport or null
// Resolve error locationValidatorTest.resolveErrorLocation(result, sourceMap) // → SourceLocation or null
// Assert with source location in error messageValidatorTest.assertValidatesWithSourceMap(compileResult, args...)ValidatorTest.assertRejectsWithSourceMap(compileResult, args...)
// Assert with rich diagnostics (FailureReport) on failureValidatorTest.assertValidatesWithDiagnostics(compiled, args...)ContractTest
Section titled “ContractTest”// Compile with source mapscompileValidatorWithSourceMap(MyValidator.class)compileValidatorWithSourceMap(MyValidator.class, sourceRoot)
// Evaluate with tracingevaluateWithTrace(compiled, args...) // → EvalResult (trace stored internally)
// Evaluate with builtin trace only (no execution tracing)evaluateWithBuiltinTrace(compiled, args...) // → EvalResult (trace stored internally)
// Access last tracesgetLastExecutionTrace() // → List<ExecutionTraceEntry>getLastBuiltinTrace() // → List<BuiltinExecution>formatExecutionTrace() // → formatted step-by-step traceformatBudgetSummary() // → formatted per-location budget summary
// Assertions with source location in error messagesassertSuccess(result, sourceMap)assertFailure(result, sourceMap)
// Resolve error locationresolveErrorLocation(result, sourceMap) // → SourceLocation or null
// Log budget + error locationlogResult("testName", result, sourceMap)JulcEval
Section titled “JulcEval”JulcEval.forClass(MyClass.class).sourceMap() // enable source mapsJulcEval.forClass(MyClass.class).builtinTrace() // alias for sourceMap() — communicates intentJulcEval.forClass(MyClass.class).trace() // enable execution tracing (implies sourceMap)JulcEval.forSource(javaString).sourceMap() // works with inline source too
eval.getLastBuiltinTrace() // → List<BuiltinExecution>eval.getLastExecutionTrace() // → List<ExecutionTraceEntry>eval.formatLastTrace() // → formatted step-by-step traceeval.formatLastBudgetSummary() // → formatted per-location budget summaryJulcScriptLoader
Section titled “JulcScriptLoader”// Load pre-compiled script with source map from classpathJulcScriptLoader.loadWithSourceMap(MyValidator.class) // → LoadResultJulcScriptLoader.loadWithSourceMap(MyValidator.class, params...)// → LoadResult (parameterized)JulcScriptLoader.loadSourceMap(MyValidator.class) // → SourceMap (nullable)JulcScriptLoader.LoadResult
Section titled “JulcScriptLoader.LoadResult”record LoadResult(PlutusV3Script script, SourceMap sourceMap, Program program)
loaded.script() // PlutusV3Script for QuickTxBuilderloaded.sourceMap() // SourceMap (null if no .sourcemap.json)loaded.program() // deserialized UPLC ProgramJulcTransactionEvaluator
Section titled “JulcTransactionEvaluator”evaluator.registerScript(scriptHash, loadResult) // register script + source map + programevaluator.setSourceMap(scriptHash, sourceMap) // register source map onlyevaluator.setProgram(scriptHash, program) // register program onlyevaluator.enableTracing(true) // enable per-step tracingevaluator.getLastTraces() // → Map<String, List<ExecutionTraceEntry>> // keyed by redeemer tag+index, e.g. "SPEND[0]"Limitations
Section titled “Limitations”- Compiler-generated terms (ValidatorWrapper lambdas, wrapDecode/wrapEncode, Z-combinator) have no source position —
lookup()returns null. - Library method errors: The source map points to the call site in user code, not the library implementation. This is intentional.
- String sources: When compiling from a string (not a file),
fileNameis null. The location shows:42 (fragment)instead ofFile.java:42 (fragment). - Optimization disabled: Source maps skip UPLC optimization. Budget numbers will be higher than production. Use source maps for debugging, not benchmarking.
- Java VM only: The Scalus VM backend does not support source maps.