Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Syntactically incorrect types cause nil types in Puppet::InfoService::ClassInformationService #9140

Closed
jonathannewman opened this issue Nov 1, 2023 · 9 comments · Fixed by #9190
Labels
bug Something isn't working triaged Jira issue has been created for this

Comments

@jonathannewman
Copy link

Describe the Bug

When a type is incorrectly specified, e.g. Integer[1-3] for a class parameter, Puppet's class_information_service ignores this error, and produces an empty type specification.

def extract_type(structure, p)
return structure if p.type_expr.nil?
structure[:type] = typeexpr_to_string(p.type_expr)
structure
end

Expected Behavior

A warning should be produced, and a default type should be assigned in the nil case to prevent incorrect behavior. Note that this issue does not impact catalog compilation, but does impact the ability to get classes manifest information.

Impacts both 7.x and 8.x

@jonathannewman jonathannewman added the bug Something isn't working label Nov 1, 2023
@joshcooper joshcooper added the triaged Jira issue has been created for this label Nov 1, 2023
Copy link

github-actions bot commented Nov 1, 2023

Migrated issue to PUP-11981

@joshcooper
Copy link
Contributor

joshcooper commented Nov 1, 2023

We recently saw a similar issue when a class parameter was defined as Optional[[String]] (note the double brackets). Seems like a test should have failed along the way before the code was deployed to puppetserver.

Edit: But none of our tests puppet-lint, unit, beaker) actually failed... I would have expected compilation to fail unless the compiler resolves the type to nil and maybe falls back to Any? More investigation is needed to understand why it's not getting caught earlier.

@hlindberg
Copy link
Contributor

That API does not have a way to report detail errors, it either produces a result (and skips bad input) or returns an error. Code should have been validated before deploying and using the class info service.

@joshcooper
Copy link
Contributor

The type Integer[1-3] is actually valid, because 1-3 = -2 and Integer[-2] matches any integer greater than or equal to -2. And this is valid too, because 4 > -2:

class foo(Integer[1-3] $i) {}
class { 'foo':  i => 4 }

The class info service is getting an error because it only accepts literal expressions:

rescue Puppet::ParseError
# type is to complex - contains expressions that are not literal
nil
end

Can we validate that class parameters don't contain non-literal expressions earlier? Or maybe a puppet-lint check?

@hlindberg
Copy link
Contributor

hlindberg commented Dec 13, 2023

Should be possible to validate.
There is a validator for type, and IIRC one (or a parameter) for static expression validation.

@joshcooper
Copy link
Contributor

joshcooper commented Dec 13, 2023

Hi @hlindberg I see we validate class definitions here, for example to ensure class parameter names are unique. Would it make sense to add a check that class parameter types are literal too?

diff --git a/lib/puppet/pops/issues.rb b/lib/puppet/pops/issues.rb
index b619636568..23cb80dbde 100644
--- a/lib/puppet/pops/issues.rb
+++ b/lib/puppet/pops/issues.rb
@@ -207,6 +207,9 @@ module Issues
     _("The numeric parameter name '$%{name}' cannot be used (clashes with numeric match result variables)") % { name: name }
   end
 
+  ILLEGAL_NONLITERAL_PARAMETER_TYPE = issue :ILLEGAL_NONLITERAL_PARAMETER_TYPE, :name, :type_class do
+    _("The parameter '$%{name}' must be a literal type, not %{type_class}") % { name: name, type_class: label.a_an(type_class) }
+  end
   # In certain versions of Puppet it may be allowed to assign to a not already assigned key
   # in an array or a hash. This is an optional validation that may be turned on to prevent accidental
   # mutation.

diff --git a/lib/puppet/pops/validation/checker4_0.rb b/lib/puppet/pops/validation/checker4_0.rb
index eb7ac5cc77..66f62d1895 100644
--- a/lib/puppet/pops/validation/checker4_0.rb
+++ b/lib/puppet/pops/validation/checker4_0.rb
@@ -471,6 +471,7 @@ class Checker4_0 < Evaluator::LiteralEvaluator
     internal_check_parameter_name_uniqueness(o)
     internal_check_reserved_params(o)
     internal_check_no_idem_last(o)
