Skip to content

Commit af480a2

Browse files
committed
Inline partial leak fix #655
1 parent 8c9a3c3 commit af480a2

5 files changed

Lines changed: 124 additions & 27 deletions

File tree

handlebars/src/main/java/com/github/jknack/handlebars/internal/BaseTemplate.java

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ public String filename() {
214214

215215
@Override
216216
public int[] position() {
217-
return new int[]{line, column };
217+
return new int[]{line, column};
218218
}
219219

220220
/**
@@ -255,32 +255,32 @@ public <T> TypeSafeTemplate<T> as() {
255255
* @return A new {@link TypeSafeTemplate}.
256256
*/
257257
private static Object newTypeSafeTemplate(final Class<?> rootType, final Template template) {
258-
return Proxy.newProxyInstance(rootType.getClassLoader(), new Class[]{rootType },
258+
return Proxy.newProxyInstance(rootType.getClassLoader(), new Class[]{rootType},
259259
new InvocationHandler() {
260260

261261
private final Map<String, Object> attributes = new HashMap<>();
262262
private final Object[] emptyArgs = {};
263263

264264
private boolean isDefault(final Method method) {
265265
return ((method.getModifiers()
266-
& (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC)) == Modifier.PUBLIC)
267-
&& method.getDeclaringClass().isInterface();
266+
& (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC)) == Modifier.PUBLIC)
267+
&& method.getDeclaringClass().isInterface();
268268
}
269269

270270
private Object invokeDefaultMethod(final Method method, final Class<?> lookupClass,
271-
final Object proxy, final Object ... args)
272-
throws Throwable {
271+
final Object proxy, final Object... args)
272+
throws Throwable {
273273
// Jumping through these hoops is needed because calling unreflectSpecial requires that
274274
// the lookup instance have private access to the special caller. None of the static
275275
// factory methods for Lookup will give us an instance with the access modes we need,
276276
// so we work around it by calling the private constructor via reflection.
277277
Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class
278-
.getDeclaredConstructor(Class.class, int.class);
278+
.getDeclaredConstructor(Class.class, int.class);
279279
constructor.setAccessible(true);
280280
return constructor.newInstance(lookupClass, -1 /* trusted */)
281-
.unreflectSpecial(method, lookupClass)
282-
.bindTo(proxy)
283-
.invokeWithArguments(args);
281+
.unreflectSpecial(method, lookupClass)
282+
.bindTo(proxy)
283+
.invokeWithArguments(args);
284284
}
285285

286286
@Override
@@ -304,8 +304,8 @@ public Object invoke(final Object proxy, final Method method, final Object[] met
304304
}
305305

306306
if (args.length == 1
307-
&& methodName.equals("equals")
308-
&& method.getParameterTypes()[0] == Object.class) {
307+
&& methodName.equals("equals")
308+
&& method.getParameterTypes()[0] == Object.class) {
309309
return args[0] == proxy;
310310
}
311311

@@ -374,11 +374,11 @@ protected void collectReferenceParameters(final Collection<String> result) {
374374

375375
@Override
376376
public String toJavaScript() {
377-
if (javaScript == null) {
378-
javaScript = handlebars.precompileInline(text());
379-
}
380-
return javaScript;
377+
if (javaScript == null) {
378+
javaScript = handlebars.precompileInline(text());
381379
}
380+
return javaScript;
381+
}
382382

383383
/**
384384
* @return True if this template has decorators.

handlebars/src/main/java/com/github/jknack/handlebars/internal/Partial.java

Lines changed: 54 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@
3535
import com.github.jknack.handlebars.Handlebars;
3636
import com.github.jknack.handlebars.HandlebarsError;
3737
import com.github.jknack.handlebars.HandlebarsException;
38+
import com.github.jknack.handlebars.PathCompiler;
39+
import com.github.jknack.handlebars.PathExpression;
3840
import com.github.jknack.handlebars.Template;
3941
import com.github.jknack.handlebars.io.TemplateLoader;
4042
import com.github.jknack.handlebars.io.TemplateSource;
@@ -50,6 +52,12 @@
5052
*/
5153
class Partial extends HelperResolver {
5254

55+
/** Special hash/map properties that we need to override. */
56+
private static final List<List<PathExpression>> OVERRIDE_PROPERTIES = Arrays.asList(
57+
PathCompiler.compile("size"),
58+
PathCompiler.compile("empty")
59+
);
60+
5361
/**
5462
* The partial path.
5563
*/
@@ -84,6 +92,9 @@ class Partial extends HelperResolver {
8492
/** A partial block body. */
8593
private Template partial;
8694

95+
/** Used to clear context after partial block got executed. See #655. */
96+
private boolean decorate;
97+
8798
/**
8899
* Creates a new {@link Partial}.
89100
*
@@ -111,7 +122,23 @@ public void before(final Context context, final Writer writer) throws IOExceptio
111122
@Override
112123
public void after(final Context context, final Writer writer) throws IOException {
113124
LinkedList<Map<String, Template>> partials = context.data(Context.INLINE_PARTIALS);
114-
partials.removeLast();
125+
if (partials.size() > 0) {
126+
partials.removeLast();
127+
}
128+
}
129+
130+
@Override public boolean decorate() {
131+
return decorate;
132+
}
133+
134+
/**
135+
* Used to clear context after partial block got executed. See #655
136+
* @param decorate True to clear context.
137+
* @return This partial.
138+
*/
139+
Partial setDecorate(final boolean decorate) {
140+
this.decorate = decorate;
141+
return this;
115142
}
116143

117144
@Override
@@ -130,7 +157,7 @@ protected void merge(final Context context, final Writer writer)
130157

131158
if (pathIsPartialBlock && parentIsNotLastPartialBlock) {
132159
throw new IllegalArgumentException(
133-
callee + " does not provide a @partial-block for " + this
160+
callee + " does not provide a @partial-block for " + this
134161
);
135162
}
136163

@@ -141,10 +168,10 @@ protected void merge(final Context context, final Writer writer)
141168

142169
inlineTemplates.put("@partial-block",
143170
new PartialBlockForwardingTemplate(this,
144-
this.partial,
145-
inlineTemplates.get("@partial-block"),
146-
callee,
147-
handlebars
171+
this.partial,
172+
inlineTemplates.get("@partial-block"),
173+
callee,
174+
handlebars
148175
)
149176
);
150177
}
@@ -192,13 +219,13 @@ protected void merge(final Context context, final Writer writer)
192219
throw fnf;
193220
}
194221
}
195-
222+
} else {
223+
partials.removeLast();
196224
}
197225
context.data(Context.CALLEE, this);
198226
Map<String, Object> hash = hash(context);
199227
// HACK: hide/override local attribute with parent version (if any)
200-
hash.put("size", context.get("size"));
201-
hash.put("empty", context.get("empty"));
228+
override(context, hash, OVERRIDE_PROPERTIES);
202229
Context ctx = Context.newPartialContext(context, this.scontext, hash);
203230
template.apply(ctx, writer);
204231
context.data(Context.CALLEE, callee);
@@ -212,6 +239,24 @@ protected void merge(final Context context, final Writer writer)
212239
}
213240
}
214241

