Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions src/main/java/io/jawk/backend/AVM.java
Original file line number Diff line number Diff line change
Expand Up @@ -2889,9 +2889,15 @@ private String normalizeIndirectFunctionName(String functionName) {

/**
* Unwinds every call frame and clears the per-call runtime state, after an
* {@code exit} statement or an abandoned execution.
* {@code exit} or {@code nextfile} statement or an abandoned execution.
* When profiling, the abandoned function calls are recorded as exited at
* this point, so the timing report stays accurate and the active-function
* stack does not leak entries (see #557).
*/
private void resetCallState() {
if (profiling) {
recordAllFunctionExits(System.nanoTime());
}
runtimeStack.popAllFrames();
elementArgumentReferences.clear();
clearOperandStack();
Expand Down Expand Up @@ -4180,8 +4186,7 @@ private void executeNextfile(PositionTracker position) {
"`nextfile' cannot be called from an ENDFILE rule");
}
// nextfile can be invoked from user-defined functions: unwind them.
runtimeStack.popAllFrames();
clearOperandStack();
resetCallState();
if (endFileAddress == null
|| withinBeginFileBlocks && jrt.hasPendingInputFileError(resolvedInputSource)) {
// No ENDFILE rules to run, or the file could not be opened: skip
Expand Down
6 changes: 6 additions & 0 deletions src/site/markdown/behavior-changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ released version automatically via .github/scripts/stamp-behavior-changes.sh.

## Unreleased

- The `--profile` function timing report now records a user-defined function call abandoned by
`nextfile` as exited when `nextfile` unwinds it, with its actual duration. Previously each such
call leaked an entry on the internal function timing stack (unbounded memory growth on long
profiled runs), and the abandoned calls were either missing from the report or accounted with
wildly wrong durations at program exit
([#557](https://github.com/jawkio/jawk/issues/557)).
- A conditional expression whose result feeds a further operation on a literal — shapes like
`(v ? v : 24) * 2`, `-(v ? 5 : 24)`, or `$(v ? 1 : 2)` — now evaluates correctly under the
default tuple optimization. Previously the peephole literal fold merged the false branch's
Expand Down
40 changes: 40 additions & 0 deletions src/test/java/io/jawk/CliOptionTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.junit.Test;
import org.junit.Rule;
import org.junit.rules.TemporaryFolder;
Expand Down Expand Up @@ -118,6 +120,44 @@ public void profileOptionRecordsIndirectUserFunctions() throws Exception {
assertTrue(result.errorOutput().contains("typeof"));
}

@Test
public void profileOptionRecordsFunctionsUnwoundByNextfile() throws Exception {
AwkTestSupport.TestResult result = AwkTestSupport
.cliTest("CLI --profile records functions unwound by nextfile")
.argument("--profile")
.script("function inner() { nextfile } function outer() { inner() } { outer() }")
.file("f1", "a1\na2\n")
.file("f2", "b1\nb2\n")
.file("f3", "c1\nc2\n")
.operand("{{f1}}", "{{f2}}", "{{f3}}")
.expect("")
.run();

result.assertExpected();
// Each file triggers exactly one outer() -> inner() -> nextfile chain;
// nextfile abandons both calls, which must still be recorded once each.
assertEquals(3, profiledFunctionCount(result.errorOutput(), "inner"));
assertEquals(3, profiledFunctionCount(result.errorOutput(), "outer"));
}

/**
* Extracts the execution count of one function from a {@code --profile}
* report.
*
* @param report the profiling report text
* @param functionName the function to look up
* @return the reported execution count
*/
private static long profiledFunctionCount(String report, String functionName) {
Matcher matcher = Pattern
.compile("^\\s{2}" + Pattern.quote(functionName) + "\\s+(\\d+)\\s", Pattern.MULTILINE)
.matcher(report);
assertTrue(
"function `" + functionName + "' missing from profiling report:\n" + report,
matcher.find());
return Long.parseLong(matcher.group(1));
}

@Test
public void profileOptionWithFilenameWritesReportToFile() throws Exception {
File profile = tempFolder.newFile("profile.txt");
Expand Down
Loading