+    internal_check_parameter_type_literal(o)
   end
 
   def check_ResourceTypeDefinition(o)
@@ -683,6 +684,17 @@ class Checker4_0 < Evaluator::LiteralEvaluator
     end
   end
 
+  def internal_check_parameter_type_literal(o)
+    o.parameters.each do |p|
+      next unless p.type_expr
+      catch :not_literal do
+        literal(p.type_expr)
+        next
+      end
+      acceptor.accept(Issues::ILLEGAL_NONLITERAL_PARAMETER_TYPE, p, {name: p.name, type_class: p.type_expr.class})
+    end
+  end
+

The Checker4_0 inherits from LiteralEvaluator, so I'm just calling the super literal method.

However, we have tests that class parameter types can be QualifiedReferences which would be rejected by the above changes because the LiteralEvaluator says:

# Not considered literal
# QualifiedReference # i.e. File, FooBar

Since the ClassInfoService validates parameter types using the TypeParser, I tried using that instead, but it too rejects QualifiedReference:

(byebug) Puppet::Pops::Types::TypeParser.singleton.interpret_any(p, nil)
*** Puppet::ParseError Exception: The expression <Type[Bar] $x> is not a valid type specification.

Is there a different evaluator I should be using for class parameters? Or should I create a new evaluator and implement the missing literal_QualifiedReference(o)?

class Something < LiteralEvaluator

  def literal_QualifiedReference(o)
    ...
  end

I assume whatever we do, both the ClassInfoService and Checker4_0 should call the same validation code so that the compiler and class info service accept/reject the same types.

@hlindberg
Copy link
Contributor

The one issue that bothers me a bit is that any type of reference that isn't defined as a data type or one of the built-in is considered a reference to a class, defined resource-type, or built-in resource type. Thus I am thinking that when validation takes place it isn't known if it is a reference to an actual thing or not. It probably does not matter as the problem here is that it cannot be turned into a human-readable string for the info-service.

It will probably work to allow qualified references in a specialized evaluator like you suggest and then make sure it gets used in both places. The logic that turns QualifiedReference into a human-readable string probably already exists (i.e. the class where it now fails for the info-service).

I don't remember if the info-service also produces information for defined resources. If so the validator for those must do the same - otherwise it could be allowed (as for functions). In both those case it is still not good for generating documentation as it would need to produce somethng humany-readable. You may thus want to also validate functions to only accept literal type definitions. You should still allow it for lambdas and everywhere else - just not in any puppet language "definition" (i.e. define, class, function).

May have to be adjusted for Bolt as well if they use specialized validators. Also take a look at what Puppet String does if faced with non-literal type in definitions and how it handles if it gets a QualifiedReference.

The much simpler solution would be to consider QualifiedReference to be "literal" - it is after all a literal reference, but it can be faulty (not reference anything). I was very conservative when not allowing it to be a "literal". You could try that and see if it breaks any tests. Or maybe you tried that already? If changed it may affect Bolt and Puppet Strings.

Hope that helps.

AriaXLi added a commit to AriaXLi/puppet that referenced this issue Jan 11, 2024
This commit adds logic to validate class parameters are literal and provides a
descriptive warning if parameter(s) are not literal, adds QualifiedReference
and AccessExpression as a valid literal values & updates spec tests to check
for more cases of invalid class parameters and any that broke as a result of
the changes.

The logic to validate class parameters is done in
internal_check_parameter_type_literal method where the appropriate literal
method is called on each parameter. There are different literal methods for
each literal object/type [1]. For example if the parameter is String type, then
literal_LiteralString is called. If a parameter is found to be not literal,
then a :not_literal will be thrown & warning message with the invalid class
parameter and its type will be printed.

