Streamline JSRetainPtr, fix leaks of JSString and JSGlobalContext
[WebKit-https.git] / Tools / WebKitTestRunner / InjectedBundle / Bindings / CodeGeneratorTestRunner.pm
1 # Copyright (C) 2010 Apple Inc. All rights reserved.
2 # Copyright (C) 2012 Samsung Electronics
3 #
4 # Redistribution and use in source and binary forms, with or without
5 # modification, are permitted provided that the following conditions
6 # are met:
7 # 1. Redistributions of source code must retain the above copyright
8 #    notice, this list of conditions and the following disclaimer.
9 # 2. Redistributions in binary form must reproduce the above copyright
10 #    notice, this list of conditions and the following disclaimer in the
11 #    documentation and/or other materials provided with the distribution.
12 #
13 # THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 # THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 # BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 # THE POSSIBILITY OF SUCH DAMAGE.
24
25 use strict;
26 use warnings;
27 use File::Spec;
28
29 package CodeGeneratorTestRunner;
30
31 use Carp qw<longmess>;
32 use Data::Dumper;
33
34 sub assert
35 {
36     my $message = shift;
37     
38     my $mess = longmess();
39     print Dumper($mess);
40
41     die $message;
42 }
43
44 sub new
45 {
46     my ($class, $codeGenerator, $writeDependencies, $verbose, $idlFilePath) = @_;
47
48     my $reference = {
49         codeGenerator => $codeGenerator,
50         idlFilePath => $idlFilePath,
51     };
52
53     bless($reference, $class);
54     return $reference;
55 }
56
57 sub GenerateInterface
58 {
59 }
60
61 sub WriteData
62 {
63     my ($self, $interface, $outputDir) = @_;
64
65     foreach my $file ($self->_generateHeaderFile($interface), $self->_generateImplementationFile($interface)) {
66         $$self{codeGenerator}->UpdateFile(File::Spec->catfile($outputDir, $$file{name}), join("", @{$$file{contents}}));
67     }
68 }
69
70 sub _className
71 {
72     my ($type) = @_;
73
74     return "JS" . _implementationClassName($type);
75 }
76
77 sub _classRefGetter
78 {
79     my ($self, $type) = @_;
80
81     return $$self{codeGenerator}->WK_lcfirst(_implementationClassName($type)) . "Class";
82 }
83
84 sub _parseLicenseBlock
85 {
86     my ($fileHandle) = @_;
87
88     my ($copyright, $readCount, $buffer, $currentCharacter, $previousCharacter);
89     my $startSentinel = "/*";
90     my $lengthOfStartSentinel = length($startSentinel);
91     $readCount = read($fileHandle, $buffer, $lengthOfStartSentinel);
92     return "" if ($readCount < $lengthOfStartSentinel || $buffer ne $startSentinel);
93     $copyright = $buffer;
94
95     while ($readCount = read($fileHandle, $currentCharacter, 1)) {
96         $copyright .= $currentCharacter;
97         return $copyright if $currentCharacter eq "/" && $previousCharacter eq "*";
98         $previousCharacter = $currentCharacter;
99     }
100
101     return "";
102 }
103
104 sub _parseLicenseBlockFromFile
105 {
106     my ($path) = @_;
107     open my $fileHandle, "<", $path or die "Failed to open $path for reading: $!";
108     my $licenseBlock = _parseLicenseBlock($fileHandle);
109     close($fileHandle);
110     return $licenseBlock;
111 }
112
113 sub _defaultLicenseBlock
114 {
115     return <<EOF;
116 /*
117  * Copyright (C) 2010 Apple Inc. All rights reserved.
118  *
119  * Redistribution and use in source and binary forms, with or without
120  * modification, are permitted provided that the following conditions
121  * are met:
122  * 1. Redistributions of source code must retain the above copyright
123  *    notice, this list of conditions and the following disclaimer.
124  * 2. Redistributions in binary form must reproduce the above copyright
125  *    notice, this list of conditions and the following disclaimer in the
126  *    documentation and/or other materials provided with the distribution.
127  *
128  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
129  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
130  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
131  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
132  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
133  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
134  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
135  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
136  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
137  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
138  * THE POSSIBILITY OF SUCH DAMAGE.
139  */
140 EOF
141 }
142
143 sub _licenseBlock
144 {
145     my ($self) = @_;
146     return $self->{licenseBlock} if $self->{licenseBlock};
147
148     my $licenseBlock = _parseLicenseBlockFromFile($self->{idlFilePath}) || _defaultLicenseBlock();
149     $self->{licenseBlock} = $licenseBlock;
150     return $licenseBlock;
151 }
152
153 sub _generateHeaderFile
154 {
155     my ($self, $interface) = @_;
156
157     my @contents = ();
158
159     my $type = $interface->type;
160     my $className = _className($type);
161     my $implementationClassName = _implementationClassName($type);
162     my $filename = $className . ".h";
163
164     push(@contents, $self->_licenseBlock());
165
166     my $parentClassName = _parentClassName($interface);
167
168     push(@contents, <<EOF);
169
170 #ifndef ${className}_h
171 #define ${className}_h
172
173 #include "${parentClassName}.h"
174 EOF
175     push(@contents, <<EOF);
176
177 namespace WTR {
178
179 class ${implementationClassName};
180
181 class ${className} : public ${parentClassName} {
182 public:
183     static JSClassRef @{[$self->_classRefGetter($type)]}();
184
185 private:
186     static const JSStaticFunction* staticFunctions();
187     static const JSStaticValue* staticValues();
188 EOF
189
190     if (my @operations = @{$interface->operations}) {
191         push(@contents, "\n    // Functions\n\n");
192         foreach my $operation (@operations) {
193             push(@contents, "    static JSValueRef @{[$operation->name]}(JSContextRef, JSObjectRef, JSObjectRef, size_t, const JSValueRef[], JSValueRef*);\n");
194         }
195     }
196
197     if (my @attributes = @{$interface->attributes}) {
198         push(@contents, "\n    // Attributes\n\n");
199         foreach my $attribute (@attributes) {
200             push(@contents, "    static JSValueRef @{[$self->_getterName($attribute)]}(JSContextRef, JSObjectRef, JSStringRef, JSValueRef*);\n");
201             push(@contents, "    static bool @{[$self->_setterName($attribute)]}(JSContextRef, JSObjectRef, JSStringRef, JSValueRef, JSValueRef*);\n") unless $attribute->isReadOnly;
202         }
203     }
204
205     push(@contents, <<EOF);
206 };
207     
208 ${implementationClassName}* to${implementationClassName}(JSContextRef, JSValueRef);
209
210 } // namespace WTR
211
212 #endif // ${className}_h
213 EOF
214
215     return { name => $filename, contents => \@contents };
216 }
217
218 sub _generateImplementationFile
219 {
220     my ($self, $interface) = @_;
221
222     my @contentsPrefix = ();
223     my %contentsIncludes = ();
224     my @contents = ();
225
226     my $type = $interface->type;
227     my $className = _className($type);
228     my $implementationClassName = _implementationClassName($type);
229     my $filename = $className . ".cpp";
230
231     push(@contentsPrefix, $self->_licenseBlock());
232
233     my $classRefGetter = $self->_classRefGetter($type);
234     my $parentClassName = _parentClassName($interface);
235
236     $contentsIncludes{"${className}.h"} = 1;
237     $contentsIncludes{"${implementationClassName}.h"} = 1;
238
239     push(@contentsPrefix, <<EOF);
240
241 EOF
242
243     push(@contents, <<EOF);
244 #include <JavaScriptCore/JSRetainPtr.h>
245 #include <wtf/GetPtr.h>
246
247 namespace WTR {
248
249 ${implementationClassName}* to${implementationClassName}(JSContextRef context, JSValueRef value)
250 {
251     if (!context || !value || !${className}::${classRefGetter}() || !JSValueIsObjectOfClass(context, value, ${className}::${classRefGetter}()))
252         return 0;
253     return static_cast<${implementationClassName}*>(JSWrapper::unwrap(context, value));
254 }
255
256 JSClassRef ${className}::${classRefGetter}()
257 {
258     static JSClassRef jsClass;
259     if (!jsClass) {
260         JSClassDefinition definition = kJSClassDefinitionEmpty;
261         definition.className = "@{[$type->name]}";
262         definition.parentClass = @{[$self->_parentClassRefGetterExpression($interface)]};
263         definition.staticValues = staticValues();
264         definition.staticFunctions = staticFunctions();
265 EOF
266
267     push(@contents, "        definition.initialize = initialize;\n") unless _parentInterface($interface);
268     push(@contents, "        definition.finalize = finalize;\n") unless _parentInterface($interface);
269
270     push(@contents, <<EOF);
271         jsClass = JSClassCreate(&definition);
272     }
273     return jsClass;
274 }
275
276 EOF
277
278     push(@contents, $self->_staticFunctionsGetterImplementation($interface), "\n");
279     push(@contents, $self->_staticValuesGetterImplementation($interface));
280
281     if (my @operations = @{$interface->operations}) {
282         push(@contents, "\n// Functions\n");
283
284         foreach my $operation (@operations) {
285             push(@contents, <<EOF);
286
287 JSValueRef ${className}::@{[$operation->name]}(JSContextRef context, JSObjectRef, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
288 {
289     ${implementationClassName}* impl = to${implementationClassName}(context, thisObject);
290     if (!impl)
291         return JSValueMakeUndefined(context);
292
293 EOF
294             my $functionCall;
295             if ($operation->extendedAttributes->{"CustomArgumentHandling"}) {
296                 $functionCall = "impl->" . $operation->name . "(context, argumentCount, arguments, exception)";
297             } else {
298                 my @arguments = ();
299                 my @specifiedArguments = @{$operation->arguments};
300
301                 $self->_includeHeaders(\%contentsIncludes, $operation->type);
302
303                 if ($operation->extendedAttributes->{"PassContext"}) {
304                     push(@arguments, "context");
305                 }
306
307                 foreach my $i (0..$#specifiedArguments) {
308                     my $argument = $specifiedArguments[$i];
309
310                     $self->_includeHeaders(\%contentsIncludes, $type);
311
312                     push(@contents, "    " . $self->_platformTypeVariableDeclaration($argument->type, $argument->name, "arguments[$i]", "argumentCount > $i") . "\n");
313                     
314                     push(@arguments, $self->_argumentExpression($argument));
315                 }
316
317                 $functionCall = "impl->" . $operation->name . "(" . join(", ", @arguments) . ")";
318             }
319             
320             push(@contents, "    ${functionCall};\n\n") if $operation->type->name eq "void";
321             push(@contents, "    return " . $self->_returnExpression($operation->type, $functionCall) . ";\n}\n");
322         }
323     }
324
325     if (my @attributes = @{$interface->attributes}) {
326         push(@contents, "\n// Attributes\n");
327         foreach my $attribute (@attributes) {
328             $self->_includeHeaders(\%contentsIncludes, $attribute->type);
329
330             my $getterName = $self->_getterName($attribute);
331             my $getterExpression = "impl->${getterName}()";
332
333             push(@contents, <<EOF);
334
335 JSValueRef ${className}::${getterName}(JSContextRef context, JSObjectRef object, JSStringRef, JSValueRef* exception)
336 {
337     ${implementationClassName}* impl = to${implementationClassName}(context, object);
338     if (!impl)
339         return JSValueMakeUndefined(context);
340
341     return @{[$self->_returnExpression($attribute->type, $getterExpression)]};
342 }
343 EOF
344
345             unless ($attribute->isReadOnly) {
346                 push(@contents, <<EOF);
347
348 bool ${className}::@{[$self->_setterName($attribute)]}(JSContextRef context, JSObjectRef object, JSStringRef, JSValueRef value, JSValueRef* exception)
349 {
350     ${implementationClassName}* impl = to${implementationClassName}(context, object);
351     if (!impl)
352         return false;
353
354 EOF
355
356                 my $platformValue = $self->_platformTypeConstructor($attribute->type, "value");
357
358                 push(@contents, <<EOF);
359     impl->@{[$self->_setterName($attribute)]}(${platformValue});
360
361     return true;
362 }
363 EOF
364             }
365         }
366     }
367
368     push(@contents, <<EOF);
369
370 } // namespace WTR
371
372 EOF
373
374     unshift(@contents, map { "#include \"$_\"\n" } sort keys(%contentsIncludes));
375     unshift(@contents, "#include \"config.h\"\n");
376     unshift(@contents, @contentsPrefix);
377
378     return { name => $filename, contents => \@contents };
379 }
380
381 sub _getterName
382 {
383     my ($self, $attribute) = @_;
384
385     return $attribute->name;
386 }
387
388 sub _includeHeaders
389 {
390     my ($self, $headers, $type) = @_;
391
392     return unless defined $type;
393     return if $type->name eq "boolean";
394     return if $type->name eq "object";
395     return if $$self{codeGenerator}->IsPrimitiveType($type);
396     return if $$self{codeGenerator}->IsStringType($type);
397
398     $$headers{_className($type) . ".h"} = 1;
399     $$headers{_implementationClassName($type) . ".h"} = 1;
400 }
401
402 sub _implementationClassName
403 {
404     my ($type) = @_;
405
406     return $type->name;
407 }
408
409 sub _parentClassName
410 {
411     my ($interface) = @_;
412
413     my $parentInterface = _parentInterface($interface);
414     return $parentInterface ? _className($parentInterface) : "JSWrapper";
415 }
416
417 sub _parentClassRefGetterExpression
418 {
419     my ($self, $interface) = @_;
420
421     my $parentInterface = _parentInterface($interface);
422     return $parentInterface ? $self->_classRefGetter($parentInterface) . "()" : "0";
423 }
424
425 sub _parentInterface
426 {
427     my ($interface) = @_;
428     return $interface->parentType;
429 }
430
431 sub _platformType
432 {
433     my ($self, $type) = @_;
434
435     return undef unless defined $type;
436
437     return "bool" if $type->name eq "boolean";
438     return "JSValueRef" if $type->name eq "object";
439     return "JSRetainPtr<JSStringRef>" if $$self{codeGenerator}->IsStringType($type);
440     return "double" if $$self{codeGenerator}->IsPrimitiveType($type);
441     return _implementationClassName($type);
442 }
443
444 sub _platformTypeConstructor
445 {
446     my ($self, $type, $argumentName) = @_;
447
448     return "JSValueToNullableBoolean(context, $argumentName)" if $type->name eq "boolean" && $type->isNullable;
449     return "JSValueToBoolean(context, $argumentName)" if $type->name eq "boolean";
450     return "$argumentName" if $type->name eq "object";
451     return "adopt(JSValueToStringCopy(context, $argumentName, nullptr))" if $$self{codeGenerator}->IsStringType($type);
452     return "JSValueToNumber(context, $argumentName, nullptr)" if $$self{codeGenerator}->IsPrimitiveType($type);
453     return "to" . _implementationClassName($type) . "(context, $argumentName)";
454 }
455
456 sub _platformTypeVariableDeclaration
457 {
458     my ($self, $type, $variableName, $argumentName, $condition) = @_;
459
460     my $platformType = $self->_platformType($type);
461     my $constructor = $self->_platformTypeConstructor($type, $argumentName);
462
463     my %nonPointerTypes = (
464         "bool" => 1,
465         "double" => 1,
466         "JSRetainPtr<JSStringRef>" => 1,
467         "JSValueRef" => 1,
468     );
469
470     my $nullValue = "0";
471     if ($platformType eq "JSValueRef") {
472         $nullValue = "JSValueMakeUndefined(context)";
473     } elsif (defined $nonPointerTypes{$platformType} && $platformType ne "double") {
474         $nullValue = "$platformType()";
475     }
476
477     $platformType .= "*" unless defined $nonPointerTypes{$platformType};
478
479     return "$platformType $variableName = $condition && $constructor;" if $condition && $platformType eq "bool";
480     return "$platformType $variableName = $condition ? $constructor : $nullValue;" if $condition;
481     return "$platformType $variableName = $constructor;";
482 }
483
484 sub _returnExpression
485 {
486     my ($self, $returnType, $expression) = @_;
487
488     return "JSValueMakeUndefined(context)" if $returnType->name eq "void";
489     return "JSValueMakeBooleanOrNull(context, ${expression})" if $returnType->name eq "boolean" && $returnType->isNullable;
490     return "JSValueMakeBoolean(context, ${expression})" if $returnType->name eq "boolean";
491     return "${expression}" if $returnType->name eq "object";
492     return "JSValueMakeNumber(context, ${expression})" if $$self{codeGenerator}->IsPrimitiveType($returnType);
493     return "JSValueMakeStringOrNull(context, ${expression}.get())" if $$self{codeGenerator}->IsStringType($returnType);
494     return "toJS(context, WTF::getPtr(${expression}))";
495 }
496
497 sub _argumentExpression
498 {
499     my ($self, $argument) = @_;
500
501     my $type = $argument->type;
502     my $name = $argument->name;
503
504     return "${name}.get()" if $$self{codeGenerator}->IsStringType($type);
505     return $name;
506 }
507
508 sub _setterName
509 {
510     my ($self, $attribute) = @_;
511
512     my $name = $attribute->name;
513
514     return "set" . $$self{codeGenerator}->WK_ucfirst($name);
515 }
516
517 sub _staticFunctionsGetterImplementation
518 {
519     my ($self, $interface) = @_;
520
521     my $mapFunction = sub {
522         my $name = $_->name;
523         my @attributes = qw(kJSPropertyAttributeDontDelete kJSPropertyAttributeReadOnly);
524         push(@attributes, "kJSPropertyAttributeDontEnum") if $_->extendedAttributes->{"DontEnum"};
525
526         return  "{ \"$name\", $name, " . join(" | ", @attributes) . " }";
527     };
528
529     return $self->_staticFunctionsOrValuesGetterImplementation($interface, "function", "{ 0, 0, 0 }", $mapFunction, $interface->operations);
530 }
531
532 sub _staticFunctionsOrValuesGetterImplementation
533 {
534     my ($self, $interface, $functionOrValue, $arrayTerminator, $mapFunction, $operationsOrAttributes) = @_;
535
536     my $className = _className($interface->type);
537     my $uppercaseFunctionOrValue = $$self{codeGenerator}->WK_ucfirst($functionOrValue);
538
539     my $result = <<EOF;
540 const JSStatic${uppercaseFunctionOrValue}* ${className}::static${uppercaseFunctionOrValue}s()
541 {
542 EOF
543
544     my @initializers = map(&$mapFunction, @{$operationsOrAttributes});
545     return $result . "    return 0;\n}\n" unless @initializers;
546
547     $result .= <<EOF
548     static const JSStatic${uppercaseFunctionOrValue} ${functionOrValue}s[] = {
549         @{[join(",\n        ", @initializers)]},
550         ${arrayTerminator}
551     };
552     return ${functionOrValue}s;
553 }
554 EOF
555 }
556
557 sub _staticValuesGetterImplementation
558 {
559     my ($self, $interface) = @_;
560
561     my $mapFunction = sub {
562         return if $_->extendedAttributes->{"NoImplementation"};
563
564         my $attributeName = $_->name;
565         my $getterName = $self->_getterName($_);
566         my $setterName = $_->isReadOnly ? "0" : $self->_setterName($_);
567         my @attributes = qw(kJSPropertyAttributeDontDelete);
568         push(@attributes, "kJSPropertyAttributeReadOnly") if $_->isReadOnly;
569         push(@attributes, "kJSPropertyAttributeDontEnum") if $_->extendedAttributes->{"DontEnum"};
570
571         return "{ \"$attributeName\", $getterName, $setterName, " . join(" | ", @attributes) . " }";
572     };
573
574     return $self->_staticFunctionsOrValuesGetterImplementation($interface, "value", "{ 0, 0, 0, 0 }", $mapFunction, $interface->attributes);
575 }
576
577 1;