From 86d595b08653a7b814b969530c75c4ced18fc043 Mon Sep 17 00:00:00 2001 From: Martin Carlberg Date: Wed, 14 May 2025 10:09:01 +0200 Subject: [PATCH] Fixed: Make the isa property on classes not enumerable. (#3100) As we have a tool-free bridges between many classes and JavaScript types by setting the isa property on the type, for example CPArray and Array. Using the for...in enumeration the isa property will be included in the loop if the property is enumerable (this is the default behavior). This commit set the property as not enumerable. Also added test case for check that the isa property on classes are not enumerable using a for...in loop. --- Foundation/CPArray/_CPJavaScriptArray.j | 13 +++++++++++- Foundation/CPData.j | 26 ++++++++++++++++++++++-- Foundation/CPDate.j | 13 +++++++++++- Foundation/CPDictionary.j | 26 ++++++++++++++++++++++-- Foundation/CPError.j | 13 +++++++++++- Foundation/CPException.j | 26 ++++++++++++++++++++++-- Foundation/CPNumber.j | 27 +++++++++++++++++++++++-- Foundation/CPString.j | 13 +++++++++++- Foundation/CPURL.j | 13 +++++++++++- Tests/Foundation/CPArrayTest.j | 16 +++++++++++++++ 10 files changed, 173 insertions(+), 13 deletions(-) diff --git a/Foundation/CPArray/_CPJavaScriptArray.j b/Foundation/CPArray/_CPJavaScriptArray.j index 41653919b..454d5a670 100644 --- a/Foundation/CPArray/_CPJavaScriptArray.j +++ b/Foundation/CPArray/_CPJavaScriptArray.j @@ -360,4 +360,15 @@ var concat = Array.prototype.concat, @end -Array.prototype.isa = _CPJavaScriptArray; +if (Array.prototype.isa !== _CPJavaScriptArray) +{ + Object.defineProperties(Array.prototype, + { + isa: + { + value: _CPJavaScriptArray, + enumerable: false, + writable: true + } + }); +} diff --git a/Foundation/CPData.j b/Foundation/CPData.j index 1412e034a..78478cff3 100644 --- a/Foundation/CPData.j +++ b/Foundation/CPData.j @@ -214,5 +214,27 @@ @end -CFData.prototype.isa = CPData; -CFMutableData.prototype.isa = CPData; +if (CFData.prototype.isa !== CPData) +{ + Object.defineProperties(CFData.prototype, + { + isa: + { + value: CPData, + enumerable: false, + writable: true + } + }) +}; +if (CFMutableData.prototype.isa !== CPData) +{ + Object.defineProperties(CFMutableData.prototype, + { + isa: + { + value: CPData, + enumerable: false, + writable: true + } + }); +} diff --git a/Foundation/CPDate.j b/Foundation/CPDate.j index 000a2c849..79efc26ca 100644 --- a/Foundation/CPDate.j +++ b/Foundation/CPDate.j @@ -286,7 +286,18 @@ Date.parseISO8601 = function (date) return timestamp; }; -Date.prototype.isa = CPDate; +if (Date.prototype.isa !== CPDate) +{ + Object.defineProperties(Date.prototype, + { + isa: + { + value: CPDate, + enumerable: false, + writable: true + } + }); +} function _isNumberType(value) { diff --git a/Foundation/CPDictionary.j b/Foundation/CPDictionary.j index 1238f1e09..d2cd8f229 100755 --- a/Foundation/CPDictionary.j +++ b/Foundation/CPDictionary.j @@ -751,5 +751,27 @@ var CPDictionaryMaxDescriptionRecursion = 10; @end -CFDictionary.prototype.isa = CPDictionary; -CFMutableDictionary.prototype.isa = CPMutableDictionary; +if (CFDictionary.prototype.isa !== CPDictionary) +{ + Object.defineProperties(CFDictionary.prototype, + { + isa: + { + value: CPDictionary, + enumerable: false, + writable: true + } + }); +} +if (CFMutableDictionary.prototype.isa !== CPMutableDictionary) +{ + Object.defineProperties(CFMutableDictionary.prototype, + { + isa: + { + value: CPMutableDictionary, + enumerable: false, + writable: true + } + }); +} \ No newline at end of file diff --git a/Foundation/CPError.j b/Foundation/CPError.j index edc270514..8e120ca9d 100644 --- a/Foundation/CPError.j +++ b/Foundation/CPError.j @@ -166,5 +166,16 @@ var CPErrorCodeKey = @"CPErrorCodeKey", @end -CFError.prototype.isa = CPError; +if (CFError.prototype.isa !== CPError) +{ + Object.defineProperties(CFError.prototype, + { + isa: + { + value: CPError, + enumerable: false, + writable: true + } + }); +} diff --git a/Foundation/CPException.j b/Foundation/CPException.j index 70064a984..392cbe1b0 100755 --- a/Foundation/CPException.j +++ b/Foundation/CPException.j @@ -222,8 +222,30 @@ var CPExceptionNameKey = @"CPExceptionNameKey", // toll-free bridge Error to CPException // [CPException alloc] uses an objj_exception, which is a subclass of Error -Error.prototype.isa = CPException; -Error.prototype._userInfo = null; +if (Error.prototype.isa !== CPException) +{ + Object.defineProperties(Error.prototype, + { + isa: + { + value: CPException, + enumerable: false, + writable: true + } + }); +} +if (Error.prototype._userInfo !== null) +{ + Object.defineProperties(Error.prototype, + { + _userInfo: + { + value: null, + enumerable: false, + writable: true + } + }); +} [CPException initialize]; diff --git a/Foundation/CPNumber.j b/Foundation/CPNumber.j index 0a45c2bd2..c14caf47c 100644 --- a/Foundation/CPNumber.j +++ b/Foundation/CPNumber.j @@ -346,6 +346,29 @@ FIXME: Do we need this? @end -Number.prototype.isa = CPNumber; -Boolean.prototype.isa = CPNumber; +if (Number.prototype.isa !== CPNumber) +{ + Object.defineProperties(Number.prototype, + { + isa: + { + value: CPNumber, + enumerable: false, + writable: true + } + }); +} +if (Boolean.prototype.isa !== CPNumber) +{ + Object.defineProperties(Boolean.prototype, + { + isa: + { + value: CPNumber, + enumerable: false, + writable: true + } + }); +} + [CPNumber initialize]; diff --git a/Foundation/CPString.j b/Foundation/CPString.j index eeccf81b5..e642bab49 100644 --- a/Foundation/CPString.j +++ b/Foundation/CPString.j @@ -1010,4 +1010,15 @@ String.prototype.stripDiacritics = function() return output; }; -String.prototype.isa = CPString; +if (String.prototype.isa !== CPString) +{ + Object.defineProperties(String.prototype, + { + isa: + { + value: CPString, + enumerable: false, + writable: true + } + }) +}; diff --git a/Foundation/CPURL.j b/Foundation/CPURL.j index dd69280c5..8202fdbc8 100644 --- a/Foundation/CPURL.j +++ b/Foundation/CPURL.j @@ -260,4 +260,15 @@ var CPURLURLStringKey = @"CPURLURLStringKey", @end -CFURL.prototype.isa = [CPURL class]; +if (CFURL.prototype.isa !== CPURL) +{ + Object.defineProperties(CFURL.prototype, + { + isa: + { + value: CPURL, + enumerable: false, + writable: true + } + }) +}; diff --git a/Tests/Foundation/CPArrayTest.j b/Tests/Foundation/CPArrayTest.j index 828b70efe..850af1cbd 100644 --- a/Tests/Foundation/CPArrayTest.j +++ b/Tests/Foundation/CPArrayTest.j @@ -722,6 +722,22 @@ }]; } +// As we have a tool-free bridges between many classes and JavaScript types by setting the isa property +// on the type, for example Array and CPArray. Using the for...in enumeration the isa property should not +// be included in the loop as the property is now not enumerable. +- (void)testLoopingArrayWithForIn +{ + let array = @[@"a", @"b", @"c", @"d"], + i = 0; + + for (const element in array) { + i = i + 1; + element + element; // Remove warning that the variable is never read + } + + [self assert:i equals:4]; +} + @end @implementation AlwaysEqual : CPObject