QualifiedReference is considered literal since it is a literal reference,
however, there is a possibility it could be a faulty dangling reference that
doesn't reference anything. AccessExpresions are anything with the Access
Operator ([ ]) such as Optional[String] or Variant[Boolean, Float] [2]. Since
these are valid class parameters, AccessExpressions are also considered
literal.

The logic to add QualifiedReference & AccessExpression as valid
literal values is done in the literal_QualifiedReference &
literal_AccessExpression methods. The literal_AccessExpression works by
validating each key in the expression is literal. The
literal_QualifiedReference method looks at o.value, similar to other existing
literal methods like literal_LiteralString.

[1] http://puppet-on-the-edge.blogspot.com/2014/02/puppet-internals-polymorphic-dispatch.html
[2] https://github.com/puppetlabs/puppet-specifications/blob/master/language/expressions.md#assignment-operator

For more information on this issue, see puppetlabs#9140
AriaXLi added a commit to AriaXLi/puppet that referenced this issue Jan 11, 2024
This commit adds logic to validate class parameters are literal and provides a
descriptive warning if parameter(s) are not literal, adds QualifiedReference
and AccessExpression as a valid literal values & updates spec tests to check
for more cases of invalid class parameters and any that broke as a result of
the changes.

The logic to validate class parameters is done in
internal_check_parameter_type_literal method where the appropriate literal
method is called on each parameter. There are different literal methods for
each literal object/type [1]. For example if the parameter is String type, then
literal_LiteralString is called. If a parameter is found to be not literal,
then a :not_literal will be thrown & warning message with the invalid class
parameter and its type will be printed.

QualifiedReference is considered literal since it is a literal reference,
however, there is a possibility it could be a faulty dangling reference that
doesn't reference anything. AccessExpresions are anything with the Access
Operator ([ ]) such as Optional[String] or Variant[Boolean, Float] [2]. Since
these are valid class parameters, AccessExpressions are also considered
literal.

The logic to add QualifiedReference & AccessExpression as valid
literal values is done in the literal_QualifiedReference &
literal_AccessExpression methods. The literal_AccessExpression works by
validating each key in the expression is literal. The
literal_QualifiedReference method looks at o.value, similar to other existing
literal methods like literal_LiteralString.

[1] http://puppet-on-the-edge.blogspot.com/2014/02/puppet-internals-polymorphic-dispatch.html
[2] https://github.com/puppetlabs/puppet-specifications/blob/master/language/expressions.md#assignment-operator

For more information on this issue, see puppetlabs#9140
AriaXLi added a commit to AriaXLi/puppet that referenced this issue Jan 11, 2024
This commit adds logic to validate class parameters are literal and provides a
descriptive warning if parameter(s) are not literal, adds QualifiedReference
and AccessExpression as a valid literal values & updates spec tests to check
for more cases of invalid class parameters and any that broke as a result of
the changes.

The logic to validate class parameters is done in
internal_check_parameter_type_literal method where the appropriate literal
method is called on each parameter. There are different literal methods for
each literal object/type [1]. For example if the parameter is String type, then
literal_LiteralString is called. If a parameter is found to be not literal,
then a :not_literal will be thrown & warning message with the invalid class
parameter and its type will be printed.

QualifiedReference is considered literal since it is a literal reference,
however, there is a possibility it could be a faulty dangling reference that
doesn't reference anything. AccessExpresions are anything with the Access
Operator ([ ]) such as Optional[String] or Variant[Boolean, Float] [2]. Since
these are valid class parameters, AccessExpressions are also considered
literal.

The logic to add QualifiedReference & AccessExpression as valid
literal values is done in the literal_QualifiedReference &
literal_AccessExpression methods. The literal_AccessExpression works by
validating each key in the expression is literal. The
literal_QualifiedReference method looks at o.value, similar to other existing
literal methods like literal_LiteralString.