242+
/**
243+
* Check for property conflicts and resolve them. Basically we ignore internal properties from
244+
* Map if they already present in current context.
245+
*
246+
* @param context Current context.
247+
* @param hash Partial context.
248+
* @param properties Property to check for.
249+
*/
250+
private void override(final Context context, final Map<String, Object> hash,
251+
final List<List<PathExpression>> properties) {
252+
for (List<PathExpression> path : properties) {
253+
String key = path.toString();
254+
if (!hash.containsKey(key)) {
255+
hash.put(key, context.get(path));
256+
}
257+
}
258+
}
259+
215260
/**
216261
* @param callee parent template of the currently traversed template
217262
* @param partialBlock partial block candidate

handlebars/src/main/java/com/github/jknack/handlebars/internal/TemplateBuilder.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -594,6 +594,7 @@ public Object visitPartialBlock(final PartialBlockContext ctx) {
594594

595595
String startDelim = ctx.start.getText();
596596
Template partial = new Partial(handlebars, info.path, info.context, info.hash)
597+
.setDecorate(true)
597598
.setPartial(fn)
598599
.startDelimiter(startDelim.substring(0, startDelim.length() - 1))
599600
.endDelimiter(ctx.stop.getText())

handlebars/src/test/java/com/github/jknack/handlebars/issues/Issue643.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ public void shouldAllowES6LetOrConstLiterals() throws Exception {
1010
shouldCompileTo("template: {{empty}} "
1111
+ "{{> partial}}",
1212
$("partials", $("partial", "partial: {{empty}}"),
13-
"data", $("empty", false)),
14-
"template: true partial: true");
13+
"hash", $("empty", false)),
14+
"template: false partial: false");
1515
}
1616

1717
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package com.github.jknack.handlebars.issues;
2+
3+
import com.github.jknack.handlebars.v4Test;
4+
import org.junit.Test;
5+
6+
public class Issue655 extends v4Test {
7+
8+
@Test
9+
public void inlinePartialsLeak() throws Exception {
10+
shouldCompileTo("main has partials:<br>\n"
11+
+ "-------------<br>\n"
12+
+ "{{>inherit1}}\n"
13+
+ "-------------<br>\n"
14+
+ "{{>inherit2}}",
15+
$("hash", $,
16+
"partials",
17+
$(
18+
"base",
19+
"text from base partial<br>\n"
20+
+ "{{#>inlinePartial}}{{/inlinePartial}}<br>\n"
21+
+ "{{#>inlinePartial2}}{{/inlinePartial2}}<br>",
22+
"inherit1", "inherit1<br>\n"
23+
+ "{{#>base}}\n"
24+
+ " {{#*inline \"inlinePartial\"}}\n"
25+
+ " inline partial defined by inherit1, called from base\n"
26+
+ " {{/inline}}\n"
27+
+ " {{#*inline \"inlinePartial2\"}}\n"
28+
+ " {{>some-other-template}}\n"
29+
+ " {{/inline}}\n"
30+
+ "{{/base}}",
31+
"inherit2", "inherit2<br>\n"
32+
+ "{{#>base}}\n"
33+
+ "{{/base}}",
34+
"some-other-template", "template called from second inline partial of inherit 1")
35+
),
36+
"main has partials:<br>\n"
37+
+ "-------------<br>\n"
38+
+ "inherit1<br>\n"
39+
+ "text from base partial<br>\n"
40+
+ "\n"
41+
+ " inline partial defined by inherit1, called from base\n"
42+
+ " <br>\n"
43+
+ "\n"
44+
+ " template called from second inline partial of inherit 1\n"
45+
+ " <br>\n"
46+
+ "-------------<br>\n"
47+
+ "inherit2<br>\n"
48+
+ "text from base partial<br>\n"
49+
+ "<br>\n<br>");
50+
}
51+
}

0 commit comments

Comments
 (0)