Saravanan Subiramaniam
11/30/2025, 1:06 PMuse(groovy.time.TimeCategory) {
new Date() - 7.months
}
Please note that this script works when running using standalone Groovy (eg: using groovy command), but fails in our application (Java based) with this error. Both groovy-dateutil and groovy-datetime dependencies are in the classpath. Our application uses Java 21.
Dynamic logic TESTUNIT_2:
1: *ERROR* --> use(groovy.time.TimeCategory) {
2: new Date() - 7.months
3: }
groovy.lang.MissingMethodException: No signature of method: minus for class: java.util.Date is applicable for argument types: (groovy.time.DatumDependentDuration) values: [7 months]
Possible solutions: minus(java.util.Date), minus(int), find(), find(groovy.lang.Closure), is(java.lang.Object), plus(int)
at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.unwrap(ScriptBytecodeAdapter.java:74)
at org.codehaus.groovy.vmplugin.v8.IndyGuardsFiltersAndSignatures.unwrap(IndyGuardsFiltersAndSignatures.java:163)
at org.codehaus.groovy.vmplugin.v8.IndyInterface.fromCache(IndyInterface.java:344)
at ohi.dynamiclogic.testunit_2.TESTUNIT_2$_run_closure1.doCall(ohi.dynamiclogic.testunit_2.TESTUNIT_2.groovy:2)
at ohi.dynamiclogic.testunit_2.TESTUNIT_2$_run_closure1.doCall(ohi.dynamiclogic.testunit_2.TESTUNIT_2.groovy)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:338)
at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:274)
at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:270)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1008)
at groovy.lang.Closure.call(Closure.java:471)
at groovy.lang.Closure.call(Closure.java:450)
at org.codehaus.groovy.runtime.GroovyCategorySupport$ThreadCategoryInfo.use(GroovyCategorySupport.java:124)
at org.codehaus.groovy.runtime.GroovyCategorySupport.use(GroovyCategorySupport.java:262)
at org.codehaus.groovy.runtime.DefaultGroovyMethods.use(DefaultGroovyMethods.java:17206)
at org.codehaus.groovy.runtime.dgm$1263.doMethodInvoke(Unknown Source)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1235)
at org.codehaus.groovy.vmplugin.v8.IndyInterface.fromCache(IndyInterface.java:344)
at ohi.dynamiclogic.testunit_2.TESTUNIT_2.run(ohi.dynamiclogic.testunit_2.TESTUNIT_2.groovy:1)jonnybot
12/01/2025, 7:27 PMgroovyConsole using Java 17 and 21 in Groovy 5.0.2 and it worked okay. That suggests there's something environmental going on.Saravanan Subiramaniam
12/02/2025, 2:04 AMjonnybot
12/02/2025, 5:46 AMjonnybot
12/02/2025, 5:47 AMuse method in https://docs.groovy-lang.org/latest/html/api/org/codehaus/groovy/runtime/GroovyCategorySupport.html if you're wanting to look under the hood.Saravanan Subiramaniam
12/02/2025, 6:05 AMSaravanan Subiramaniam
12/04/2025, 6:01 AMinvokeConstructor, invokeStaticMethod and invokeMethod to intercept the calls and perform security checks. When I remove this SecureMetaClass, then minus operation worked fine in our product. For now, we'll use normal MetaClass for Date - to fix this issue. But I'm wondering what's wrong with our implementation and is there a recommended/best practices for doing such checks. I'm aware of SecureASTCustomizer, but it's a compile-time check, which may not cover all the scenarios and hence we wanted to do check at runtime.Saravanan Subiramaniam
12/04/2025, 6:02 AMMetaClassRegistry metaClassRegistry = getMetaClassRegistry();
metaClassRegistry.setMetaClassCreationHandle(new SecureMetaClassCreationHandle());
the SecureMetaClassCreationHandle looks like this
public class SecureMetaClassCreationHandle extends MetaClassRegistry.MetaClassCreationHandle {
@Override
protected MetaClass createNormalMetaClass(Class theClass, MetaClassRegistry registry) {
if (GeneratedClosure.class.isAssignableFrom(theClass)) {
return new ClosureMetaClass(registry, theClass);
} else {
return new SecureMetaClass(registry, theClass);
}
}
}
the SecureMetaClass looks like this
public class SecureMetaClass extends MetaClassImpl {
private static final String staticOrNullTarget = "[Static|Null]";
public SecureMetaClass(Class theClass, MetaMethod[] add) {
super(theClass, add);
}
public SecureMetaClass(Class theClass) {
super(theClass);
}
public SecureMetaClass(MetaClassRegistry registry, Class theClass, MetaMethod[] add) {
super(registry, theClass, add);
}
public SecureMetaClass(MetaClassRegistry registry, Class theClass) {
super(registry, theClass);
}
@Override
public CallSite createPojoCallSite(CallSite site, Object receiver, Object[] args) {
CallSiteKey callSiteKey = new CallSiteKey(getReceiverName(receiver), site.getName());
checkRestrictions(receiver, callSiteKey, args);
return super.createPojoCallSite(site, receiver, args);
}
@Override
public CallSite createStaticSite(CallSite site, Object[] args) {
CallSiteKey callSiteKey = new CallSiteKey(staticOrNullTarget, site.getName());
checkRestrictions(null, callSiteKey, args);
return super.createStaticSite(site, args);
}
@Override
public Object invokeConstructor(Object[] arguments) {
MetaReceiver metaReceiver = new MetaReceiver(null, getTheClass());
CallSiteKey callSiteKey = new CallSiteKey(staticOrNullTarget, "<$constructor$>");
checkRestrictions(metaReceiver, callSiteKey, arguments);
return super.invokeConstructor(arguments);
}
@Override
public Object invokeStaticMethod(Object object, String methodName, Object[] arguments) {
CallSiteKey callSiteKey = new CallSiteKey(staticOrNullTarget, methodName);
checkRestrictions(null, callSiteKey, arguments);
return super.invokeStaticMethod(object, methodName, arguments);
}
@Override
public Object invokeMethod(Object object, String methodName, Object arguments) {
CallSiteKey callSiteKey = new CallSiteKey(getReceiverName(object), methodName);
if (arguments == null) {
checkRestrictions(object, callSiteKey, MetaClassHelper.EMPTY_ARRAY);
}
if (arguments instanceof Tuple<?> tuple) {
checkRestrictions(object, callSiteKey, tuple.toArray());
}
if (arguments instanceof Object[] object1s) {
checkRestrictions(object, callSiteKey, object1s);
}
return super.invokeMethod(object, methodName, arguments);
}
private String getReceiverName(Object receiver) {
if (receiver != null) {
if (receiver instanceof GroovyObject) {
return GroovyObject.class.cast(receiver).getMetaClass().getTheClass().getCanonicalName();
} else {
return receiver.getClass().getCanonicalName();
}
} else {
return staticOrNullTarget;
}
}
private void checkRestrictions(Object object, CallSiteKey callSiteKey, Object[] args) {
// Check if constructor/method/static method calls are allowed based on whitelist/blacklist rules
}
private static class CallSiteKey {
private String siteReceiver;
private String siteName;
public CallSiteKey(String siteReceiver, String siteName) {
this.siteReceiver = siteReceiver;
this.siteName = siteName;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
CallSiteKey that = (CallSiteKey) o;
return Objects.equals(siteName, that.siteName) &&
Objects.equals(siteReceiver, that.siteReceiver);
}
@Override
public int hashCode() {
return Objects.hash(siteReceiver, siteName);
}
@Override
public String toString() {
return "CallSiteKey{" +
"sitReceiver='" + siteReceiver + "'\''" +
", siteName='" + siteName + '\'' +
'}';
}
}
}