Compatibility

Does Obfuscation Break Set Operations?

The Set operations are the kind of addition that removes a utility file. union, intersection, difference, symmetricDifference, and the three predicates isSubsetOf, isSupersetOf and isDisjointFrom. They read like permission checks and tag filters because that is mostly what they get used for, which puts them on the short list of things worth knowing are safe before you protect an authorization path with them.

What was measured

Two sets of colour names, then every operation the language offers between them, with the results printed as sorted strings rather than compared as objects. Then the three predicates, each with a case chosen to return a value rather than a constant. Then the ordinary Set surface -- add, size, has, delete -- because a result about the new methods is not much use if the old ones moved. Then a small permission model built out of two sets and a difference call, which is the shape this feature actually appears in.

And then the case that turned out to carry the whole article: a set-like object. The specification does not require a real Set as the argument to these methods. It accepts any object exposing a numeric size, a has method and a keys method, which is what lets a custom collection participate without copying itself into a Set first.

Protected in five configurations -- the default target, the modern target, both gate profiles and the string-transform profile -- every one of the thirteen results was identical. Union, intersection, difference and symmetric difference all produced the same members in the same order; all three predicates returned the same booleans; the permission model still reported the same missing permission and the same authorization decision.

The reason is the one this site keeps arriving at for collections. The members here are string values, not property names, and a value is not a rename site. The string transforms change where a literal lives and how it is encoded, not what it decodes to, so a set of colour names is a set of colour names on both sides of the build.

A real Set is immune to renaming its own methods

A member pattern matching size, or matching has, left every operation on the real sets working perfectly. All seven operations and all three predicates produced correct results, and so did the plain add, size, has and delete sequence. That is worth stating because it is not obvious.

The explanation is the same one that protects a helper chain over a built-in iterator. Set.prototype.has and the size accessor are implemented inside the engine. They are not properties of any object literal in your file, so a member pattern has nothing in your source to rewrite, and the calls keep resolving to the engine's implementations.

Renaming the operation names themselves is a different matter and fails in the ordinary way. A pattern matching union, intersection, difference and symmetricDifference threw on the first line with TypeError: a._0x1 is not a function. A pattern matching the three predicates got further -- through all four operations -- and then threw with the same shape. Immediate, loud, and the generated name says what happened.

So on a real Set, this whole surface has exactly one failure mode and it is the well-behaved one: a member pattern that is too broad takes out the method it names, on the first call, with a diagnostic that points at renaming.

The set-like object is where it gets interesting

Point any of these operations at an object you wrote and the situation inverts, because now the platform is reading properties out of your file. Measured with a pattern matching only size, every operation on the real sets still passed, and then the first set-like call threw TypeError: The .size property is NaN.

Read that message against the source. There is a property called size, it is right there, and its value is the number three. The emitted file has renamed it to a generated name, so the platform looked for size, found nothing, coerced the absent value, and reported that a property you are looking at is not a number. It is an unusually good error message pointing at an unusually confusing situation.

The has arm is even more direct. A pattern matching has produced TypeError: string "has" is not a function -- the platform quoting the exact name it went looking for, from a specification step that fetches the method and checks that it is callable. Once again the property exists in the source and does not exist in the build.

This is the same category as an Intl options dictionary, a fetch init object, a stream's underlying source and a custom iterable's next. The object is yours; the reader is not. Every one of those has produced a sharp result in this series, and the pattern is now reliable enough to use as a design rule rather than a curiosity.

The names you own are safe, measured

The control arm matched granted, required and missing -- the fields and method of the permission model, all of them read only by code in the same file. Every result was identical on both targets. The model still reported admin as the missing permission and still returned false for the authorization check.

That is the boundary this series keeps drawing, and set operations draw it unusually cleanly because both halves appear in one small file. Names reached only by code you control are safe to rename. Names that something outside your file reads by name are not, whether that something is the language, the platform, a library or your own string-based reflection.

It is worth being precise about which half of the contract a sample owns, because a measurement that owns both halves proves nothing. Here the reader of size, has and keys is the engine's implementation of union, which is genuinely outside the file. The reader of granted and required is a method twenty lines further down, which is genuinely inside it.