[1] http://puppet-on-the-edge.blogspot.com/2014/02/puppet-internals-polymorphic-dispatch.html
[2] https://github.com/puppetlabs/puppet-specifications/blob/master/language/expressions.md#assignment-operator

For more information on this issue, see puppetlabs#9140
AriaXLi added a commit to AriaXLi/puppet that referenced this issue Jan 16, 2024
This commit adds logic to validate class parameters are literal and provides a
descriptive warning if parameter(s) are not literal, adds QualifiedReference
and AccessExpression as a valid literal values & updates spec tests to check
for more cases of invalid class parameters and any that broke as a result of
the changes.

The logic to validate class parameters is done in
internal_check_parameter_type_literal method where the appropriate literal
method is called on each parameter. There are different literal methods for
each literal object/type [1]. For example if the parameter is String type, then
literal_LiteralString is called. If a parameter is found to be not literal,
then a :not_literal will be thrown & warning message with the invalid class
parameter and its type will be printed.

QualifiedReference is considered literal since it is a literal reference,
however, there is a possibility it could be a faulty dangling reference that
doesn't reference anything. AccessExpresions are anything with the Access
Operator ([ ]) such as Optional[String] or Variant[Boolean, Float] [2]. Since
these are valid class parameters, AccessExpressions are also considered
literal.

The logic to add QualifiedReference & AccessExpression as valid
literal values is done in the literal_QualifiedReference &
literal_AccessExpression methods. The literal_AccessExpression works by
validating each key in the expression is literal. The
literal_QualifiedReference method looks at o.value, similar to other existing
literal methods like literal_LiteralString.

[1] http://puppet-on-the-edge.blogspot.com/2014/02/puppet-internals-polymorphic-dispatch.html
[2] https://github.com/puppetlabs/puppet-specifications/blob/master/language/expressions.md#assignment-operator

For more information on this issue, see puppetlabs#9140
AriaXLi added a commit to AriaXLi/puppet that referenced this issue Jan 16, 2024
This commit adds logic to validate class parameters are literal and provides a
descriptive warning if parameter(s) are not literal, adds QualifiedReference
and AccessExpression as a valid literal values & updates spec tests to check
for more cases of invalid class parameters and any that broke as a result of
the changes.

The logic to validate class parameters is done in
internal_check_parameter_type_literal method where the appropriate literal
method is called on each parameter. There are different literal methods for
each literal object/type [1]. For example if the parameter is String type, then
literal_LiteralString is called. If a parameter is found to be not literal,
then a :not_literal will be thrown & warning message with the invalid class
parameter and its type will be printed.

QualifiedReference is considered literal since it is a literal reference,
however, there is a possibility it could be a faulty dangling reference that
doesn't reference anything. AccessExpresions are anything with the Access
Operator ([ ]) such as Optional[String] or Variant[Boolean, Float] [2]. Since
these are valid class parameters, AccessExpressions are also considered
literal.

The logic to add QualifiedReference & AccessExpression as valid
literal values is done in the literal_QualifiedReference &
literal_AccessExpression methods. The literal_AccessExpression works by
validating each key in the expression is literal. The
literal_QualifiedReference method looks at o.value, similar to other existing
literal methods like literal_LiteralString.

[1] http://puppet-on-the-edge.blogspot.com/2014/02/puppet-internals-polymorphic-dispatch.html
[2] https://github.com/puppetlabs/puppet-specifications/blob/master/language/expressions.md#assignment-operator

For more information on this issue, see puppetlabs#9140
joshcooper pushed a commit to joshcooper/puppet that referenced this issue Jan 18, 2024
This commit adds logic to validate class parameters are literal and provides a
descriptive warning if parameter(s) are not literal, adds QualifiedReference
and AccessExpression as a valid literal values & updates spec tests to check
for more cases of invalid class parameters and any that broke as a result of
the changes.

