From 72d938fc5452be061a2ed650b9c0f57de1f5aef2 Mon Sep 17 00:00:00 2001 From: Jack Smith Date: Tue, 3 Sep 2024 18:37:33 -0400 Subject: [PATCH] Add feature to ignore parse errors when doing nested interpretation --- .../hubspot/jinjava/interpret/Context.java | 33 +++++++++++++++++++ .../interpret/ContextConfigurationIF.java | 8 +++++ .../jinjava/interpret/JinjavaInterpreter.java | 16 ++++++++- .../com/hubspot/jinjava/tree/TreeParser.java | 25 ++++++++------ 4 files changed, 71 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/hubspot/jinjava/interpret/Context.java b/src/main/java/com/hubspot/jinjava/interpret/Context.java index 1ea7aaf71..4aef74aec 100644 --- a/src/main/java/com/hubspot/jinjava/interpret/Context.java +++ b/src/main/java/com/hubspot/jinjava/interpret/Context.java @@ -819,6 +819,23 @@ public TemporaryValueClosable withUnwrapRawOverride() { return temporaryValueClosable; } + public boolean isIgnoreParseErrors() { + return contextConfiguration.isIgnoreParseErrors(); + } + + private void setIgnoreParseErrors(boolean ignoreParseErrors) { + contextConfiguration = contextConfiguration.withIgnoreParseErrors(ignoreParseErrors); + } + + public TemporaryValueClosable withIgnoreParseErrors() { + TemporaryValueClosable temporaryValueClosable = new TemporaryValueClosable<>( + isIgnoreParseErrors(), + this::setIgnoreParseErrors + ); + setIgnoreParseErrors(true); + return temporaryValueClosable; + } + public static class TemporaryValueClosable implements AutoCloseable { private final T previousValue; @@ -829,10 +846,26 @@ private TemporaryValueClosable(T previousValue, Consumer resetValueConsumer) this.resetValueConsumer = resetValueConsumer; } + public static TemporaryValueClosable noOp() { + return new NoOpTemporaryValueClosable<>(); + } + @Override public void close() { resetValueConsumer.accept(previousValue); } + + private static class NoOpTemporaryValueClosable extends TemporaryValueClosable { + + private NoOpTemporaryValueClosable() { + super(null, null); + } + + @Override + public void close() { + // No-op + } + } } public Node getCurrentNode() { diff --git a/src/main/java/com/hubspot/jinjava/interpret/ContextConfigurationIF.java b/src/main/java/com/hubspot/jinjava/interpret/ContextConfigurationIF.java index 5aac82fa8..b224ca262 100644 --- a/src/main/java/com/hubspot/jinjava/interpret/ContextConfigurationIF.java +++ b/src/main/java/com/hubspot/jinjava/interpret/ContextConfigurationIF.java @@ -47,4 +47,12 @@ default boolean isPartialMacroEvaluation() { default boolean isUnwrapRawOverride() { return false; } + + /** + * When trying nested interpretation, parsing errors are likely. This flag avoids propogating to differentiate between static and dynamic parsing errors. + */ + @Default + default boolean isIgnoreParseErrors() { + return false; + } } diff --git a/src/main/java/com/hubspot/jinjava/interpret/JinjavaInterpreter.java b/src/main/java/com/hubspot/jinjava/interpret/JinjavaInterpreter.java index 9f5a99ce9..89d1b0c48 100644 --- a/src/main/java/com/hubspot/jinjava/interpret/JinjavaInterpreter.java +++ b/src/main/java/com/hubspot/jinjava/interpret/JinjavaInterpreter.java @@ -29,6 +29,7 @@ import com.hubspot.jinjava.el.ExpressionResolver; import com.hubspot.jinjava.el.ext.DeferredParsingException; import com.hubspot.jinjava.el.ext.ExtendedParser; +import com.hubspot.jinjava.interpret.Context.TemporaryValueClosable; import com.hubspot.jinjava.interpret.TemplateError.ErrorItem; import com.hubspot.jinjava.interpret.TemplateError.ErrorReason; import com.hubspot.jinjava.interpret.TemplateError.ErrorType; @@ -83,6 +84,8 @@ public class JinjavaInterpreter implements PyishSerializable { public static final String OUTPUT_UNDEFINED_VARIABLES_ERROR = "OUTPUT_UNDEFINED_VARIABLES_ERROR"; + public static final String IGNORE_NESTED_INTERPRETATION_PARSE_ERRORS = + "IGNORE_NESTED_INTERPRETATION_PARSE_ERRORS"; private final Multimap blocks = ArrayListMultimap.create(); private final LinkedList extendParentRoots = new LinkedList<>(); private final Map revertibleObjects = new HashMap<>(); @@ -259,7 +262,7 @@ public String renderFlat(String template) { public String renderFlat(String template, long renderLimit) { int depth = context.getRenderDepth(); - try { + try (TemporaryValueClosable c = ignoreParseErrorsIfActivated()) { if (depth > config.getMaxRenderDepth()) { ENGINE_LOG.warn("Max render depth exceeded: {}", Integer.toString(depth)); return template; @@ -272,6 +275,17 @@ public String renderFlat(String template, long renderLimit) { } } + private TemporaryValueClosable ignoreParseErrorsIfActivated() { + return config + .getFeatures() + .getActivationStrategy( + JinjavaInterpreter.IGNORE_NESTED_INTERPRETATION_PARSE_ERRORS + ) + .isActive(context) + ? context.withIgnoreParseErrors() + : TemporaryValueClosable.noOp(); + } + /** * Parse the given string into a root Node, and then renders it processing extend parents. * diff --git a/src/main/java/com/hubspot/jinjava/tree/TreeParser.java b/src/main/java/com/hubspot/jinjava/tree/TreeParser.java index 8bdc29653..76d65a9d0 100644 --- a/src/main/java/com/hubspot/jinjava/tree/TreeParser.java +++ b/src/main/java/com/hubspot/jinjava/tree/TreeParser.java @@ -81,7 +81,7 @@ public Node buildTree() { do { if (parent != root) { - interpreter.addError( + maybeAddError( TemplateError.fromException( new MissingEndTagException( ((TagNode) parent).getEndName(), @@ -113,7 +113,7 @@ private Node nextNode() { if (token.getType() == symbols.getFixed()) { if (token instanceof UnclosedToken) { - interpreter.addError( + maybeAddError( new TemplateError( ErrorType.WARNING, ErrorReason.SYNTAX_ERROR, @@ -134,7 +134,7 @@ private Node nextNode() { } else if (token.getType() == symbols.getNote()) { String commentClosed = symbols.getClosingComment(); if (!token.getImage().endsWith(commentClosed)) { - interpreter.addError( + maybeAddError( new TemplateError( ErrorType.WARNING, ErrorReason.SYNTAX_ERROR, @@ -148,7 +148,7 @@ private Node nextNode() { ); } } else { - interpreter.addError( + maybeAddError( TemplateError.fromException( new UnexpectedTokenException( token.getImage(), @@ -237,13 +237,11 @@ private Node tag(TagToken tagToken) { try { tag = interpreter.getContext().getTag(tagToken.getTagName()); if (tag == null) { - interpreter.addError( - TemplateError.fromException(new UnknownTagException(tagToken)) - ); + maybeAddError(TemplateError.fromException(new UnknownTagException(tagToken))); return null; } } catch (DisabledException e) { - interpreter.addError( + maybeAddError( new TemplateError( ErrorType.FATAL, ErrorReason.DISABLED, @@ -292,7 +290,7 @@ private void endTag(Tag tag, TagToken tagToken) { hasMatchingStartTag = true; break; } else { - interpreter.addError( + maybeAddError( TemplateError.fromException( new TemplateSyntaxException( tagToken.getImage(), @@ -305,7 +303,7 @@ private void endTag(Tag tag, TagToken tagToken) { } } if (!hasMatchingStartTag) { - interpreter.addError( + maybeAddError( new TemplateError( ErrorType.WARNING, ErrorReason.SYNTAX_ERROR, @@ -326,4 +324,11 @@ private boolean isTrimmingEnabledForToken(Token token, JinjavaConfig jinjavaConf } return jinjavaConfig.getLegacyOverrides().isUseTrimmingForNotesAndExpressions(); } + + private void maybeAddError(TemplateError templateError) { + if (interpreter.getContext().isIgnoreParseErrors()) { + return; + } + interpreter.addError(templateError); + } }