The practical consequence for an authorization path is mild but specific. Building permission checks on real Set objects and your own field names is safe under member renaming. Building them on a custom collection that pretends to be a Set is the case to exclude from your member pattern, and it is the case least likely to be covered by a test that only exercises the happy path with real sets.

Ordering, and one thing these methods do not give you

The results kept their insertion order across every configuration, which matters because these methods have a specified order rather than an arbitrary one -- union yields the receiver's members first, then the argument's new ones. The measured output was identical, so code that relies on that order, including anything that joins the result into a string for display or for a cache key, behaves the same after protection.

That ties into the broader result on this site about object key order, and it points the same way: protection does not reorder collections, and where ordering appears to change the cause is usually renaming making a lookup miss rather than anything reordering.

One boundary should be stated so this page is not read as more than it is. Set operations are a correctness and readability feature, not a protection feature. Putting a permission check into a difference call does not make the check any harder to find or to edit in a shipped bundle than an if statement would be, and it should not be treated as a control.

The authorization decision that matters still has to be made somewhere the user cannot edit. A protected client can express a permission model clearly and can raise the cost of quietly changing it, but the enforcement belongs on the server, and this site makes that point consistently rather than making an exception for a tidier API.

How to check your own build

If a Set operation is throwing after protection, read the error text first, because these two produce unusually specific messages. A message naming a generated method -- _0x1 is not a function -- means a member pattern reached the operation name. A message mentioning .size being NaN, or quoting "has", means the argument was a set-like object of your own whose contract properties were renamed.

If nothing is throwing but a result is wrong, check whether the argument is a real Set. Every failure measured here was loud, so a silently wrong result is more likely to be a genuine logic difference than a protection artefact, and comparing the same operation on the unprotected build will settle it in one run.

For a set-like collection you intend to keep, the fix is to exclude size, has and keys from your member pattern, or to give the class a real Symbol.iterator and hand a genuine Set to the operation. Both work; the second removes the contract from your file entirely, which is the more durable of the two.

As a general check, one assertion per operation on the values you actually use is enough. These methods have no options dictionary and no callbacks, which is exactly why the base column was clean in all five configurations -- there is very little surface for a build step to interact with.

Frequently asked questions

Do JavaScript Set operations survive obfuscation?

Yes. Union, intersection, difference, symmetricDifference and the isSubsetOf, isSupersetOf and isDisjointFrom predicates were measured producing identical results in five protection configurations, including both output targets and the string-transform profile. Set members are values rather than property names, and a value is not a rename site.

Does member renaming break Set methods?

Renaming the operation names does, in the ordinary loud way: a pattern matching union and its siblings threw on the first call with a message naming a generated method. Renaming size or has does not affect a real Set at all, because those are implemented inside the engine rather than being properties of any object in your file.

Why does the error say my .size property is NaN when I can see it?

Because the argument was a set-like object of your own and a member pattern renamed its size property. The platform reads size, has and keys off such an object by name, found no property called size in the emitted file, and reported the absent value. The property is genuinely there in your source and genuinely not there in the build.

What is a set-like object and why does it behave differently?

It is any object exposing a numeric size, a has method and a keys method, which the Set operations accept in place of a real Set. It behaves differently because the contract lives in your file rather than in the engine, so a member pattern can reach it. This is the same category as an Intl options dictionary or a custom iterable's next method.

Is it safe to rename my own fields on objects that use sets?

Yes, and it was measured. A pattern matching the field and method names of a small permission model built on two sets changed nothing observable on either target, because those names are read only by code in the same file. The rule is that names reached only by code you control are safe, and names something outside your file reads are not.

Does protection change the order of the results?

No. These methods have a specified order rather than an arbitrary one, and the measured output kept insertion order identically across all five configurations. Code that joins a result into a string for display or for a cache key behaves the same after protection.

Can I use a set difference to hide a permission check?

Not usefully. Set operations are a correctness and readability feature rather than a protection control, and expressing a permission model through difference does not make it meaningfully harder to find or edit in a shipped bundle than an if statement would be. Protection raises the cost of quiet tampering, but the decision that matters still belongs on the server.

Related reading