The logic to validate class parameters is done in
internal_check_parameter_type_literal method where the appropriate literal
method is called on each parameter. There are different literal methods for
each literal object/type [1]. For example if the parameter is String type, then
literal_LiteralString is called. If a parameter is found to be not literal,
then a :not_literal will be thrown & warning message with the invalid class
parameter and its type will be printed.

QualifiedReference is considered literal since it is a literal reference,
however, there is a possibility it could be a faulty dangling reference that
doesn't reference anything. AccessExpresions are anything with the Access
Operator ([ ]) such as Optional[String] or Variant[Boolean, Float] [2]. Since
these are valid class parameters, AccessExpressions are also considered
literal.

The logic to add QualifiedReference & AccessExpression as valid
literal values is done in the literal_QualifiedReference &
literal_AccessExpression methods. The literal_AccessExpression works by
validating each key in the expression is literal. The
literal_QualifiedReference method looks at o.value, similar to other existing
literal methods like literal_LiteralString.

[1] http://puppet-on-the-edge.blogspot.com/2014/02/puppet-internals-polymorphic-dispatch.html
[2] https://github.com/puppetlabs/puppet-specifications/blob/master/language/expressions.md#assignment-operator

For more information on this issue, see puppetlabs#9140

(cherry picked from commit adbb02c)
AriaXLi added a commit to AriaXLi/puppet that referenced this issue Jan 22, 2024
This commit adds logic to validate class parameters are literal and provides a
descriptive warning if parameter(s) are not literal, adds QualifiedReference
and AccessExpression as a valid literal values & updates spec tests to check
for more cases of invalid class parameters and any that broke as a result of
the changes.

The logic to validate class parameters is done in
internal_check_parameter_type_literal method where the appropriate literal
method is called on each parameter. There are different literal methods for
each literal object/type [1]. For example if the parameter is String type, then
literal_LiteralString is called. If a parameter is found to be not literal,
then a :not_literal will be thrown & warning message with the invalid class
parameter and its type will be printed.

QualifiedReference is considered literal since it is a literal reference,
however, there is a possibility it could be a faulty dangling reference that
doesn't reference anything. AccessExpresions are anything with the Access
Operator ([ ]) such as Optional[String] or Variant[Boolean, Float] [2]. Since
these are valid class parameters, AccessExpressions are also considered
literal.

The logic to add QualifiedReference & AccessExpression as valid
literal values is done in the literal_QualifiedReference &
literal_AccessExpression methods. The literal_AccessExpression works by
validating each key in the expression is literal. The
literal_QualifiedReference method looks at o.value, similar to other existing
literal methods like literal_LiteralString.

[1] http://puppet-on-the-edge.blogspot.com/2014/02/puppet-internals-polymorphic-dispatch.html
[2] https://github.com/puppetlabs/puppet-specifications/blob/master/language/expressions.md#assignment-operator

For more information on this issue, see puppetlabs#9140

(cherry picked from commit adbb02c)
AriaXLi added a commit to AriaXLi/puppet that referenced this issue Jan 22, 2024
This commit adds logic to validate class parameters are literal and provides a
descriptive warning if parameter(s) are not literal, adds QualifiedReference
and AccessExpression as a valid literal values & updates spec tests to check
for more cases of invalid class parameters and any that broke as a result of
the changes.

The logic to validate class parameters is done in
internal_check_parameter_type_literal method where the appropriate literal
method is called on each parameter. There are different literal methods for
each literal object/type [1]. For example if the parameter is String type, then
literal_LiteralString is called. If a parameter is found to be not literal,
then a :not_literal will be thrown & warning message with the invalid class
parameter and its type will be printed.

QualifiedReference is considered literal since it is a literal reference,
however, there is a possibility it could be a faulty dangling reference that
doesn't reference anything. AccessExpresions are anything with the Access
Operator ([ ]) such as Optional[String] or Variant[Boolean, Float] [2]. Since
these are valid class parameters, AccessExpressions are also considered
literal.

