Bug #3426


No binding slot allocated for xsl:apply-templates call within xsl:override

Added by Michael Kay over 6 years ago. Updated over 6 years ago.

XSLT 3.0 packages
Start date:
Due date:
% Done:


Estimated time:
Legacy ID:
Applies to branch:
Fix Committed on Branch:
Fixed in Maintenance Release:


When an xsl:apply-templates instruction with no mode attribute appears within a template rule within the xsl:override element, no explicit binding slot is allocated for binding the mode. This does not cause a run-time error because there is a fallback strategy of using the unnamed mode in the same package. However, this strategy is not necessarily correct in all cases.

There are surprisingly few test cases that exercise this condition: one is use-package-170, and in this case the fallback strategy works successfully.

The problem reveals itself when running use-package-170 under Saxon-JS, which does not have the same fallback strategy. In this case the failure to allocate a binding slot (resulting in the value bSlot="-1" in the SEF file) is catastrophic.

Actions #1

Updated by Michael Kay over 6 years ago

There is a TODO comment at XSLUsePackage#127

// TODO: surely too early to allocate slots, until we've done all the overrides

which acknowledges the problem: slots for template rules in an overridden mode are being allocated prematurely (and the invoked code does nothing).

Actions #2

Updated by Michael Kay over 6 years ago

There's an interaction here with JIT compilation of template rules.

With JIT compilation, the code in TemplateRuleInitializer.init() calls SimpleMode.allocateBindingSlotsRecursive() to allocate component binding slots to a template rule when it is first invoked. I have a concern that this isn't synchronized: two different template rules could be compiled simultaneously in different threads leading to conflicting slot numbers.

With JIT disabled, as far as I can see there is no attempt to allocate binding slots to an overriding template rule; the fallback components are always used (which will cause incorrect behaviour if the fallback components are themselves overridden in a third package). For the Saxon-JS case, of course, template rules are always compiled eagerly so the code can be exported in SEF form.

In the XSLT test driver for W3C tests, JIT compilation is disabled by default. It can be enabled using the -jit flag, but this only affects the top-level package: library packages (it seems) are always compiled eagerly. This doesn't seem necessarily a good idea.

Actions #3

Updated by Michael Kay over 6 years ago

The attempt to allocate slots at XSLUsePackage#127 is not only ineffective, it is positively harmful, because it results in the flag CompoundMode.bindingSlotsAllocated being set, which frustrates the second attempt to allocate slots at PrincipalStylesheetModule#1383.

If we abandon the first attempt, the second attempt does in fact allocate binding slots to the overriding template rules: in this case it allocates slot 2. However, we then get a crash at run-time because the binding vector does not have a slot 2. It seems that the component in whose binding vector we created the slot is not the component that is current at run-time.

Actions #4

Updated by Michael Kay over 6 years ago

The ModeEE object has a flag bindingSlotsAllocated which ensures that binding slots are only allocated to component references once.

So we are getting slots allocated to mode-normal-in-package-B. This mode is then used unchanged by being accepted into package-A. When the time comes to allocate slots to mode-normal-in-package-A, nothing is happening, because the two components share the same ModeEE object and the flag is set indicating slots have already been allocated.

In principle, when a component is declared in one package and used in another, we should have two Component objects sharing the same Actor object but with different binding vectors (of the same length); the slot numbers in the component invocation objects are necessarily the same, but the components referenced in the two binding vectors are different.

I'm investigating how this is actually implemented. The code in Actor.processComponentReference() does two things: it allocates a slot number, and it adds a component reference to the binding vector. But it's explicitly designed to only process each Actor once (it throws an error if the slot number has already been allocated).

Actions #5

Updated by Michael Kay over 6 years ago

Debugging is not helped by the fact that the test driver compiles the top-level stylesheet package twice. If ~~export is set, the call on compile() within the exportImport() method in the test driver is redundant ~~ I have taken it out.

I've created a new test override-f-033 to explore what's going on, taking care to avoid complications like function inlining that cause many of the existing tests to work without going through the full machinery.

For a component like f:h() that is declared in one component and used in another, the binding vector for the component in the using package is created not by calling PrincipalStylesheetModule#allocateAllBindingSlots, but rather in StylesheetPackage.addComponentsFromUsedPackage(), where the binding vector is created by modifying the binding vector of the base component, without actually looking at the expression tree for the body of the component at all. This should apply to Modes as much as to individual named components.

Actions #6

Updated by Michael Kay over 6 years ago

It seems that StylesheetPackage.addComponentsFromUsedPackage() creates a list of completion actions which are carried out at the end of the use-package process, and these actions include copying the modified binding vectors for "used" components to the new derived components in the using package. This process is overwriting the binding vector created for the mode that includes additional binding slots for the overridden template rules.

The logic of the completion action is handling overridden named components properly, but it doesn't cater for overridden modes.

Actions #7

Updated by Michael Kay over 6 years ago

  • Status changed from New to Resolved
  • Applies to branch 9.8 added
  • Fix Committed on Branch 9.8 added

Now fixed. Summary of changes:

  • In StylesheetPackage.addComponentsFromUsedPackage, add new logic for handling modes that have one or more template rules overridden in the using package.

  • In XSLUsePackage (a) collect information about modes that have been overridden by adding template rules, and (b) avoid attempting to allocate binding slots to overridden modes, because this will be be done later.

Actions #8

Updated by O'Neil Delpratt over 6 years ago

  • Status changed from Resolved to Closed
  • % Done changed from 0 to 100
  • Fixed in Maintenance Release added

Bug fix applied in the Saxon maintenance release.

Please register to edit this issue

Also available in: Atom PDF