The logic to add QualifiedReference & AccessExpression as valid
literal values is done in the literal_QualifiedReference &
literal_AccessExpression methods. The literal_AccessExpression works by
validating each key in the expression is literal. The
literal_QualifiedReference method looks at o.value, similar to other existing
literal methods like literal_LiteralString.

[1] http://puppet-on-the-edge.blogspot.com/2014/02/puppet-internals-polymorphic-dispatch.html
[2] https://github.com/puppetlabs/puppet-specifications/blob/master/language/expressions.md#assignment-operator

For more information on this issue, see puppetlabs#9140

(cherry picked from commit adbb02c)

(PUP-11981) Add test for non-literal class params in ClassInfoService spec

(cherry picked from commit 8992763)
@ibuclaw
Copy link

ibuclaw commented Mar 4, 2024

Why wasn't this listed as a breaking change? It breaks many downstream modules that expect i.e: Integer[-1] to work.

i.e: https://github.com/voxpupuli/puppet-dhcp/blob/master/manifests/init.pp#L29-L30

Puppet The parameter '$default_lease_time' must be a literal type, not a Puppet::Pops::Model::AccessExpression (file: /etc/puppetlabs/code/environments/production/modules/dhcp/manifests/init.pp, line: 29, column: 15)
Puppet The parameter '$max_lease_time' must be a literal type, not a Puppet::Pops::Model::AccessExpression (file: /etc/puppetlabs/code/environments/production/modules/dhcp/manifests/init.pp, line: 30, column: 15)

Not even the examples in the documentation work.
https://www.puppet.com/docs/puppet/7/lang_data_number#lang_data_number_numeric_type

class test (
Integer[-3,3] $foo,
Float[-3.0,3.0] $bar,
) {
}
$ puppet apply test.pp
Error: The parameter '$foo' must be a literal type, not a Puppet::Pops::Model::AccessExpression (file: test.pp, line: 2, column: 15)
Error: The parameter '$bar' must be a literal type, not a Puppet::Pops::Model::AccessExpression (file: test.pp, line: 3, column: 17)

@joshcooper
Copy link
Contributor

joshcooper commented Mar 4, 2024

@ibuclaw negative numbers were fixed in #9269

alanhargreaves pushed a commit to alanhargreaves/puppet that referenced this issue Mar 18, 2024
This commit adds logic to validate class parameters are literal and provides a
descriptive warning if parameter(s) are not literal, adds QualifiedReference
and AccessExpression as a valid literal values & updates spec tests to check
for more cases of invalid class parameters and any that broke as a result of
the changes.

The logic to validate class parameters is done in
internal_check_parameter_type_literal method where the appropriate literal
method is called on each parameter. There are different literal methods for
each literal object/type [1]. For example if the parameter is String type, then
literal_LiteralString is called. If a parameter is found to be not literal,
then a :not_literal will be thrown & warning message with the invalid class
parameter and its type will be printed.

QualifiedReference is considered literal since it is a literal reference,
however, there is a possibility it could be a faulty dangling reference that
doesn't reference anything. AccessExpresions are anything with the Access
Operator ([ ]) such as Optional[String] or Variant[Boolean, Float] [2]. Since
these are valid class parameters, AccessExpressions are also considered
literal.

The logic to add QualifiedReference & AccessExpression as valid
literal values is done in the literal_QualifiedReference &
literal_AccessExpression methods. The literal_AccessExpression works by
validating each key in the expression is literal. The
literal_QualifiedReference method looks at o.value, similar to other existing
literal methods like literal_LiteralString.

[1] http://puppet-on-the-edge.blogspot.com/2014/02/puppet-internals-polymorphic-dispatch.html
[2] https://github.com/puppetlabs/puppet-specifications/blob/master/language/expressions.md#assignment-operator

For more information on this issue, see puppetlabs#9140
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working triaged Jira issue has been created for this
Projects
None yet
4 participants